Merge branch 'master' into clojure.data.xml
This commit is contained in:
commit
32dc498261
81 changed files with 3368 additions and 466 deletions
|
|
@ -12,6 +12,7 @@ jobs:
|
||||||
environment:
|
environment:
|
||||||
LEIN_ROOT: "true"
|
LEIN_ROOT: "true"
|
||||||
BABASHKA_PLATFORM: linux # could be used in jar name
|
BABASHKA_PLATFORM: linux # could be used in jar name
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
|
|
@ -67,9 +68,11 @@ jobs:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
environment:
|
environment:
|
||||||
LEIN_ROOT: "true"
|
LEIN_ROOT: "true"
|
||||||
GRAALVM_HOME: /home/circleci/graalvm-ce-java8-19.3.0
|
GRAALVM_HOME: /home/circleci/graalvm-ce-java8-19.3.1
|
||||||
BABASHKA_PLATFORM: linux # used in release script
|
BABASHKA_PLATFORM: linux # used in release script
|
||||||
BABASHKA_TEST_ENV: native
|
BABASHKA_TEST_ENV: native
|
||||||
|
BABASHKA_XMX: "-J-Xmx7g"
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
|
|
@ -99,14 +102,10 @@ jobs:
|
||||||
name: Download GraalVM
|
name: Download GraalVM
|
||||||
command: |
|
command: |
|
||||||
cd ~
|
cd ~
|
||||||
if ! [ -d graalvm-ce-java8-19.3.0 ]; then
|
if ! [ -d graalvm-ce-java8-19.3.1 ]; then
|
||||||
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.0/graalvm-ce-java8-linux-amd64-19.3.0.tar.gz
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
tar xzf graalvm-ce-java8-linux-amd64-19.3.0.tar.gz
|
tar xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
fi
|
fi
|
||||||
# - run:
|
|
||||||
# name: Install GraalVM SSL libs
|
|
||||||
# command: |
|
|
||||||
# .circleci/script/graalvm_ssl
|
|
||||||
- run:
|
- run:
|
||||||
name: Build binary
|
name: Build binary
|
||||||
command: |
|
command: |
|
||||||
|
|
@ -117,10 +116,6 @@ jobs:
|
||||||
command: |
|
command: |
|
||||||
script/test
|
script/test
|
||||||
script/run_lib_tests
|
script/run_lib_tests
|
||||||
# - run:
|
|
||||||
# name: Performance report
|
|
||||||
# command: |
|
|
||||||
# .circleci/script/performance
|
|
||||||
- run:
|
- run:
|
||||||
name: Release
|
name: Release
|
||||||
command: |
|
command: |
|
||||||
|
|
@ -128,7 +123,78 @@ jobs:
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- ~/.m2
|
- ~/.m2
|
||||||
- ~/graalvm-ce-java8-19.3.0
|
- ~/graalvm-ce-java8-19.3.1
|
||||||
|
key: linux-{{ 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
|
||||||
|
linux-static:
|
||||||
|
docker:
|
||||||
|
- image: circleci/clojure:lein-2.8.1
|
||||||
|
working_directory: ~/repo
|
||||||
|
environment:
|
||||||
|
LEIN_ROOT: "true"
|
||||||
|
GRAALVM_HOME: /home/circleci/graalvm-ce-java8-19.3.1
|
||||||
|
BABASHKA_PLATFORM: linux-static # used in release script
|
||||||
|
BABASHKA_TEST_ENV: native
|
||||||
|
BABASHKA_STATIC: true
|
||||||
|
BABASHKA_XMX: "-J-Xmx7g"
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: "Pull Submodules"
|
||||||
|
command: |
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- linux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
||||||
|
- 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-java8-19.3.1 ]; then
|
||||||
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
tar xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
fi
|
||||||
|
- run:
|
||||||
|
name: Build binary
|
||||||
|
command: |
|
||||||
|
script/compile
|
||||||
|
no_output_timeout: 30m
|
||||||
|
- run:
|
||||||
|
name: Run tests
|
||||||
|
command: |
|
||||||
|
script/test
|
||||||
|
script/run_lib_tests
|
||||||
|
- run:
|
||||||
|
name: Release
|
||||||
|
command: |
|
||||||
|
.circleci/script/release
|
||||||
|
- save_cache:
|
||||||
|
paths:
|
||||||
|
- ~/.m2
|
||||||
|
- ~/graalvm-ce-java8-19.3.1
|
||||||
key: linux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
key: linux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/release
|
path: /tmp/release
|
||||||
|
|
@ -141,9 +207,11 @@ jobs:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.0"
|
xcode: "9.0"
|
||||||
environment:
|
environment:
|
||||||
GRAALVM_HOME: /Users/distiller/graalvm-ce-java8-19.3.0/Contents/Home
|
GRAALVM_HOME: /Users/distiller/graalvm-ce-java8-19.3.1/Contents/Home
|
||||||
BABASHKA_PLATFORM: macos # used in release script
|
BABASHKA_PLATFORM: macos # used in release script
|
||||||
BABASHKA_TEST_ENV: native
|
BABASHKA_TEST_ENV: native
|
||||||
|
BABASHKA_XMX: "-J-Xmx7g"
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
|
|
@ -157,24 +225,20 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Install Clojure
|
name: Install Clojure
|
||||||
command: |
|
command: |
|
||||||
.circleci/script/install-clojure /usr/local
|
script/install-clojure /usr/local
|
||||||
- run:
|
- run:
|
||||||
name: Install Leiningen
|
name: Install Leiningen
|
||||||
command: |
|
command: |
|
||||||
.circleci/script/install-leiningen
|
script/install-leiningen
|
||||||
- run:
|
- run:
|
||||||
name: Download GraalVM
|
name: Download GraalVM
|
||||||
command: |
|
command: |
|
||||||
cd ~
|
cd ~
|
||||||
ls -la
|
ls -la
|
||||||
if ! [ -d graalvm-ce-java8-19.3.0 ]; then
|
if ! [ -d graalvm-ce-java8-19.3.1 ]; then
|
||||||
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.0/graalvm-ce-java8-darwin-amd64-19.3.0.tar.gz
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-darwin-amd64-19.3.1.tar.gz
|
||||||
tar xzf graalvm-ce-java8-darwin-amd64-19.3.0.tar.gz
|
tar xzf graalvm-ce-java8-darwin-amd64-19.3.1.tar.gz
|
||||||
fi
|
fi
|
||||||
# - run:
|
|
||||||
# name: Install GraalVM SSL libs
|
|
||||||
# command: |
|
|
||||||
# .circleci/script/graalvm_ssl
|
|
||||||
- run:
|
- run:
|
||||||
name: Build binary
|
name: Build binary
|
||||||
command: |
|
command: |
|
||||||
|
|
@ -185,10 +249,6 @@ jobs:
|
||||||
command: |
|
command: |
|
||||||
script/test
|
script/test
|
||||||
script/run_lib_tests
|
script/run_lib_tests
|
||||||
# - run:
|
|
||||||
# name: Performance report
|
|
||||||
# command: |
|
|
||||||
# .circleci/script/performance
|
|
||||||
- run:
|
- run:
|
||||||
name: Release
|
name: Release
|
||||||
command: |
|
command: |
|
||||||
|
|
@ -196,7 +256,7 @@ jobs:
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- ~/.m2
|
- ~/.m2
|
||||||
- ~/graalvm-ce-java8-19.3.0
|
- ~/graalvm-ce-java8-19.3.1
|
||||||
key: mac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
key: mac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/release
|
path: /tmp/release
|
||||||
|
|
@ -206,6 +266,7 @@ jobs:
|
||||||
command: |
|
command: |
|
||||||
./bb .circleci/script/publish_artifact.clj
|
./bb .circleci/script/publish_artifact.clj
|
||||||
deploy:
|
deploy:
|
||||||
|
resource_class: large
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/clojure:lein-2.8.1
|
- image: circleci/clojure:lein-2.8.1
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
|
|
@ -229,6 +290,7 @@ jobs:
|
||||||
- ~/.m2
|
- ~/.m2
|
||||||
key: v1-dependencies-{{ checksum "project.clj" }}
|
key: v1-dependencies-{{ checksum "project.clj" }}
|
||||||
docker:
|
docker:
|
||||||
|
resource_class: large
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/buildpack-deps:stretch
|
- image: circleci/buildpack-deps:stretch
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -249,6 +311,7 @@ workflows:
|
||||||
jobs:
|
jobs:
|
||||||
- jvm
|
- jvm
|
||||||
- linux
|
- linux
|
||||||
|
- linux-static
|
||||||
- mac
|
- mac
|
||||||
- deploy:
|
- deploy:
|
||||||
filters:
|
filters:
|
||||||
|
|
@ -257,6 +320,7 @@ workflows:
|
||||||
requires:
|
requires:
|
||||||
- jvm
|
- jvm
|
||||||
- linux
|
- linux
|
||||||
|
- linux-static
|
||||||
- mac
|
- mac
|
||||||
- docker:
|
- docker:
|
||||||
filters:
|
filters:
|
||||||
|
|
@ -265,4 +329,5 @@ workflows:
|
||||||
requires:
|
requires:
|
||||||
- jvm
|
- jvm
|
||||||
- linux
|
- linux
|
||||||
|
- linux-static
|
||||||
- mac
|
- mac
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ fi
|
||||||
if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then
|
if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then
|
||||||
echo "Building Docker image $image_name:$image_tag"
|
echo "Building Docker image $image_name:$image_tag"
|
||||||
echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin
|
echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin
|
||||||
docker build -t "$image_name" .
|
docker build -t "$image_name" --build-arg BABASHKA_XMX="-J-Xmx6900m" .
|
||||||
docker tag "$image_name:$latest_tag" "$image_name:$image_tag"
|
docker tag "$image_name:$latest_tag" "$image_name:$image_tag"
|
||||||
# we only update latest when it's not a SNAPSHOT version
|
# we only update latest when it's not a SNAPSHOT version
|
||||||
if [ "false" = "$snapshot" ]; then
|
if [ "false" = "$snapshot" ]; then
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
curl https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein > lein
|
|
||||||
sudo mkdir -p /usr/local/bin/
|
|
||||||
sudo mv lein /usr/local/bin/lein
|
|
||||||
sudo chmod a+x /usr/local/bin/lein
|
|
||||||
|
|
@ -1,23 +1,35 @@
|
||||||
(require '[clojure.java.shell :refer [sh]]
|
(require '[cheshire.core :refer [generate-string]]
|
||||||
'[clojure.java.io :as io]
|
'[clojure.java.io :as io]
|
||||||
'[cheshire.core :refer [generate-string]]
|
'[clojure.java.shell :refer [sh]]
|
||||||
'[clojure.string :as str])
|
'[clojure.string :as str])
|
||||||
|
|
||||||
(def channel "#babashka_circleci_builds")
|
(def channel "#babashka_circleci_builds")
|
||||||
#_(def channel "#_test")
|
#_(def channel "#_test")
|
||||||
(def babashka-version (str/trim (slurp (io/file "resources" "BABASHKA_VERSION"))))
|
(def babashka-version (str/trim (slurp (io/file "resources" "BABASHKA_VERSION"))))
|
||||||
|
|
||||||
(def text (format "[%s - %s@%s]: https://%s-201467090-gh.circle-artifacts.com/0/release/babashka-%s-%s-amd64.zip"
|
|
||||||
(System/getenv "BABASHKA_PLATFORM")
|
|
||||||
(System/getenv "CIRCLE_BRANCH")
|
|
||||||
(System/getenv "CIRCLE_SHA1")
|
|
||||||
(System/getenv "CIRCLE_BUILD_NUM")
|
|
||||||
babashka-version
|
|
||||||
(System/getenv "BABASHKA_PLATFORM")))
|
|
||||||
|
|
||||||
(def slack-hook-url (System/getenv "SLACK_HOOK_URL"))
|
(def slack-hook-url (System/getenv "SLACK_HOOK_URL"))
|
||||||
(when slack-hook-url
|
|
||||||
(let [json (generate-string {:username "borkdude"
|
(defn slack! [text]
|
||||||
:channel channel
|
(when slack-hook-url
|
||||||
:text text})]
|
(let [json (generate-string {:username "borkdude"
|
||||||
(sh "curl" "-X" "POST" "-H" "Content-Type: application/json" "-d" json slack-hook-url)))
|
:channel channel
|
||||||
|
:text text})]
|
||||||
|
(sh "curl" "-X" "POST" "-H" "Content-Type: application/json" "-d" json slack-hook-url))))
|
||||||
|
|
||||||
|
(def release-text (format "[%s - %s@%s]: https://%s-201467090-gh.circle-artifacts.com/0/release/babashka-%s-%s-amd64.zip"
|
||||||
|
(System/getenv "BABASHKA_PLATFORM")
|
||||||
|
(System/getenv "CIRCLE_BRANCH")
|
||||||
|
(System/getenv "CIRCLE_SHA1")
|
||||||
|
(System/getenv "CIRCLE_BUILD_NUM")
|
||||||
|
babashka-version
|
||||||
|
(System/getenv "BABASHKA_PLATFORM")))
|
||||||
|
|
||||||
|
(slack! release-text)
|
||||||
|
|
||||||
|
(def binary-size-text
|
||||||
|
(format "[%s - %s@%s] binary size: %s"
|
||||||
|
(System/getenv "BABASHKA_PLATFORM")
|
||||||
|
(System/getenv "CIRCLE_BRANCH")
|
||||||
|
(System/getenv "CIRCLE_SHA1")
|
||||||
|
(slurp (io/file "/tmp/bb_size/size"))))
|
||||||
|
|
||||||
|
(slack! binary-size-text)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ cp bb /tmp/release
|
||||||
VERSION=$(cat resources/BABASHKA_VERSION)
|
VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
|
||||||
cd /tmp/release
|
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 zip archive
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@
|
||||||
babashka.impl.File/gen-wrapper-fn-2 clojure.core/def
|
babashka.impl.File/gen-wrapper-fn-2 clojure.core/def
|
||||||
babashka.impl.Pattern/gen-wrapper-fn-2 clojure.core/def
|
babashka.impl.Pattern/gen-wrapper-fn-2 clojure.core/def
|
||||||
babashka.impl.Pattern/gen-constants clojure.core/declare}
|
babashka.impl.Pattern/gen-constants clojure.core/declare}
|
||||||
:linters {:unsorted-namespaces {:level :warning}}}
|
:linters {:unsorted-required-namespaces {:level :warning}}}
|
||||||
|
|
|
||||||
18
.dockerignore
Normal file
18
.dockerignore
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
.circleci/
|
||||||
|
.git/
|
||||||
|
.clj-kondo/
|
||||||
|
.github/
|
||||||
|
doc/
|
||||||
|
examples/
|
||||||
|
logo/
|
||||||
|
test-resources/
|
||||||
|
test/
|
||||||
|
.gitignore
|
||||||
|
.carve_ignore
|
||||||
|
.gitmodules
|
||||||
|
appveyor.yml
|
||||||
|
CHANGES.md
|
||||||
|
deps.edn
|
||||||
|
Dockerfile
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
github: borkdude # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: borkdude # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
patreon: borkdude
|
patreon: borkdude
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: babashka
|
||||||
ko_fi: borkdude
|
ko_fi: borkdude
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
|
|
||||||
8
.github/script/deploy
vendored
Executable file
8
.github/script/deploy
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ -z "$GITHUB_HEAD_REF" ] && [ "${GITHUB_REF##*/}" = "master" ]
|
||||||
|
then
|
||||||
|
lein deploy clojars
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0;
|
||||||
36
.github/script/docker
vendored
Executable file
36
.github/script/docker
vendored
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
image_name="borkdude/babashka"
|
||||||
|
image_tag=$(cat resources/BABASHKA_VERSION)
|
||||||
|
latest_tag="latest"
|
||||||
|
|
||||||
|
if [[ $image_tag =~ SNAPSHOT$ ]]
|
||||||
|
then
|
||||||
|
echo "This is a snapshot version"
|
||||||
|
snapshot="true"
|
||||||
|
else
|
||||||
|
echo "This is a non-snapshot version"
|
||||||
|
snapshot="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITHUB_HEAD_REF" ] && [ "${GITHUB_REF##*/}" = "master" ]
|
||||||
|
then
|
||||||
|
echo "Building Docker image $image_name:$image_tag"
|
||||||
|
echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin
|
||||||
|
docker build -t "$image_name" .
|
||||||
|
docker tag "$image_name:$latest_tag" "$image_name:$image_tag"
|
||||||
|
# we only update latest when it's not a SNAPSHOT version
|
||||||
|
if [ "false" = "$snapshot" ]; then
|
||||||
|
echo "Pushing image $image_name:$latest_tag"
|
||||||
|
docker push "$image_name:$latest_tag"
|
||||||
|
fi
|
||||||
|
# we update the version tag, even if it's a SNAPSHOT version
|
||||||
|
echo "Pushing image $image_name:$image_tag"
|
||||||
|
docker push "$image_name:$image_tag"
|
||||||
|
else
|
||||||
|
echo "Not publishing Docker image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0;
|
||||||
345
.github/workflows/build.yml
vendored
Normal file
345
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on: [push
|
||||||
|
, pull_request
|
||||||
|
]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
scratch:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Scratch
|
||||||
|
run: |
|
||||||
|
echo "Scratch"
|
||||||
|
|
||||||
|
jvm:
|
||||||
|
# ubuntu 18.04 comes with lein + java8 installed
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Cache deps
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-deps
|
||||||
|
with:
|
||||||
|
path: ~/.m2/repository
|
||||||
|
key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
|
- name: Fetch deps
|
||||||
|
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
lein deps
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
script/test
|
||||||
|
|
||||||
|
- name: Test libraries
|
||||||
|
run: |
|
||||||
|
sudo script/install-clojure
|
||||||
|
script/run_lib_tests
|
||||||
|
|
||||||
|
- name: Build uberjar
|
||||||
|
run: |
|
||||||
|
lein with-profiles +reflection do run
|
||||||
|
lein do clean, uberjar
|
||||||
|
|
||||||
|
- name: Babashka version
|
||||||
|
id: babashka-version
|
||||||
|
run: |
|
||||||
|
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
echo "##[set-output name=version;]${BABASHKA_VERSION}"
|
||||||
|
|
||||||
|
- name: Reflection artifact
|
||||||
|
run: |
|
||||||
|
cp reflection.json babashka-${{ steps.babashka-version.outputs.version }}-reflection.json
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: target/babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: reflection.json
|
||||||
|
path: babashka-${{ steps.babashka-version.outputs.version }}-reflection.json
|
||||||
|
|
||||||
|
linux:
|
||||||
|
needs: [jvm]
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: reflection.json
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- name: Cache deps
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-deps
|
||||||
|
with:
|
||||||
|
path: ~/.m2/repository
|
||||||
|
key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
|
- name: Cache GraalVM
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-graalvm
|
||||||
|
with:
|
||||||
|
path: ~/graalvm-ce-java8-19.3.1
|
||||||
|
key: ${{ runner.os }}-graalvm-19.3.1
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-graalvm-19.3.1
|
||||||
|
|
||||||
|
- name: Download GraalVM
|
||||||
|
run: |
|
||||||
|
cd ~
|
||||||
|
if ! [ -d graalvm-ce-java8-19.3.1 ]; then
|
||||||
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
tar xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Babashka version
|
||||||
|
id: babashka-version
|
||||||
|
run: |
|
||||||
|
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
echo "##[set-output name=version;]${BABASHKA_VERSION}"
|
||||||
|
|
||||||
|
- name: Build Linux native image
|
||||||
|
run: |
|
||||||
|
export BABASHKA_JAR=babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar
|
||||||
|
export BABASHKA_XMX="-J-Xmx6g"
|
||||||
|
export GRAALVM_HOME="$HOME/graalvm-ce-java8-19.3.1"
|
||||||
|
cp babashka-${{ steps.babashka-version.outputs.version }}-reflection.json reflection.json
|
||||||
|
script/compile
|
||||||
|
|
||||||
|
- name: Test binary
|
||||||
|
run: |
|
||||||
|
BABASHKA_TEST_ENV=native script/test
|
||||||
|
|
||||||
|
- name: Install clojure
|
||||||
|
run: |
|
||||||
|
sudo script/install-clojure /usr/local
|
||||||
|
|
||||||
|
- name: Test libraries
|
||||||
|
run: |
|
||||||
|
BABASHKA_TEST_ENV=native script/run_lib_tests
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
path: bb
|
||||||
|
name: babashka-${{ steps.babashka-version.outputs.version }}-linux-amd64.zip
|
||||||
|
|
||||||
|
linux-static:
|
||||||
|
needs: [jvm]
|
||||||
|
runs-on: ubuntu-16.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: reflection.json
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- name: Cache deps
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-deps
|
||||||
|
with:
|
||||||
|
path: ~/.m2/repository
|
||||||
|
key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
|
- name: Cache GraalVM
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-graalvm
|
||||||
|
with:
|
||||||
|
path: ~/graalvm-ce-java8-19.3.1
|
||||||
|
key: ${{ runner.os }}-graalvm-19.3.1
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-graalvm-19.3.1
|
||||||
|
|
||||||
|
- name: Download GraalVM
|
||||||
|
run: |
|
||||||
|
cd ~
|
||||||
|
if ! [ -d graalvm-ce-java8-19.3.1 ]; then
|
||||||
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
tar xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Babashka version
|
||||||
|
id: babashka-version
|
||||||
|
run: |
|
||||||
|
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
echo "##[set-output name=version;]${BABASHKA_VERSION}"
|
||||||
|
|
||||||
|
- name: Build Linux native image
|
||||||
|
run: |
|
||||||
|
export BABASHKA_JAR=babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar
|
||||||
|
export BABASHKA_XMX="-J-Xmx6g"
|
||||||
|
export GRAALVM_HOME="$HOME/graalvm-ce-java8-19.3.1"
|
||||||
|
export BABASHKA_STATIC=true
|
||||||
|
cp babashka-${{ steps.babashka-version.outputs.version }}-reflection.json reflection.json
|
||||||
|
script/compile
|
||||||
|
|
||||||
|
- name: Test binary
|
||||||
|
run: |
|
||||||
|
./bb '(+ 1 2 3)'
|
||||||
|
BABASHKA_TEST_ENV=native script/test
|
||||||
|
|
||||||
|
- name: Install clojure
|
||||||
|
run: |
|
||||||
|
sudo script/install-clojure
|
||||||
|
|
||||||
|
- name: Test libraries
|
||||||
|
run: |
|
||||||
|
BABASHKA_TEST_ENV=native script/run_lib_tests
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
path: bb
|
||||||
|
name: babashka-${{ steps.babashka-version.outputs.version }}-linux-static-amd64.zip
|
||||||
|
|
||||||
|
mac:
|
||||||
|
needs: [jvm]
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: reflection.json
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- name: Cache GraalVM
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-graalvm
|
||||||
|
with:
|
||||||
|
path: ~/graalvm-ce-java8-19.3.1
|
||||||
|
key: ${{ runner.os }}-graalvm-19.3.1
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-graalvm-19.3.1
|
||||||
|
|
||||||
|
- name: Download GraalVM
|
||||||
|
run: |
|
||||||
|
cd ~
|
||||||
|
if ! [ -d graalvm-ce-java8-19.3.1 ]; then
|
||||||
|
curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-darwin-amd64-19.3.1.tar.gz
|
||||||
|
tar xzf graalvm-ce-java8-darwin-amd64-19.3.1.tar.gz
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Babashka version
|
||||||
|
id: babashka-version
|
||||||
|
run: |
|
||||||
|
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
echo "##[set-output name=version;]${BABASHKA_VERSION}"
|
||||||
|
|
||||||
|
- name: Build macOS native image
|
||||||
|
run: |
|
||||||
|
export BABASHKA_JAR=babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar
|
||||||
|
export BABASHKA_XMX="-J-Xmx6g"
|
||||||
|
export GRAALVM_HOME="$HOME/graalvm-ce-java8-19.3.1/Contents/Home"
|
||||||
|
cp babashka-${{ steps.babashka-version.outputs.version }}-reflection.json reflection.json
|
||||||
|
script/compile
|
||||||
|
|
||||||
|
- name: Test binary
|
||||||
|
run: |
|
||||||
|
sudo script/install-leiningen
|
||||||
|
BABASHKA_TEST_ENV=native script/test
|
||||||
|
|
||||||
|
- name: Test libraries
|
||||||
|
run: |
|
||||||
|
sudo script/install-clojure
|
||||||
|
BABASHKA_TEST_ENV=native script/run_lib_tests
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
path: bb
|
||||||
|
name: babashka-${{ steps.babashka-version.outputs.version }}-macos-amd64.zip
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: [jvm, linux, linux-static, mac]
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Cache deps
|
||||||
|
uses: actions/cache@v1
|
||||||
|
id: cache-deps
|
||||||
|
with:
|
||||||
|
path: ~/.m2/repository
|
||||||
|
key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
env:
|
||||||
|
CLOJARS_USER: "${{ secrets.CLOJARS_USER }}"
|
||||||
|
CLOJARS_PASS: "${{ secrets.CLOJARS_PASS }}"
|
||||||
|
run: |
|
||||||
|
.github/script/deploy
|
||||||
|
|
||||||
|
docker:
|
||||||
|
needs: [jvm, linux, linux-static, mac]
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Docker build
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USER: "${{ secrets.DOCKERHUB_USER }}"
|
||||||
|
DOCKERHUB_PASS: "${{ secrets.DOCKERHUB_PASS }}"
|
||||||
|
run: |
|
||||||
|
.github/script/docker
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -15,4 +15,6 @@ pom.xml.asc
|
||||||
!java/src/babashka/impl/LockFix.class
|
!java/src/babashka/impl/LockFix.class
|
||||||
!test-resources/babashka/src_for_classpath_test/foo.jar
|
!test-resources/babashka/src_for_classpath_test/foo.jar
|
||||||
.cpcache
|
.cpcache
|
||||||
reflection.json
|
*reflection.json
|
||||||
|
/tmp
|
||||||
|
/reports
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -2,3 +2,6 @@
|
||||||
path = sci
|
path = sci
|
||||||
url = https://github.com/borkdude/sci
|
url = https://github.com/borkdude/sci
|
||||||
branch = master
|
branch = master
|
||||||
|
[submodule "babashka.curl"]
|
||||||
|
path = babashka.curl
|
||||||
|
url = https://github.com/borkdude/babashka.curl
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@
|
||||||
|
|
||||||
## Breaking changes
|
## Breaking changes
|
||||||
|
|
||||||
|
## v0.0.79
|
||||||
|
- [babashka.curl#9](https://github.com/borkdude/babashka.curl/issues/9):
|
||||||
|
BREAKING! Functions in `babashka.curl` like `get`, `post`, etc. now always
|
||||||
|
return a map with `:status`, `:body`, and `:headers`.
|
||||||
|
|
||||||
## v0.0.71
|
## v0.0.71
|
||||||
- #267 Change behavior of reader conditionals: the `:clj` branch is taken when
|
- #267 Change behavior of reader conditionals: the `:clj` branch is taken when
|
||||||
it occurs before a `:bb` branch.
|
it occurs before a `:bb` branch.
|
||||||
|
|
||||||
## v0.0.44 - 0.0.45
|
## v0.0.44 - 0.0.45
|
||||||
- #173: Rename `*in*` to `*input*` (in the `user` namespace). The reason for
|
- #173: Rename `*in*` to `*input*` (in the `user` namespace). The reason for
|
||||||
this is that itt shadowed `clojure.core/*in*` when used unqualified.
|
this is that it shadowed `clojure.core/*in*` when used unqualified.
|
||||||
|
|
||||||
## v0.0.43
|
## v0.0.43
|
||||||
- #160: Add support for `java.lang.ProcessBuilder`. See docs. This replaces the
|
- #160: Add support for `java.lang.ProcessBuilder`. See docs. This replaces the
|
||||||
|
|
|
||||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
See [doc/dev.md](doc/dev.md).
|
||||||
21
Dockerfile
21
Dockerfile
|
|
@ -1,20 +1,25 @@
|
||||||
FROM ubuntu AS BASE
|
FROM clojure:lein-2.9.1 AS BASE
|
||||||
|
ARG BABASHKA_XMX="-J-Xmx3g"
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt update
|
||||||
RUN apt-get install -yy curl unzip build-essential zlib1g-dev
|
RUN apt install --no-install-recommends -yy curl unzip build-essential zlib1g-dev
|
||||||
WORKDIR "/opt"
|
WORKDIR "/opt"
|
||||||
RUN curl -sLO https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
RUN curl -sLO https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
RUN tar -xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
RUN tar -xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||||
|
|
||||||
ENV GRAALVM_HOME="/opt/graalvm-ce-java8-19.3.1"
|
ENV GRAALVM_HOME="/opt/graalvm-ce-java8-19.3.1"
|
||||||
ENV JAVA_HOME="/opt/graalvm-ce-java8-19.3.1/bin"
|
ENV JAVA_HOME="/opt/graalvm-ce-java8-19.3.1/bin"
|
||||||
ENV PATH="$PATH:$JAVA_HOME"
|
ENV PATH="$PATH:$JAVA_HOME"
|
||||||
|
ENV BABASHKA_STATIC="true"
|
||||||
|
ENV BABASHKA_XMX=$BABASHKA_XMX
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apt install -y sudo
|
|
||||||
RUN ./.circleci/script/install-leiningen
|
|
||||||
RUN ./script/compile
|
RUN ./script/compile
|
||||||
RUN cp bb /usr/local/bin
|
|
||||||
|
|
||||||
|
|
||||||
FROM ubuntu:bionic
|
FROM alpine:latest
|
||||||
COPY --from=BASE /usr/local/bin/bb /usr/local/bin
|
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
RUN mkdir -p /usr/local/bin
|
||||||
|
COPY --from=BASE /opt/bb /usr/local/bin/bb
|
||||||
CMD ["bb"]
|
CMD ["bb"]
|
||||||
|
|
|
||||||
559
README.md
559
README.md
|
|
@ -1,10 +1,8 @@
|
||||||
<img src="logo/babashka.svg" width="425px">
|
<img src="logo/babashka.svg" width="425px">
|
||||||
|
|
||||||
[](https://circleci.com/gh/borkdude/babashka/tree/master)
|
[](https://circleci.com/gh/borkdude/babashka/tree/master)
|
||||||
[](https://clojars.org/borkdude/babashka)
|
|
||||||
[](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
|
[](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
|
||||||
|
[](https://opencollective.com/babashka) [](https://clojars.org/borkdude/babashka)
|
||||||
<!-- [](https://cljdoc.org/d/borkdude/babashka/CURRENT) -->
|
|
||||||
|
|
||||||
A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas of Bash.
|
A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas of Bash.
|
||||||
|
|
||||||
|
|
@ -14,80 +12,106 @@ A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas
|
||||||
<a href="https://github.com/laheadle">@laheadle</a> on Clojurians Slack
|
<a href="https://github.com/laheadle">@laheadle</a> on Clojurians Slack
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
## Quickstart
|
## Introduction
|
||||||
|
|
||||||
``` shellsession
|
The main idea behind babashka is to leverage Clojure in places where you would
|
||||||
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
|
be using bash otherwise.
|
||||||
$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *input*)'
|
|
||||||
("doc" "resources" "sci" "script" "src" "target" "test")
|
|
||||||
bb took 4ms.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rationale
|
|
||||||
|
|
||||||
The sweet spot for babashka is executing Clojure snippets or scripts in the same
|
|
||||||
space where you would use Bash.
|
|
||||||
|
|
||||||
As one user described it:
|
As one user described it:
|
||||||
|
|
||||||
> I’m quite at home in Bash most of the time, but there’s a substantial grey area of things that are too complicated to be simple in bash, but too simple to be worth writing a clj/s script for. Babashka really seems to hit the sweet spot for those cases.
|
> I’m quite at home in Bash most of the time, but there’s a substantial grey area of things that are too complicated to be simple in bash, but too simple to be worth writing a clj/s script for. Babashka really seems to hit the sweet spot for those cases.
|
||||||
|
|
||||||
Goals:
|
### Goals
|
||||||
|
|
||||||
* Fast startup / low latency. This is achieved by compiling to native using [GraalVM](https://github.com/oracle/graal).
|
* Low latency Clojure scripting alternative to JVM Clojure.
|
||||||
* Familiarity and portability. Keep migration barriers between bash and Clojure as low as possible by:
|
* Easy installation: grab the self-contained binary and run. No JVM needed.
|
||||||
- Gradually introducing Clojure expressions to existing bash scripts
|
* Familiarity and portability:
|
||||||
- Scripts written in babashka should also be able to run on the JVM without major changes.
|
- Scripts should be compatible with JVM Clojure as much as possible
|
||||||
* Multi-threading support similar to Clojure on the JVM
|
- Scripts should be platform-independent as much as possible. Babashka offers
|
||||||
* Batteries included (clojure.tools.cli, core.async, ...)
|
support for linux, macOS and Windows.
|
||||||
|
* Allow interop with commonly used classes like `java.io.File` and `System`
|
||||||
|
* Multi-threading support (`pmap`, `future`, `core.async`)
|
||||||
|
* Batteries included (tools.cli, cheshire, ...)
|
||||||
|
* Library support via popular tools like the `clojure` CLI
|
||||||
|
|
||||||
Non-goals:
|
### Non-goals
|
||||||
|
|
||||||
* Performance
|
* Performance<sup>1<sup>
|
||||||
* Provide a mixed Clojure/bash DSL (see portability).
|
* Provide a mixed Clojure/Bash DSL (see portability).
|
||||||
* Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.
|
* Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.
|
||||||
|
|
||||||
Babashka uses [sci](https://github.com/borkdude/sci) for interpreting Clojure. Sci
|
<sup>1<sup> Babashka uses [sci](https://github.com/borkdude/sci) for
|
||||||
implements a subset of Clojure and is not as performant as compiled code. If your script is taking more than a few seconds, Clojure on the JVM may be a better fit.
|
interpreting Clojure. Sci implements a suffiently large subset of
|
||||||
|
Clojure. Interpreting code is in general not as performant as executing compiled
|
||||||
|
code. If your script takes more than a few seconds to run, Clojure on the JVM
|
||||||
|
may be a better fit, since the performance of Clojure on the JVM outweighs its
|
||||||
|
startup time penalty. Read more about the differences with Clojure
|
||||||
|
[here](#differences-with-clojure).
|
||||||
|
|
||||||
Read more about the differences with Clojure [here](#differences-with-clojure).
|
|
||||||
|
|
||||||
## Status
|
### Talk
|
||||||
|
|
||||||
Experimental. Breaking changes are expected to happen at this phase. Keep an eye
|
To get an overview of babashka, you can watch this talk ([slides](https://speakerdeck.com/borkdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020)):
|
||||||
on [CHANGES.md](CHANGES.md) for a list of breaking changes.
|
|
||||||
|
|
||||||
## Examples
|
[](https://www.youtube.com/watch?v=Nw8aN-nrdEk)
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
$ ls | bb -i '*input*'
|
$ curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install-babashka
|
||||||
["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "resources" "script" "src" "target" "test"]
|
$ chmod +x install-babashka && ./install-babashka
|
||||||
|
$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *input*)'
|
||||||
|
("doc" "resources" "sci" "script" "src" "target" "test")
|
||||||
|
bb took 4ms.
|
||||||
|
```
|
||||||
|
|
||||||
$ ls | bb -i '(count *input*)'
|
### Examples
|
||||||
12
|
|
||||||
|
|
||||||
|
Read the output from a shell command as a lazy seq of strings:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ ls | bb -i '(take 2 *input*)'
|
||||||
|
("CHANGES.md" "Dockerfile")
|
||||||
|
```
|
||||||
|
|
||||||
|
Read EDN from stdin and write the result to stdout:
|
||||||
|
|
||||||
|
``` shell
|
||||||
$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
|
$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
|
||||||
[1 2]
|
[1 2]
|
||||||
|
```
|
||||||
|
|
||||||
$ bb '(filterv :foo *input*)' <<< '[{:foo 1} {:bar 2}]'
|
Read more about input and output flags
|
||||||
[{:foo 1}]
|
[here](https://github.com/borkdude/babashka/#input-and-output-flags).
|
||||||
|
|
||||||
$ bb '(#(+ %1 %2 %3) 1 2 *input*)' <<< 3
|
Execute a script. E.g. print the current time in California using the
|
||||||
6
|
`java.time` API:
|
||||||
|
|
||||||
$ ls | bb -i '(filterv #(re-find #"README" %) *input*)'
|
File `pst.clj`:
|
||||||
["README.md"]
|
``` clojure
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
|
(def now (java.time.ZonedDateTime/now))
|
||||||
$ ls /tmp/test | bb -i '*input*'
|
(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
|
||||||
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
|
(def LA-time (.withZoneSameInstant now LA-timezone))
|
||||||
|
(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
|
||||||
|
(println (.format LA-time pattern))
|
||||||
|
```
|
||||||
|
|
||||||
$ bb -O '(repeat "dude")' | bb --stream '(str *input* "rino")' | bb -I '(take 3 *input*)'
|
``` shell
|
||||||
("duderino" "duderino" "duderino")
|
$ pst.clj
|
||||||
|
05:17
|
||||||
```
|
```
|
||||||
|
|
||||||
More examples can be found in the [gallery](#gallery).
|
More examples can be found in the [gallery](#gallery).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Functionality regarding `clojure.core` and `java.lang` can be considered stable
|
||||||
|
and is unlikely to change. Changes may happen in other parts of babashka,
|
||||||
|
although we will try our best to prevent them. Always check the release notes or
|
||||||
|
[CHANGES.md](CHANGES.md) before upgrading.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Brew
|
### Brew
|
||||||
|
|
@ -130,7 +154,13 @@ $ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/inst
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
You may also download a binary from [Github](https://github.com/borkdude/babashka/releases).
|
You may also download a binary from
|
||||||
|
[Github](https://github.com/borkdude/babashka/releases). For linux there is a
|
||||||
|
static binary available which can be used on Alpine.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Check out the image on [Docker hub](https://hub.docker.com/r/borkdude/babashka/).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -138,7 +168,7 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
|
||||||
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
||||||
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
||||||
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
||||||
--repl | --socket-repl [<host>:]<port> ]
|
--repl | --socket-repl [<host>:]<port> | --nrepl-server [<host>:]<port> ]
|
||||||
[ arg* ]
|
[ arg* ]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
@ -160,6 +190,7 @@ Options:
|
||||||
-m, --main <ns> Call the -main function from namespace with args.
|
-m, --main <ns> Call the -main function from namespace with args.
|
||||||
--repl Start REPL. Use rlwrap for history.
|
--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).
|
--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).
|
||||||
--time Print execution time before exiting.
|
--time Print execution time before exiting.
|
||||||
-- Stop parsing args and pass everything after -- to *command-line-args*
|
-- Stop parsing args and pass everything after -- to *command-line-args*
|
||||||
|
|
||||||
|
|
@ -184,14 +215,16 @@ enumerated explicitly.
|
||||||
`make-parents`, `output-stream`, `reader`, `resource`, `writer`
|
`make-parents`, `output-stream`, `reader`, `resource`, `writer`
|
||||||
- `clojure.main`: `repl`
|
- `clojure.main`: `repl`
|
||||||
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
|
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
|
||||||
`async`. The `alt` and `go` macros are not available but `alts!!` does work as
|
`async`.
|
||||||
it is a function.
|
|
||||||
- `clojure.stacktrace`
|
- `clojure.stacktrace`
|
||||||
- `clojure.test`
|
- `clojure.test`
|
||||||
- `clojure.pprint`: `pprint` (currently backed by [fipp](https://github.com/brandonbloom/fipp)'s `fipp.edn/pprint`)
|
- `clojure.pprint`: `pprint` (currently backed by [fipp](https://github.com/brandonbloom/fipp)'s `fipp.edn/pprint`)
|
||||||
- [`clojure.tools.cli`](https://github.com/clojure/tools.cli) aliased as `tools.cli`
|
- [`clojure.tools.cli`](https://github.com/clojure/tools.cli) aliased as `tools.cli`
|
||||||
- [`clojure.data.csv`](https://github.com/clojure/data.csv) aliased as `csv`
|
- [`clojure.data.csv`](https://github.com/clojure/data.csv) aliased as `csv`
|
||||||
- [`cheshire.core`](https://github.com/dakrone/cheshire) aliased as `json`
|
- [`cheshire.core`](https://github.com/dakrone/cheshire) aliased as `json`
|
||||||
|
- [`cognitect.transit`](https://github.com/cognitect/transit-clj) aliased as `transit`
|
||||||
|
- [`clj-yaml.core`](https://github.com/clj-commons/clj-yaml) alias as `yaml`
|
||||||
|
- [`bencode.core`](https://github.com/nrepl/bencode) aliased as `bencode`: `read-bencode`, `write-bencode`
|
||||||
|
|
||||||
A selection of java classes are available, see `babashka/impl/classes.clj`.
|
A selection of java classes are available, see `babashka/impl/classes.clj`.
|
||||||
|
|
||||||
|
|
@ -311,6 +344,12 @@ $ bb '((fn [x] (println x) (when (not (signal/pipe-signal-received?)) (recur (in
|
||||||
|
|
||||||
The namespace `babashka.signal` is aliased as `signal` in the `user` namespace.
|
The namespace `babashka.signal` is aliased as `signal` in the `user` namespace.
|
||||||
|
|
||||||
|
#### babashka.curl
|
||||||
|
|
||||||
|
The namespace `babashka.curl` is a tiny wrapper around curl. It's aliased as
|
||||||
|
`curl` in the user namespace. See
|
||||||
|
[babashka.curl](https://github.com/borkdude/babashka.curl).
|
||||||
|
|
||||||
## Running a file
|
## Running a file
|
||||||
|
|
||||||
Scripts may be executed from a file using `-f` or `--file`:
|
Scripts may be executed from a file using `-f` or `--file`:
|
||||||
|
|
@ -375,6 +414,12 @@ $ cat script.clj
|
||||||
("hello" "1" "2" "3")
|
("hello" "1" "2" "3")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## [Running a REPL](doc/repl.md)
|
||||||
|
|
||||||
|
Babashka offers a REPL, a socket REPL and an nREPL server. Look
|
||||||
|
[here](doc/repl.md) for more information on how to use and integrate them with
|
||||||
|
your editor.
|
||||||
|
|
||||||
## Preloads
|
## Preloads
|
||||||
|
|
||||||
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
|
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
|
||||||
|
|
@ -416,6 +461,28 @@ $ bb --classpath src --main my.namespace
|
||||||
Hello from my namespace!
|
Hello from my namespace!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
So if you have a larger script with a classic Clojure project layout like
|
||||||
|
|
||||||
|
```shellsession
|
||||||
|
$ tree -L 3
|
||||||
|
├── deps.edn
|
||||||
|
├── README
|
||||||
|
├── src
|
||||||
|
│ └── project_namespace
|
||||||
|
│ ├── main.clj
|
||||||
|
│ └── utilities.clj
|
||||||
|
└── test
|
||||||
|
└── project_namespace
|
||||||
|
├── test_main.clj
|
||||||
|
└── test_utilities.clj
|
||||||
|
```
|
||||||
|
Then you can tell Babashka to include both the `src` and `test`
|
||||||
|
folders in the classpath and start a socket REPL by running:
|
||||||
|
|
||||||
|
```shellsession
|
||||||
|
$ bb --classpath src:test --socket-repl 1666
|
||||||
|
```
|
||||||
|
|
||||||
Note that you can use the `clojure` tool to produce classpaths and download dependencies:
|
Note that you can use the `clojure` tool to produce classpaths and download dependencies:
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
|
|
@ -441,6 +508,13 @@ $ bb "(my-gist-script/-main)"
|
||||||
Hello from gist script!
|
Hello from gist script!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When invoking `bb` with a main function, the expression `(System/getProperty
|
||||||
|
"babashka.main")` will return the name of the main function.
|
||||||
|
|
||||||
|
Also see the
|
||||||
|
[babashka.classpath](https://github.com/borkdude/babashka/#babashkaclasspath)
|
||||||
|
namespace which allows dynamically adding to the classpath.
|
||||||
|
|
||||||
### Deps.clj
|
### Deps.clj
|
||||||
|
|
||||||
The [`deps.clj`](https://github.com/borkdude/deps.clj/) script can be used to work with `deps.edn`-based projects:
|
The [`deps.clj`](https://github.com/borkdude/deps.clj/) script can be used to work with `deps.edn`-based projects:
|
||||||
|
|
@ -469,6 +543,46 @@ Hello from gist script!
|
||||||
nil
|
nil
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also use for example `deps.clj` to produce the classpath for a
|
||||||
|
`babashka` REPL:
|
||||||
|
|
||||||
|
```shellsession
|
||||||
|
$ cat script/start-repl.sh
|
||||||
|
#!/bin/sh -e
|
||||||
|
git_root=$(git rev-parse --show-toplevel)
|
||||||
|
export BABASHKA_CLASSPATH=$("$git_root"/script/deps.clj -Spath)
|
||||||
|
bb --socket-repl 1666
|
||||||
|
$ ./script/start-repl.sh
|
||||||
|
Babashka socket REPL started at localhost:1666
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, given that your `deps.edn` and source tree looks something like
|
||||||
|
|
||||||
|
```shellsession
|
||||||
|
$ cat deps.edn
|
||||||
|
{:paths ["src" "test"]
|
||||||
|
:deps {}}
|
||||||
|
$ tree -L 3
|
||||||
|
├── deps.edn
|
||||||
|
├── README
|
||||||
|
├── script
|
||||||
|
│ ├── deps.clj
|
||||||
|
│ └── start-repl.sh
|
||||||
|
├── src
|
||||||
|
│ └── project_namespace
|
||||||
|
│ ├── main.clj
|
||||||
|
│ └── utilities.clj
|
||||||
|
└── test
|
||||||
|
└── project_namespace
|
||||||
|
├── test_main.clj
|
||||||
|
└── test_utilities.clj
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
you should now be able to `(require '[multi-machine-rsync.utilities :as util])`
|
||||||
|
in your REPL and the source code in `/src/multi_machine_rsync/utilities.clj`
|
||||||
|
will be evaluated and made available through the symbol `util`.
|
||||||
|
|
||||||
## Uberscript
|
## Uberscript
|
||||||
|
|
||||||
The `--uberscript` option collects the expressions in
|
The `--uberscript` option collects the expressions in
|
||||||
|
|
@ -547,44 +661,6 @@ bb -cp "src:test:resources" \
|
||||||
(System/exit (+ fail error)))"
|
(System/exit (+ fail error)))"
|
||||||
```
|
```
|
||||||
|
|
||||||
## REPL
|
|
||||||
|
|
||||||
Babashka supports both a REPL and socket REPL. To start the REPL, type:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ bb --repl
|
|
||||||
```
|
|
||||||
|
|
||||||
To get history with up and down arrows, use `rlwrap`:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ rlwrap bb --repl
|
|
||||||
```
|
|
||||||
|
|
||||||
To start the socket REPL you can do this:
|
|
||||||
|
|
||||||
``` shellsession
|
|
||||||
$ bb --socket-repl 1666
|
|
||||||
Babashka socket REPL started at localhost:1666
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can connect with your favorite socket REPL client:
|
|
||||||
|
|
||||||
``` shellsession
|
|
||||||
$ rlwrap nc 127.0.0.1 1666
|
|
||||||
Babashka v0.0.14 REPL.
|
|
||||||
Use :repl/quit or :repl/exit to quit the REPL.
|
|
||||||
Clojure rocks, Bash reaches.
|
|
||||||
|
|
||||||
bb=> (+ 1 2 3)
|
|
||||||
6
|
|
||||||
bb=> :repl/quit
|
|
||||||
$
|
|
||||||
```
|
|
||||||
|
|
||||||
A socket REPL client for Emacs is
|
|
||||||
[inf-clojure](https://github.com/clojure-emacs/inf-clojure).
|
|
||||||
|
|
||||||
## Spawning and killing a process
|
## Spawning and killing a process
|
||||||
|
|
||||||
Use the `java.lang.ProcessBuilder` class.
|
Use the `java.lang.ProcessBuilder` class.
|
||||||
|
|
@ -604,10 +680,9 @@ Also see this [example](examples/process_builder.clj).
|
||||||
|
|
||||||
## Async
|
## Async
|
||||||
|
|
||||||
Apart from `future` and `pmap` for creating threads, you may use the `async`
|
In addition to `future`, `pmap`, `promise` and friends, you may use the
|
||||||
namespace, which maps to `clojure.core.async`, for asynchronous scripting. The
|
`clojure.core.async` namespace for asynchronous scripting. The following example
|
||||||
following example shows how to get first available value from two different
|
shows how to get first available value from two different processes:
|
||||||
processes:
|
|
||||||
|
|
||||||
``` clojure
|
``` clojure
|
||||||
bb '
|
bb '
|
||||||
|
|
@ -620,30 +695,93 @@ bb '
|
||||||
process 2
|
process 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: the `go` macro is available for compatibility with JVM programs, but the
|
||||||
|
implementation maps to `clojure.core.async/thread` and the single exclamation
|
||||||
|
mark operations (`<!`, `>!`, etc.) map to the double exclamation mark operations
|
||||||
|
(`<!!`, `>!!`, etc.). It will not "park" threads, like on the JVM.
|
||||||
|
|
||||||
|
## HTTP
|
||||||
|
|
||||||
|
For making HTTP requests you can use:
|
||||||
|
|
||||||
|
- [babashka.curl](https://github.com/borkdude/babashka.curl). This library is
|
||||||
|
included with babashka and aliased as `curl` in the user namespace.
|
||||||
|
- `slurp` for simple `GET` requests
|
||||||
|
- [clj-http-lite](https://github.com/borkdude/clj-http-lite) as a library.
|
||||||
|
- `clojure.java.shell` or `java.lang.ProcessBuilder` for shelling out to your
|
||||||
|
favorite command line http client
|
||||||
|
|
||||||
|
### HTTP over Unix sockets
|
||||||
|
|
||||||
|
This can be useful for talking to Docker:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
(require '[clojure.java.shell :refer [sh]])
|
||||||
|
(require '[cheshire.core :as json])
|
||||||
|
(-> (sh "curl" "--silent"
|
||||||
|
"--no-buffer" "--unix-socket"
|
||||||
|
"/var/run/docker.sock"
|
||||||
|
"http://localhost/images/json")
|
||||||
|
:out
|
||||||
|
(json/parse-string true)
|
||||||
|
first
|
||||||
|
:RepoTags) ;;=> ["borkdude/babashka:latest"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shutdown hook
|
||||||
|
|
||||||
|
Adding a shutdown hook allows you to execute some code before the script exits.
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
$ bb -e '(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))'
|
||||||
|
bye
|
||||||
|
```
|
||||||
|
|
||||||
|
This also works when the script is interrupted with ctrl-c.
|
||||||
|
|
||||||
|
## Bencode
|
||||||
|
|
||||||
|
Babashka comes with the [nrepl/bencode](https://github.com/nrepl/bencode)
|
||||||
|
library which allows you to read and write bencode messages to a socket. A
|
||||||
|
simple example which evaluates a Clojure expression on an nREPL server started
|
||||||
|
with `lein repl`:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
(ns nrepl-client
|
||||||
|
(:require [bencode.core :as b]))
|
||||||
|
|
||||||
|
(defn nrepl-eval [port expr]
|
||||||
|
(let [s (java.net.Socket. "localhost" port)
|
||||||
|
out (.getOutputStream s)
|
||||||
|
in (java.io.PushbackInputStream. (.getInputStream s))
|
||||||
|
_ (b/write-bencode out {"op" "eval" "code" expr})
|
||||||
|
bytes (get (b/read-bencode in) "value")]
|
||||||
|
(String. bytes)))
|
||||||
|
|
||||||
|
(nrepl-eval 52054 "(+ 1 2 3)") ;;=> "6"
|
||||||
|
```
|
||||||
|
|
||||||
## Differences with Clojure
|
## Differences with Clojure
|
||||||
|
|
||||||
Babashka is implemented using the [Small Clojure
|
Babashka is implemented using the [Small Clojure
|
||||||
Interpreter](https://github.com/borkdude/sci). This means that a snippet or
|
Interpreter](https://github.com/borkdude/sci). This means that a snippet or
|
||||||
script is not compiled to JVM bytecode, but executed form by form by a runtime
|
script is not compiled to JVM bytecode, but executed form by form by a runtime
|
||||||
which implements a subset of Clojure. Babashka is compiled to a native binary
|
which implements a sufficiently large subset of Clojure. Babashka is compiled to
|
||||||
using [GraalVM](https://github.com/oracle/graal). It comes with a selection of
|
a native binary using [GraalVM](https://github.com/oracle/graal). It comes with
|
||||||
built-in namespaces and functions from Clojure and other useful libraries. The
|
a selection of built-in namespaces and functions from Clojure and other useful
|
||||||
data types (numbers, strings, persistent collections) are the
|
libraries. The data types (numbers, strings, persistent collections) are the
|
||||||
same. Multi-threading is supported (`pmap`, `future`).
|
same. Multi-threading is supported (`pmap`, `future`).
|
||||||
|
|
||||||
Differences with Clojure:
|
Differences with Clojure:
|
||||||
|
|
||||||
- A subset of Java classes are supported.
|
- A pre-selected set of Java classes are supported. You cannot add Java classes
|
||||||
|
at runtime.
|
||||||
- Only the `clojure.core`, `clojure.edn`, `clojue.java.io`,
|
|
||||||
`clojure.java.shell`, `clojure.set`, `clojure.stacktrace`, `clojure.string`,
|
|
||||||
`clojure.template`, `clojure.test` and `clojure.walk` namespaces are available
|
|
||||||
from Clojure.
|
|
||||||
|
|
||||||
- Interpretation comes with overhead. Therefore tight loops are likely slower
|
- Interpretation comes with overhead. Therefore tight loops are likely slower
|
||||||
than in Clojure on the JVM.
|
than in Clojure on the JVM. In general interpretation yields slower programs
|
||||||
|
than compiled programs.
|
||||||
|
|
||||||
- No support for unboxed types.
|
- No `defprotocol`, `defrecord` and unboxed math.
|
||||||
|
|
||||||
## External resources
|
## External resources
|
||||||
|
|
||||||
|
|
@ -682,13 +820,13 @@ Ran 1 tests containing 0 assertions.
|
||||||
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
|
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
|
||||||
|
|
||||||
``` clojure
|
``` clojure
|
||||||
{:git/url "https://github.com/weavejester" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
|
{:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley {:git/url "https://github.com/weavejester" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}}}')
|
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley {:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}}}')
|
||||||
|
|
||||||
$ bb -e "(require '[medley.core :as m]) (m/index-by :id [{:id 1} {:id 2}])"
|
$ bb -e "(require '[medley.core :as m]) (m/index-by :id [{:id 1} {:id 2}])"
|
||||||
{1 {:id 1}, 2 {:id 2}}
|
{1 {:id 1}, 2 {:id 2}}
|
||||||
|
|
@ -749,26 +887,81 @@ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clojure-csv {:mvn/version "
|
||||||
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
|
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
|
||||||
|
|
||||||
``` clojure
|
``` clojure
|
||||||
{:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}
|
{:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}}}')
|
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}}}')
|
||||||
|
|
||||||
$ bb -e "(require '[lambdaisland.regal :as regal]) (regal/regex [:* \"ab\"])"
|
$ bb -e "(require '[lambdaisland.regal :as regal]) (regal/regex [:* \"ab\"])"
|
||||||
#"(?:\Qab\E)*"
|
#"(?:\Qab\E)*"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### [spartan.test](https://github.com/borkdude/spartan.test/)
|
#### [4bb](https://github.com/porkostomus/4bb)
|
||||||
|
|
||||||
A minimal test framework compatible with babashka. This library is deprecated
|
4clojure as a babashka script!
|
||||||
since babashka v0.0.68 which has `clojure.test` built-in.
|
|
||||||
|
|
||||||
|
#### [cprop](https://github.com/tolitius/cprop/)
|
||||||
|
|
||||||
### Blogs
|
A clojure configuration libary. Latest test version: `"0.1.16"`.
|
||||||
|
|
||||||
|
#### [comb](https://github.com/weavejester/comb)
|
||||||
|
|
||||||
|
Simple templating system for Clojure. Latest tested version: `"0.1.1"`.
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {comb {:mvn/version "0.1.1"}}}')
|
||||||
|
$ rlwrap bb
|
||||||
|
...
|
||||||
|
user=> (require '[comb.template :as template])
|
||||||
|
user=> (template/eval "<% (dotimes [x 3] %>foo<% ) %>")
|
||||||
|
"foofoofoo"
|
||||||
|
user=> (template/eval "Hello <%= name %>" {:name "Alice"})
|
||||||
|
"Hello Alice"
|
||||||
|
user=> (def hello (template/fn [name] "Hello <%= name %>"))
|
||||||
|
user=> (hello "Alice")
|
||||||
|
"Hello Alice"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [nubank/docopt](https://github.com/nubank/docopt.clj#babashka)
|
||||||
|
|
||||||
|
Docopt implementation in Clojure, compatible with babashka.
|
||||||
|
|
||||||
|
#### [babashka lambda layer](https://github.com/dainiusjocas/babashka-lambda-layer)
|
||||||
|
|
||||||
|
Babashka Lambda runtime packaged as a Lambda layer.
|
||||||
|
|
||||||
|
#### [Release on push Github action](https://github.com/rymndhng/release-on-push-action)
|
||||||
|
|
||||||
|
Github Action to create a git tag + release when pushed to master. Written in
|
||||||
|
babashka.
|
||||||
|
|
||||||
|
#### [justone/bb-scripts](https://github.com/justone/bb-scripts)
|
||||||
|
|
||||||
|
A collection of scripts developed by [@justone](https://github.com/justone).
|
||||||
|
|
||||||
|
#### [nativity](https://github.com/MnRA/nativity)
|
||||||
|
|
||||||
|
Turn babashka scripts into binaries using GraalVM `native-image`.
|
||||||
|
|
||||||
|
#### [arrangement](https://github.com/greglook/clj-arrangement)
|
||||||
|
|
||||||
|
A micro-library which provides a total-ordering comparator for Clojure
|
||||||
|
values. Tested with version `1.2.0`.
|
||||||
|
|
||||||
|
## Package babashka script as a AWS Lambda
|
||||||
|
|
||||||
|
AWS Lambda runtime doesn't support signals, therefore babashka has to disable
|
||||||
|
handling of the SIGPIPE. This can be done by setting
|
||||||
|
`BABASHKA_DISABLE_PIPE_SIGNAL_HANDLER` to `true`.
|
||||||
|
|
||||||
|
## Articles, podcasts and videos
|
||||||
|
|
||||||
|
- [Implementing an nREPL server for babashka](https://youtu.be/0YmZYnwyHHc): impromptu presentation by Michiel Borkent at the online [Dutch Clojure Meetup](http://meetup.com/The-Dutch-Clojure-Meetup)
|
||||||
|
- [ClojureScript podcast](https://soundcloud.com/user-959992602/s3-e5-babashka-with-michiel-borkent) with Jacek Schae interviewing Michiel Borkent
|
||||||
|
- [Babashka talk at ClojureD](https://www.youtube.com/watch?v=Nw8aN-nrdEk) ([slides](https://speakerdeck.com/borkdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020)) by Michiel Borkent
|
||||||
- [Babashka: a quick example](https://juxt.pro/blog/posts/babashka.html) by Malcolm Sparks
|
- [Babashka: a quick example](https://juxt.pro/blog/posts/babashka.html) by Malcolm Sparks
|
||||||
- [Clojure Start Time in 2019](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019) by Stuart Sierra
|
- [Clojure Start Time in 2019](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019) by Stuart Sierra
|
||||||
- [Advent of Random
|
- [Advent of Random
|
||||||
|
|
@ -837,13 +1030,18 @@ $ < /tmp/test.txt bb -io '(shuffle *input*)'
|
||||||
|
|
||||||
### Fetch latest Github release tag
|
### Fetch latest Github release tag
|
||||||
|
|
||||||
For converting JSON to EDN, see [jet](https://github.com/borkdude/jet).
|
``` shell
|
||||||
|
(require '[clojure.java.shell :refer [sh]]
|
||||||
|
'[cheshire.core :as json])
|
||||||
|
|
||||||
``` shellsession
|
(defn babashka-latest-version []
|
||||||
$ curl -s https://api.github.com/repos/borkdude/babashka/tags |
|
(-> (sh "curl" "https://api.github.com/repos/borkdude/babashka/tags")
|
||||||
jet --from json --keywordize --to edn |
|
:out
|
||||||
bb '(-> *input* first :name (subs 1))'
|
(json/parse-string true)
|
||||||
"0.0.4"
|
first
|
||||||
|
:name))
|
||||||
|
|
||||||
|
(babashka-latest-version) ;;=> "v0.0.73"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate deps.edn entry for a gitlib
|
### Generate deps.edn entry for a gitlib
|
||||||
|
|
@ -938,6 +1136,8 @@ bb '(let [{:keys [dependencies source-paths resource-paths]} (apply hash-map (dr
|
||||||
jet --pretty > deps.edn
|
jet --pretty > deps.edn
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A script with the same goal can be found [here](https://gist.github.com/swlkr/3f346c66410e5c60c59530c4413a248e#gistcomment-3232605).
|
||||||
|
|
||||||
### Print current time in California
|
### Print current time in California
|
||||||
|
|
||||||
See [examples/pst.clj](https://github.com/borkdude/babashka/blob/master/examples/pst.clj)
|
See [examples/pst.clj](https://github.com/borkdude/babashka/blob/master/examples/pst.clj)
|
||||||
|
|
@ -960,13 +1160,132 @@ clojure.core/ffirst
|
||||||
Same as (first (first x))
|
Same as (first (first x))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Cryptographic hash
|
||||||
|
|
||||||
|
`sha1.clj`:
|
||||||
|
``` clojure
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
|
(defn sha1
|
||||||
|
[s]
|
||||||
|
(let [hashed (.digest (java.security.MessageDigest/getInstance "SHA-1")
|
||||||
|
(.getBytes s))
|
||||||
|
sw (java.io.StringWriter.)]
|
||||||
|
(binding [*out* sw]
|
||||||
|
(doseq [byte hashed]
|
||||||
|
(print (format "%02X" byte))))
|
||||||
|
(str sw)))
|
||||||
|
|
||||||
|
(sha1 (first *command-line-args*))
|
||||||
|
```
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ sha1.clj babashka
|
||||||
|
"0AB318BE3A646EEB1E592781CBFE4AE59701EDDF"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package script as Docker image
|
||||||
|
|
||||||
|
`Dockerfile`:
|
||||||
|
``` dockerfile
|
||||||
|
FROM borkdude/babashka
|
||||||
|
RUN echo $'\
|
||||||
|
(println "Your command line args:" *command-line-args*)\
|
||||||
|
'\
|
||||||
|
>> script.clj
|
||||||
|
|
||||||
|
ENTRYPOINT ["bb", "script.clj"]
|
||||||
|
```
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ docker build . -t script
|
||||||
|
...
|
||||||
|
$ docker run --rm script 1 2 3
|
||||||
|
Your command line args: (1 2 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extract single file from zip
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
;; Given the following:
|
||||||
|
|
||||||
|
;; $ echo 'contents' > file
|
||||||
|
;; $ zip zipfile.zip file
|
||||||
|
;; $ rm file
|
||||||
|
|
||||||
|
;; we extract the single file from the zip archive using java.nio:
|
||||||
|
|
||||||
|
(import '[java.nio.file Files FileSystems CopyOption])
|
||||||
|
(let [zip-file (io/file "zipfile.zip")
|
||||||
|
file (io/file "file")
|
||||||
|
fs (FileSystems/newFileSystem (.toPath zip-file) nil)
|
||||||
|
file-in-zip (.getPath fs "file" (into-array String []))]
|
||||||
|
(Files/copy file-in-zip (.toPath file)
|
||||||
|
(into-array CopyOption [])))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Note taking app
|
||||||
|
|
||||||
|
See
|
||||||
|
[examples/notes.clj](https://github.com/borkdude/babashka/blob/master/examples/notes.clj). This
|
||||||
|
is a variation on the
|
||||||
|
[http-server](https://github.com/borkdude/babashka/#tiny-http-server)
|
||||||
|
example. If you get prompted with a login, use `admin`/`admin`.
|
||||||
|
|
||||||
|
<img src="assets/notes-example.png" width="400px">
|
||||||
|
|
||||||
|
### which
|
||||||
|
|
||||||
|
The `which` command re-implemented in Clojure. See
|
||||||
|
[examples/which.clj](https://github.com/borkdude/babashka/blob/master/examples/which.clj).
|
||||||
|
Prints the canonical file name.
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ examples/which.clj rg
|
||||||
|
/usr/local/Cellar/ripgrep/11.0.1/bin/rg
|
||||||
|
```
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
- [adgoji](https://www.adgoji.com/) for financial support
|
- [adgoji](https://www.adgoji.com/) for financial support
|
||||||
|
- [CircleCI](https://circleci.com/) for CI and additional support
|
||||||
|
- [Nikita Prokopov](https://github.com/tonsky) for the logo
|
||||||
|
- [contributors](https://github.com/borkdude/babashka/graphs/contributors) and
|
||||||
|
other users posting issues with bug reports and ideas
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
### Code Contributors
|
||||||
|
|
||||||
|
This project exists thanks to all the people who contribute. [[Contribute](doc/dev.md)].
|
||||||
|
<a href="https://github.com/borkdude/babashka/graphs/contributors"><img src="https://opencollective.com/babashka/contributors.svg?width=890&button=false" /></a>
|
||||||
|
|
||||||
|
### Financial Contributors
|
||||||
|
|
||||||
|
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/babashka/contribute)]
|
||||||
|
|
||||||
|
#### Individuals
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/babashka"><img src="https://opencollective.com/babashka/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
|
#### Organizations
|
||||||
|
|
||||||
|
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/babashka/contribute)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/babashka/organization/0/website"><img src="https://opencollective.com/babashka/organization/0/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/1/website"><img src="https://opencollective.com/babashka/organization/1/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/2/website"><img src="https://opencollective.com/babashka/organization/2/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/3/website"><img src="https://opencollective.com/babashka/organization/3/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/4/website"><img src="https://opencollective.com/babashka/organization/4/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/5/website"><img src="https://opencollective.com/babashka/organization/5/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/6/website"><img src="https://opencollective.com/babashka/organization/6/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/7/website"><img src="https://opencollective.com/babashka/organization/7/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/8/website"><img src="https://opencollective.com/babashka/organization/8/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/babashka/organization/9/website"><img src="https://opencollective.com/babashka/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright © 2019 Michiel Borkent
|
Copyright © 2019-2020 Michiel Borkent
|
||||||
|
|
||||||
Distributed under the EPL License. See LICENSE.
|
Distributed under the EPL License. See LICENSE.
|
||||||
|
|
||||||
|
|
|
||||||
BIN
assets/notes-example.png
Normal file
BIN
assets/notes-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
1
babashka.curl
Submodule
1
babashka.curl
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a9e9fe83d56b020071c1a3bbeb4656e53c8a988d
|
||||||
10
deps.edn
10
deps.edn
|
|
@ -1,7 +1,7 @@
|
||||||
{:paths ["src" "sci/src" "resources" "sci/resources"],
|
{:paths ["src" "sci/src" "babashka.curl/src" "resources" "sci/resources"],
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.10.1"},
|
:deps {org.clojure/clojure {:mvn/version "1.10.2-alpha1"},
|
||||||
org.clojure/tools.reader {:mvn/version "1.3.2"},
|
org.clojure/tools.reader {:mvn/version "1.3.2"},
|
||||||
borkdude/edamame {:mvn/version "0.0.10"},
|
borkdude/edamame {:mvn/version "0.0.11-alpha.9"},
|
||||||
borkdude/graal.locking {:mvn/version "0.0.2"},
|
borkdude/graal.locking {:mvn/version "0.0.2"},
|
||||||
borkdude/sci.impl.reflector {:mvn/version "0.0.1"}
|
borkdude/sci.impl.reflector {:mvn/version "0.0.1"}
|
||||||
org.clojure/core.async {:mvn/version "1.0.567"},
|
org.clojure/core.async {:mvn/version "1.0.567"},
|
||||||
|
|
@ -9,7 +9,9 @@
|
||||||
org.clojure/data.csv {:mvn/version "1.0.0"},
|
org.clojure/data.csv {:mvn/version "1.0.0"},
|
||||||
cheshire {:mvn/version "5.10.0"}
|
cheshire {:mvn/version "5.10.0"}
|
||||||
org.clojure/data.xml {:mvn/version "0.2.0-alpha6"}
|
org.clojure/data.xml {:mvn/version "0.2.0-alpha6"}
|
||||||
fipp {:mvn/version "0.6.22"}}
|
fipp {:mvn/version "0.6.22"}
|
||||||
|
clj-commons/clj-yaml {:mvn/version "0.7.1"}
|
||||||
|
com.cognitect/transit-clj {:mvn/version "1.0.324"}}
|
||||||
:aliases {:main
|
:aliases {:main
|
||||||
{:main-opts ["-m" "babashka.main"]}
|
{:main-opts ["-m" "babashka.main"]}
|
||||||
:profile
|
:profile
|
||||||
|
|
|
||||||
38
doc/dev.md
38
doc/dev.md
|
|
@ -1,5 +1,9 @@
|
||||||
# Developing Babashka
|
# Developing Babashka
|
||||||
|
|
||||||
|
You need [lein](https://leiningen.org/) for running JVM tests and/or producing uberjars. For building binaries you need GraalVM. Currently we use java8-19.3.1.
|
||||||
|
|
||||||
|
## Clone repository
|
||||||
|
|
||||||
To work on Babashka itself make sure Git submodules are checked out.
|
To work on Babashka itself make sure Git submodules are checked out.
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
|
|
@ -12,8 +16,6 @@ To update later on:
|
||||||
$ git submodule update --recursive
|
$ git submodule update --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
You need [Leiningen](https://leiningen.org/), and for building binaries you need GraalVM.
|
|
||||||
|
|
||||||
## REPL
|
## REPL
|
||||||
|
|
||||||
`lein repl` will get you a standard REPL/nREPL connection. To work on tests use `lein with-profiles +test repl`.
|
`lein repl` will get you a standard REPL/nREPL connection. To work on tests use `lein with-profiles +test repl`.
|
||||||
|
|
@ -47,16 +49,40 @@ To build this project, set `$GRAALVM_HOME` to the GraalVM distribution directory
|
||||||
|
|
||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
script/compile
|
$ script/compile
|
||||||
|
|
||||||
|
To tweak maximum heap size:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ BABASHKA_XMX="-J-Xmx4g" script/compile
|
||||||
|
```
|
||||||
|
|
||||||
## Binary size
|
## Binary size
|
||||||
|
|
||||||
Keep notes here about how adding libraries and classes to Babashka affects the binary size.
|
Keep notes here about how adding libraries and classes to Babashka affects the binary size.
|
||||||
|
We're registering the size of the macOS binary (as built on CircleCI).
|
||||||
|
|
||||||
We're only registering the size of the macOS binary (as built on CircleCI).
|
2020/03/29 Added clj-yaml for parsing and generating yaml.
|
||||||
|
45196996 - 42626884 = 2570kb added.
|
||||||
|
|
||||||
2020/01/08, ..., 38.7mb / 11.3mb zipped
|
2020/03/28 Added java.nio.file.FileSystem(s) to support extracting zip files
|
||||||
Added: `clojure.data.xml`. Growth: 1.8mb / 0.4mb zipped.
|
42562284 - 42021244 = 541kb added.
|
||||||
|
|
||||||
|
2020/03/22 Added java.io.FileReader
|
||||||
|
42025276 - 42008876 = 16kb added.
|
||||||
|
|
||||||
|
2020/03/20 Added transit write, writer, read, reader
|
||||||
|
42004796 - 41025212 = 980kb added (305kb zipped).
|
||||||
|
|
||||||
|
2020/03/19 Added java.lang.NumberFormatException, java.lang.RuntimeException,
|
||||||
|
java.util.MissingResourceException and java.util.Properties to support
|
||||||
|
[cprop](https://github.com/tolitius/cprop/).
|
||||||
|
41025180 - 40729908 = 295kb added.
|
||||||
|
|
||||||
|
2020/02/21
|
||||||
|
Added java.time.temporal.ChronoUnit
|
||||||
|
40651596 - 40598260 = 53kb added.
|
||||||
|
>>>>>>> master
|
||||||
|
|
||||||
2020/02/19, e43727955a2cdabd2bb0189c20dd7f9a18156fc9
|
2020/02/19, e43727955a2cdabd2bb0189c20dd7f9a18156fc9
|
||||||
Added fipp.edn/pprint
|
Added fipp.edn/pprint
|
||||||
|
|
|
||||||
128
doc/repl.md
Normal file
128
doc/repl.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Running a REPL
|
||||||
|
|
||||||
|
Babashka supports running a REPL, a socket REPL and an nREPL server.
|
||||||
|
|
||||||
|
## REPL
|
||||||
|
|
||||||
|
To start the REPL, type:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ bb --repl
|
||||||
|
```
|
||||||
|
|
||||||
|
To get history with up and down arrows, use `rlwrap`:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ rlwrap bb --repl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Socket REPL
|
||||||
|
|
||||||
|
To start the socket REPL you can do this:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ bb --socket-repl 1666
|
||||||
|
Babashka socket REPL started at localhost:1666
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can connect with your favorite socket REPL client:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ rlwrap nc 127.0.0.1 1666
|
||||||
|
Babashka v0.0.14 REPL.
|
||||||
|
Use :repl/quit or :repl/exit to quit the REPL.
|
||||||
|
Clojure rocks, Bash reaches.
|
||||||
|
|
||||||
|
bb=> (+ 1 2 3)
|
||||||
|
6
|
||||||
|
bb=> :repl/quit
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor plugins and tools known to work with a babashka socket REPL:
|
||||||
|
|
||||||
|
- Emacs: [inf-clojure](https://github.com/clojure-emacs/inf-clojure):
|
||||||
|
|
||||||
|
To connect:
|
||||||
|
|
||||||
|
`M-x inf-clojure-connect <RET> localhost <RET> 1666`
|
||||||
|
|
||||||
|
Before evaluating from a Clojure buffer:
|
||||||
|
|
||||||
|
`M-x inf-clojure-minor-mode`
|
||||||
|
|
||||||
|
- Atom: [Chlorine](https://github.com/mauricioszabo/atom-chlorine)
|
||||||
|
- Vim: [vim-iced](https://github.com/liquidz/vim-iced)
|
||||||
|
- IntelliJ IDEA: [Cursive](https://cursive-ide.com/)
|
||||||
|
|
||||||
|
Note: you will have to use a workaround via
|
||||||
|
[tubular](https://github.com/mfikes/tubular). For more info, look
|
||||||
|
[here](https://cursive-ide.com/userguide/repl.html#repl-types).
|
||||||
|
|
||||||
|
|
||||||
|
## nREPL
|
||||||
|
|
||||||
|
To start an nREPL server:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ bb --nrepl-server 1667
|
||||||
|
```
|
||||||
|
|
||||||
|
Then connect with your favorite nREPL client:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
$ lein repl :connect 1667
|
||||||
|
Connecting to nREPL at 127.0.0.1:1667
|
||||||
|
user=> (+ 1 2 3)
|
||||||
|
6
|
||||||
|
user=>
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor plugins and tools known to work with the babashka nREPL server:
|
||||||
|
|
||||||
|
- Emacs: [CIDER](https://docs.cider.mx/cider-nrepl/)
|
||||||
|
- `lein repl :connect`
|
||||||
|
- VSCode: [Calva](http://calva.io/)
|
||||||
|
- Atom: [Chlorine](https://github.com/mauricioszabo/atom-chlorine)
|
||||||
|
- (Neo)Vim: [vim-iced](https://github.com/liquidz/vim-iced), [conjure](https://github.com/Olical/conjure), [fireplace](https://github.com/tpope/vim-fireplace)
|
||||||
|
|
||||||
|
The babashka nREPL server does not write an `.nrepl-port` file at startup, but
|
||||||
|
you can easily write a script that launches the server and writes the file:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
|
(import [java.net ServerSocket]
|
||||||
|
[java.io File]
|
||||||
|
[java.lang ProcessBuilder$Redirect])
|
||||||
|
|
||||||
|
(require '[babashka.wait :as wait])
|
||||||
|
|
||||||
|
(let [nrepl-port (with-open [sock (ServerSocket. 0)] (.getLocalPort sock))
|
||||||
|
pb (doto (ProcessBuilder. (into ["bb" "--nrepl-server" (str nrepl-port)]
|
||||||
|
*command-line-args*))
|
||||||
|
(.redirectOutput ProcessBuilder$Redirect/INHERIT))
|
||||||
|
proc (.start pb)]
|
||||||
|
(wait/wait-for-port "localhost" nrepl-port)
|
||||||
|
(spit ".nrepl-port" nrepl-port)
|
||||||
|
(.deleteOnExit (File. ".nrepl-port"))
|
||||||
|
(.waitFor proc))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging the nREPL server
|
||||||
|
|
||||||
|
To debug the nREPL server from the binary you can run:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ BABASHKA_DEV=true bb --nrepl-server 1667
|
||||||
|
```
|
||||||
|
|
||||||
|
This will print all the incoming messages.
|
||||||
|
|
||||||
|
To debug the nREPL server from source:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
$ git clone https://github.com/borkdude/babashka --recursive
|
||||||
|
$ cd babashka
|
||||||
|
$ BABASHKA_DEV=true clojure -A:main --nrepl-server 1667
|
||||||
|
```
|
||||||
147
examples/notes.clj
Executable file
147
examples/notes.clj
Executable file
|
|
@ -0,0 +1,147 @@
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
|
(import (java.net ServerSocket))
|
||||||
|
(require '[clojure.java.io :as io]
|
||||||
|
'[clojure.string :as str])
|
||||||
|
|
||||||
|
(def debug? true)
|
||||||
|
(def user "admin")
|
||||||
|
(def password "admin")
|
||||||
|
(def base64 (-> (.getEncoder java.util.Base64)
|
||||||
|
(.encodeToString (.getBytes (str user ":" password)))))
|
||||||
|
|
||||||
|
(def notes-file (io/file (System/getProperty "user.home") ".notes" "notes.txt"))
|
||||||
|
(def file-lock (Object.))
|
||||||
|
|
||||||
|
(defn write-note! [note]
|
||||||
|
(locking file-lock
|
||||||
|
(io/make-parents notes-file)
|
||||||
|
(spit notes-file (str note "\n") :append true)))
|
||||||
|
|
||||||
|
;; hiccup-like
|
||||||
|
(defn html [v]
|
||||||
|
(cond (vector? v)
|
||||||
|
(let [tag (first v)
|
||||||
|
attrs (second v)
|
||||||
|
attrs (when (map? attrs) attrs)
|
||||||
|
elts (if attrs (nnext v) (next v))
|
||||||
|
tag-name (name tag)]
|
||||||
|
(format "<%s%s>%s</%s>\n" tag-name (html attrs) (html elts) tag-name))
|
||||||
|
(map? v)
|
||||||
|
(str/join ""
|
||||||
|
(map (fn [[k v]]
|
||||||
|
(format " %s=\"%s\"" (name k) v)) v))
|
||||||
|
(seq? v)
|
||||||
|
(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
|
||||||
|
"<!DOCTYPE html>\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))
|
||||||
|
|
||||||
|
(def known-sessions
|
||||||
|
(atom #{}))
|
||||||
|
|
||||||
|
(defn new-session! []
|
||||||
|
(let [uuid (str (java.util.UUID/randomUUID))]
|
||||||
|
(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)
|
||||||
|
(swap! authenticated-sessions conj session-id)
|
||||||
|
true)))
|
||||||
|
|
||||||
|
;; 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)))
|
||||||
17
examples/which.clj
Executable file
17
examples/which.clj
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
|
(require '[clojure.java.io :as io])
|
||||||
|
|
||||||
|
(defn which [executable]
|
||||||
|
(let [path (System/getenv "PATH")
|
||||||
|
paths (.split path (System/getProperty "path.separator"))]
|
||||||
|
(loop [paths paths]
|
||||||
|
(when-first [p paths]
|
||||||
|
(let [f (io/file p executable)]
|
||||||
|
(if (and (.isFile f)
|
||||||
|
(.canExecute f))
|
||||||
|
(.getCanonicalPath f)
|
||||||
|
(recur (rest paths))))))))
|
||||||
|
|
||||||
|
(when-let [executable (first *command-line-args*)]
|
||||||
|
(println (which executable)))
|
||||||
12
project.clj
12
project.clj
|
|
@ -7,11 +7,13 @@
|
||||||
:url "https://github.com/borkdude/babashka"}
|
:url "https://github.com/borkdude/babashka"}
|
||||||
:license {:name "Eclipse Public License 1.0"
|
:license {:name "Eclipse Public License 1.0"
|
||||||
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
||||||
:source-paths ["src" "sci/src"]
|
:source-paths ["src" "sci/src" "babashka.curl/src"]
|
||||||
|
;; for debugging Reflector.java code:
|
||||||
|
;; :java-source-paths ["sci/reflector/src-java"]
|
||||||
:resource-paths ["resources" "sci/resources"]
|
:resource-paths ["resources" "sci/resources"]
|
||||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
|
||||||
[org.clojure/tools.reader "1.3.2"]
|
[org.clojure/tools.reader "1.3.2"]
|
||||||
[borkdude/edamame "0.0.10"]
|
[borkdude/edamame "0.0.11-alpha.9"]
|
||||||
[borkdude/graal.locking "0.0.2"]
|
[borkdude/graal.locking "0.0.2"]
|
||||||
[borkdude/sci.impl.reflector "0.0.1"]
|
[borkdude/sci.impl.reflector "0.0.1"]
|
||||||
[org.clojure/core.async "1.0.567"]
|
[org.clojure/core.async "1.0.567"]
|
||||||
|
|
@ -19,7 +21,9 @@
|
||||||
[org.clojure/data.csv "1.0.0"]
|
[org.clojure/data.csv "1.0.0"]
|
||||||
[org.clojure/data.xml "0.2.0-alpha6"]
|
[org.clojure/data.xml "0.2.0-alpha6"]
|
||||||
[cheshire "5.10.0"]
|
[cheshire "5.10.0"]
|
||||||
[fipp "0.6.22"]]
|
[fipp "0.6.22"]
|
||||||
|
[clj-commons/clj-yaml "0.7.1"]
|
||||||
|
[com.cognitect/transit-clj "1.0.324"]]
|
||||||
:profiles {:test {:dependencies [[clj-commons/conch "0.9.2"]
|
:profiles {:test {:dependencies [[clj-commons/conch "0.9.2"]
|
||||||
[com.clojure-goes-fast/clj-async-profiler "0.4.0"]]}
|
[com.clojure-goes-fast/clj-async-profiler "0.4.0"]]}
|
||||||
:uberjar {:global-vars {*assert* false}
|
:uberjar {:global-vars {*assert* false}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.0.71
|
0.0.86
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.0.72-SNAPSHOT
|
0.0.87-SNAPSHOT
|
||||||
19
resources/CutOffCoreServicesDependencies.java
Normal file
19
resources/CutOffCoreServicesDependencies.java
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import org.graalvm.nativeimage.Platform;
|
||||||
|
import org.graalvm.nativeimage.Platforms;
|
||||||
|
import com.oracle.svm.core.annotate.TargetClass;
|
||||||
|
import com.oracle.svm.core.annotate.Delete;
|
||||||
|
|
||||||
|
public final class CutOffCoreServicesDependencies {
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Platforms(Platform.DARWIN.class)
|
||||||
|
// @TargetClass(className = "sun.net.spi.DefaultProxySelector")
|
||||||
|
// @Delete
|
||||||
|
// final class Target_sun_net_spi_DefaultProxySelector {
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Platforms(Platform.DARWIN.class)
|
||||||
|
@TargetClass(className = "apple.security.KeychainStore")
|
||||||
|
@Delete
|
||||||
|
final class Target_apple_security_KeychainStore {
|
||||||
|
}
|
||||||
2
sci
2
sci
|
|
@ -1 +1 @@
|
||||||
Subproject commit eebb456628beb2ac0d1e31c2be46ee0683b9ee7a
|
Subproject commit ead5dd7c25e0e38cb6244077ec9e57e00665cde9
|
||||||
|
|
@ -2,48 +2,58 @@
|
||||||
|
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
|
if [ -z "$BABASHKA_XMX" ]; then
|
||||||
|
export BABASHKA_XMX="-J-Xmx3g"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$GRAALVM_HOME" ]; then
|
if [ -z "$GRAALVM_HOME" ]; then
|
||||||
echo "Please set GRAALVM_HOME"
|
echo "Please set GRAALVM_HOME"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$BABASHKA_XMX" ]; then
|
$GRAALVM_HOME/bin/gu install native-image
|
||||||
export BABASHKA_XMX="-J-Xmx3g"
|
|
||||||
fi
|
|
||||||
|
|
||||||
"$GRAALVM_HOME/bin/gu" install native-image || true
|
|
||||||
|
|
||||||
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
|
|
||||||
# # We also need to AOT sci, else something didn't work in the Mac build on CircleCI
|
|
||||||
# # See https://github.com/oracle/graal/issues/1613
|
|
||||||
# ( cd /tmp; git clone https://github.com/borkdude/sci 2> /dev/null || true )
|
|
||||||
# mkdir -p src/sci
|
|
||||||
# cp -R /tmp/sci/src/* src
|
|
||||||
|
|
||||||
export JAVA_HOME=$GRAALVM_HOME
|
export JAVA_HOME=$GRAALVM_HOME
|
||||||
|
|
||||||
lein with-profiles +reflection do run
|
SVM_JAR=$(find "$GRAALVM_HOME" | grep svm.jar)
|
||||||
lein do clean, uberjar
|
$GRAALVM_HOME/bin/javac -cp "$SVM_JAR" resources/CutOffCoreServicesDependencies.java
|
||||||
|
|
||||||
$GRAALVM_HOME/bin/native-image \
|
if [ -z "$BABASHKA_JAR" ]; then
|
||||||
-jar target/babashka-$BABASHKA_VERSION-standalone.jar \
|
lein with-profiles +reflection do run
|
||||||
-H:Name=bb \
|
lein do clean, uberjar
|
||||||
-H:+ReportExceptionStackTraces \
|
BABASHKA_JAR=${BABASHKA_JAR:-"target/babashka-$BABASHKA_VERSION-standalone.jar"}
|
||||||
-J-Dclojure.spec.skip-macros=true \
|
fi
|
||||||
-J-Dclojure.compiler.direct-linking=true \
|
|
||||||
"-H:IncludeResources=BABASHKA_VERSION" \
|
|
||||||
"-H:IncludeResources=SCI_VERSION" \
|
|
||||||
-H:ReflectionConfigurationFiles=reflection.json \
|
|
||||||
--initialize-at-run-time=java.lang.Math\$RandomNumberGeneratorHolder \
|
|
||||||
--initialize-at-build-time \
|
|
||||||
-H:Log=registerResource: \
|
|
||||||
-H:EnableURLProtocols=http,https \
|
|
||||||
--enable-all-security-services \
|
|
||||||
-H:+JNI \
|
|
||||||
--verbose \
|
|
||||||
--no-fallback \
|
|
||||||
--no-server \
|
|
||||||
"$BABASHKA_XMX"
|
|
||||||
|
|
||||||
lein clean
|
BABASHKA_BINARY=${BABASHKA_BINARY:-"bb"}
|
||||||
|
|
||||||
|
args=( -jar $BABASHKA_JAR \
|
||||||
|
-H:Name=$BABASHKA_BINARY \
|
||||||
|
-H:+ReportExceptionStackTraces \
|
||||||
|
-J-Dclojure.spec.skip-macros=true \
|
||||||
|
-J-Dclojure.compiler.direct-linking=true \
|
||||||
|
"-H:IncludeResources=BABASHKA_VERSION" \
|
||||||
|
"-H:IncludeResources=SCI_VERSION" \
|
||||||
|
-H:ReflectionConfigurationFiles=reflection.json \
|
||||||
|
--initialize-at-run-time=java.lang.Math\$RandomNumberGeneratorHolder \
|
||||||
|
--initialize-at-build-time \
|
||||||
|
-H:Log=registerResource: \
|
||||||
|
-H:EnableURLProtocols=http,https \
|
||||||
|
--enable-all-security-services \
|
||||||
|
-H:+JNI \
|
||||||
|
--verbose \
|
||||||
|
--no-fallback \
|
||||||
|
--no-server \
|
||||||
|
--report-unsupported-elements-at-runtime \
|
||||||
|
"$BABASHKA_XMX" )
|
||||||
|
|
||||||
|
if [ "$BABASHKA_STATIC" = "true" ]; then
|
||||||
|
args+=("--static")
|
||||||
|
fi
|
||||||
|
|
||||||
|
$GRAALVM_HOME/bin/native-image "${args[@]}"
|
||||||
|
|
||||||
|
if [ ! -z "$(command -v lein)" ]; then
|
||||||
|
lein clean
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
install_dir=${1:-/tmp/clojure}
|
install_dir=${1:-/usr/local}
|
||||||
mkdir -p "$install_dir"
|
mkdir -p "$install_dir"
|
||||||
cd /tmp
|
cd /tmp
|
||||||
curl -O -sL https://download.clojure.org/install/clojure-tools-1.10.1.447.tar.gz
|
curl -O -sL https://download.clojure.org/install/clojure-tools-1.10.1.447.tar.gz
|
||||||
7
script/install-leiningen
Executable file
7
script/install-leiningen
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
curl https://raw.githubusercontent.com/technomancy/leiningen/2.9.1/bin/lein > lein
|
||||||
|
mkdir -p /usr/local/bin/
|
||||||
|
mv lein /usr/local/bin/lein
|
||||||
|
chmod a+x /usr/local/bin/lein
|
||||||
|
lein self-install
|
||||||
13
script/lib_tests/arrangement_test
Executable file
13
script/lib_tests/arrangement_test
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
export BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {mvxcvi/arrangement {:mvn/version "1.2.0"}}}' -Spath)
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$BB_CMD -e "(require '[arrangement.core :as order]) (sort order/rank ['a false 2 :b nil 3.14159 \"c\" true \d [3 2] #{:one :two} [3 1 2] #{:three}])"
|
||||||
33
script/lib_tests/babashka_curl_test
Executable file
33
script/lib_tests/babashka_curl_test
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
export BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {babasha.curl {:local/root "babashka.curl"}}}' -Spath)
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$BB_CMD -e "
|
||||||
|
(require '[babashka.curl :as curl] :reload-all)
|
||||||
|
|
||||||
|
(prn (:status (curl/get \"https://www.clojure.org\")))
|
||||||
|
|
||||||
|
(prn (:status (curl/get \"https://postman-echo.com/get?foo1=bar1&foo2=bar2\")))
|
||||||
|
|
||||||
|
(prn (:status (curl/post \"https://postman-echo.com/post\")))
|
||||||
|
|
||||||
|
(prn (:status (curl/post \"https://postman-echo.com/post\"
|
||||||
|
{:body (json/generate-string {:a 1})
|
||||||
|
:headers {\"X-Hasura-Role\" \"admin\"}
|
||||||
|
:content-type :json
|
||||||
|
:accept :json})))
|
||||||
|
|
||||||
|
(prn (:status (curl/put \"https://postman-echo.com/put\"
|
||||||
|
{:body (json/generate-string {:a 1})
|
||||||
|
:headers {\"X-Hasura-Role\" \"admin\"}
|
||||||
|
:content-type :json
|
||||||
|
:accept :json})))
|
||||||
|
"
|
||||||
19
script/lib_tests/clj_yaml_test
Executable file
19
script/lib_tests/clj_yaml_test
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$BB_CMD -cp test-resources/lib_tests -e "
|
||||||
|
(require '[clojure.java.io :as io])
|
||||||
|
(require '[clj-yaml.core-test])
|
||||||
|
(require '[clojure.test :as t])
|
||||||
|
(let [{:keys [:test :pass :fail :error]} (t/run-tests 'clj-yaml.core-test)]
|
||||||
|
(when-not (pos? test)
|
||||||
|
(System/exit 1))
|
||||||
|
(System/exit (+ fail error)))
|
||||||
|
"
|
||||||
19
script/lib_tests/clojure_data_csv_test
Executable file
19
script/lib_tests/clojure_data_csv_test
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$BB_CMD -cp test-resources/lib_tests -e "
|
||||||
|
(require '[clojure.java.io :as io])
|
||||||
|
(require '[clojure.data.csv-test])
|
||||||
|
(require '[clojure.test :as t])
|
||||||
|
(let [{:keys [:test :pass :fail :error]} (t/run-tests 'clojure.data.csv-test)]
|
||||||
|
(when-not (pos? test)
|
||||||
|
(System/exit 1))
|
||||||
|
(System/exit (+ fail error)))
|
||||||
|
"
|
||||||
21
script/lib_tests/comb_test
Executable file
21
script/lib_tests/comb_test
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {comb {:mvn/version "0.1.1"}}}')
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
$BB_CMD '
|
||||||
|
(ns foo (:require [comb.template :as template]))
|
||||||
|
(prn (template/eval "<% (dotimes [x 3] %>foo<% ) %>"))
|
||||||
|
(prn (template/eval "Hello <%= name %>" {:name "Alice"}))
|
||||||
|
(def hello
|
||||||
|
(template/fn [name] "Hello <%= name %>"))
|
||||||
|
(prn (hello "Alice"))
|
||||||
|
'
|
||||||
18
script/lib_tests/cprop_test
Executable file
18
script/lib_tests/cprop_test
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {cprop {:mvn/version "0.1.16"}}}')
|
||||||
|
|
||||||
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
|
BB_CMD="./bb"
|
||||||
|
else
|
||||||
|
BB_CMD="lein bb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
CPROP_ENV="hello" $BB_CMD "
|
||||||
|
(require '[cprop.core :refer [load-config]])
|
||||||
|
(require '[cprop.source :refer [from-system-props from-env]])
|
||||||
|
(println (:cprop-env (from-env)))
|
||||||
|
"
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}}}' -Spath)"
|
export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "b059fdb06d5586a9a04c27e7b011c467ad8546db"}}}' -Spath)"
|
||||||
|
|
||||||
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
||||||
BB_CMD="./bb"
|
BB_CMD="./bb"
|
||||||
|
|
@ -10,4 +10,13 @@ else
|
||||||
BB_CMD="lein bb"
|
BB_CMD="lein bb"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$BB_CMD "(require '[lambdaisland.regal :as re]) (re/regex [:range \a \z])"
|
$BB_CMD "
|
||||||
|
(require '[lambdaisland.regal :as regal])
|
||||||
|
(def r [:cat
|
||||||
|
[:+ [:class [\a \z]]]
|
||||||
|
\"=\"
|
||||||
|
[:+ [:not \=]]])
|
||||||
|
|
||||||
|
(prn (regal/regex r))
|
||||||
|
(prn (re-matches (regal/regex r) \"foo=bar\"))
|
||||||
|
"
|
||||||
|
|
|
||||||
9
script/reflection.clj
Executable file
9
script/reflection.clj
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
|
(require '[clojure.java.io :as io]
|
||||||
|
'[clojure.java.shell :refer [sh]]
|
||||||
|
'[clojure.string :as str])
|
||||||
|
|
||||||
|
(def version (str/trim (slurp (io/file "resources" "BABASHKA_VERSION"))))
|
||||||
|
(sh "lein" "with-profiles" "+reflection" "run")
|
||||||
|
(io/copy (io/file "reflection.json") (io/file (str "babashka-" version "-reflection.json")))
|
||||||
|
|
@ -8,3 +8,9 @@ script/lib_tests/spartan_spec_test
|
||||||
script/lib_tests/clojure_csv_test
|
script/lib_tests/clojure_csv_test
|
||||||
script/lib_tests/regal_test
|
script/lib_tests/regal_test
|
||||||
script/lib_tests/medley_test
|
script/lib_tests/medley_test
|
||||||
|
script/lib_tests/babashka_curl_test
|
||||||
|
script/lib_tests/cprop_test
|
||||||
|
script/lib_tests/comb_test
|
||||||
|
script/lib_tests/arrangement_test
|
||||||
|
script/lib_tests/clj_yaml_test
|
||||||
|
script/lib_tests/clojure_data_csv_test
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
BABASHKA_PRELOADS=""
|
BABASHKA_PRELOADS=""
|
||||||
BABASHKA_CLASSPATH=""
|
BABASHKA_CLASSPATH=""
|
||||||
|
echo "running tests part 1"
|
||||||
lein test "$@"
|
lein test "$@"
|
||||||
|
|
||||||
BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
|
BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
|
||||||
BABASHKA_PRELOADS_TEST=true
|
BABASHKA_PRELOADS_TEST=true
|
||||||
|
echo "running tests part 2"
|
||||||
lein test :only babashka.main-test/preloads-test
|
lein test :only babashka.main-test/preloads-test
|
||||||
|
|
||||||
BABASHKA_PRELOADS="(require '[env-ns])"
|
BABASHKA_PRELOADS="(require '[env-ns])"
|
||||||
BABASHKA_CLASSPATH_TEST=true
|
BABASHKA_CLASSPATH_TEST=true
|
||||||
BABASHKA_CLASSPATH="test-resources/babashka/src_for_classpath_test/env"
|
BABASHKA_CLASSPATH="test-resources/babashka/src_for_classpath_test/env"
|
||||||
|
echo "running tests part 3"
|
||||||
lein test :only babashka.classpath-test/classpath-env-test
|
lein test :only babashka.classpath-test/classpath-env-test
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,50 @@
|
||||||
(ns babashka.impl.async
|
(ns babashka.impl.async
|
||||||
{:no-doc true}
|
{:no-doc true}
|
||||||
(:require [clojure.core.async :as async]
|
(:require [clojure.core.async :as async]
|
||||||
[clojure.core.async.impl.protocols :as protocols]))
|
[clojure.core.async.impl.protocols :as protocols]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
(def ^java.util.concurrent.Executor executor @#'async/thread-macro-executor)
|
||||||
|
|
||||||
|
(defn thread-call
|
||||||
|
"Executes f in another thread, returning immediately to the calling
|
||||||
|
thread. Returns a channel which will receive the result of calling
|
||||||
|
f when completed, then close."
|
||||||
|
[f]
|
||||||
|
(let [c (async/chan 1)]
|
||||||
|
(let [binds (vars/get-thread-binding-frame)]
|
||||||
|
(.execute executor
|
||||||
|
(fn []
|
||||||
|
(vars/reset-thread-binding-frame binds)
|
||||||
|
(try
|
||||||
|
(let [ret (f)]
|
||||||
|
(when-not (nil? ret)
|
||||||
|
(async/>!! c ret)))
|
||||||
|
(finally
|
||||||
|
(async/close! c))))))
|
||||||
|
c))
|
||||||
|
|
||||||
(defn thread
|
(defn thread
|
||||||
[_ _ & body]
|
[_ _ & body]
|
||||||
`(~'clojure.core.async/thread-call (fn [] ~@body)))
|
`(~'clojure.core.async/thread-call (fn [] ~@body)))
|
||||||
|
|
||||||
|
(defn alt!!
|
||||||
|
"Like alt!, except as if by alts!!, will block until completed, and
|
||||||
|
not intended for use in (go ...) blocks."
|
||||||
|
[_ _ & clauses]
|
||||||
|
(async/do-alt 'clojure.core.async/alts!! clauses))
|
||||||
|
|
||||||
|
(defn go-loop
|
||||||
|
[_ _ bindings & body]
|
||||||
|
(list 'clojure.core.async/thread (list* 'loop bindings body)))
|
||||||
|
|
||||||
(def async-namespace
|
(def async-namespace
|
||||||
{'<!! async/<!!
|
{'<!! async/<!!
|
||||||
'>!! async/>!!
|
'>!! async/>!!
|
||||||
'admix async/admix
|
'admix async/admix
|
||||||
'alts! async/alts!
|
'alts! async/alts!
|
||||||
'alts!! async/alts!!
|
'alts!! async/alts!!
|
||||||
|
'alt!! (with-meta alt!! {:sci/macro true})
|
||||||
'buffer async/buffer
|
'buffer async/buffer
|
||||||
'chan async/chan
|
'chan async/chan
|
||||||
'close! async/close!
|
'close! async/close!
|
||||||
|
|
@ -53,7 +85,7 @@
|
||||||
'take! async/take!
|
'take! async/take!
|
||||||
'tap async/tap
|
'tap async/tap
|
||||||
'thread (with-meta thread {:sci/macro true})
|
'thread (with-meta thread {:sci/macro true})
|
||||||
'thread-call async/thread-call
|
'thread-call thread-call
|
||||||
'timeout async/timeout
|
'timeout async/timeout
|
||||||
'to-chan async/to-chan
|
'to-chan async/to-chan
|
||||||
'toggle async/toggle
|
'toggle async/toggle
|
||||||
|
|
@ -65,7 +97,13 @@
|
||||||
'unsub async/unsub
|
'unsub async/unsub
|
||||||
'unsub-all async/unsub-all
|
'unsub-all async/unsub-all
|
||||||
'untap async/untap
|
'untap async/untap
|
||||||
'untap-all async/untap-all})
|
'untap-all async/untap-all
|
||||||
|
;; polyfill
|
||||||
|
'go (with-meta thread {:sci/macro true})
|
||||||
|
'<! async/<!!
|
||||||
|
'>! async/>!!
|
||||||
|
'alt! (with-meta alt!! {:sci/macro true})
|
||||||
|
'go-loop (with-meta go-loop {:sci/macro true})})
|
||||||
|
|
||||||
(def async-protocols-namespace
|
(def async-protocols-namespace
|
||||||
{'ReadPort protocols/ReadPort})
|
{'ReadPort protocols/ReadPort})
|
||||||
|
|
|
||||||
11
src/babashka/impl/bencode.clj
Normal file
11
src/babashka/impl/bencode.clj
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
(ns babashka.impl.bencode
|
||||||
|
{:no-doc true}
|
||||||
|
(:require [babashka.impl.bencode.core :as bencode]
|
||||||
|
[sci.impl.namespaces :refer [copy-var]]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
(def tns (vars/->SciNamespace 'bencode.core nil))
|
||||||
|
|
||||||
|
(def bencode-namespace
|
||||||
|
{'read-bencode (copy-var bencode/read-bencode tns)
|
||||||
|
'write-bencode (copy-var bencode/write-bencode tns)})
|
||||||
420
src/babashka/impl/bencode/core.clj
Normal file
420
src/babashka/impl/bencode/core.clj
Normal file
|
|
@ -0,0 +1,420 @@
|
||||||
|
(ns babashka.impl.bencode.core
|
||||||
|
"A netstring and bencode implementation for Clojure."
|
||||||
|
{:author "Meikel Brandmeyer"
|
||||||
|
:no-doc true}
|
||||||
|
(:require [clojure.java.io :as io])
|
||||||
|
(:import [java.io ByteArrayOutputStream
|
||||||
|
EOFException
|
||||||
|
InputStream
|
||||||
|
IOException
|
||||||
|
OutputStream
|
||||||
|
PushbackInputStream]))
|
||||||
|
|
||||||
|
;; Copyright (c) Meikel Brandmeyer. All rights reserved.
|
||||||
|
;; The use and distribution terms for this software are covered by the
|
||||||
|
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
||||||
|
;; which can be found in the file epl-v10.html at the root of this distribution.
|
||||||
|
;; By using this software in any fashion, you are agreeing to be bound by
|
||||||
|
;; the terms of this license.
|
||||||
|
;; You must not remove this notice, or any other, from this software.
|
||||||
|
|
||||||
|
;; # Motivation
|
||||||
|
;;
|
||||||
|
;; In each and every application, which contacts peer processes via some
|
||||||
|
;; communication channel, the handling of the communication channel is
|
||||||
|
;; obviously a central part of the application. Unfortunately introduces
|
||||||
|
;; handling of buffers of varying sizes often bugs in form of buffer
|
||||||
|
;; overflows and similar.
|
||||||
|
;;
|
||||||
|
;; A strong factor in this situation is of course the protocol which goes
|
||||||
|
;; over the wire. Depending on its design it might be difficult to estimate
|
||||||
|
;; the size of the input up front. This introduces more handling of message
|
||||||
|
;; buffers to accomodate for inputs of varying sizes. This is particularly
|
||||||
|
;; difficult in languages like C, where there is no bounds checking of array
|
||||||
|
;; accesses and where errors might go unnoticed for considerable amount of
|
||||||
|
;; time.
|
||||||
|
;;
|
||||||
|
;; To address these issues D. Bernstein developed the so called
|
||||||
|
;; [netstrings][net]. They are especially designed to allow easy construction
|
||||||
|
;; of the message buffers, easy and robust parsing.
|
||||||
|
;;
|
||||||
|
;; BitTorrent extended this to the [bencode][bc] protocol which also
|
||||||
|
;; includes ways to encode numbers and collections like lists or maps.
|
||||||
|
;;
|
||||||
|
;; *wire* is based on these ideas.
|
||||||
|
;;
|
||||||
|
;; [net]: http://cr.yp.to/proto/netstrings.txt
|
||||||
|
;; [bc]: http://wiki.theory.org/BitTorrentSpecification#Bencoding
|
||||||
|
;;
|
||||||
|
;; # Netstrings
|
||||||
|
;;
|
||||||
|
;; Now let's start with the basic netstrings. They consist of a byte count,
|
||||||
|
;; followed a colon and the binary data and a trailing comma. Examples:
|
||||||
|
;;
|
||||||
|
;; 13:Hello, World!,
|
||||||
|
;; 10:Guten Tag!,
|
||||||
|
;; 0:,
|
||||||
|
;;
|
||||||
|
;; The initial byte count allows to efficiently allocate a sufficiently
|
||||||
|
;; sized message buffer. The trailing comma serves as a hint to detect
|
||||||
|
;; incorrect netstrings.
|
||||||
|
;;
|
||||||
|
;; ## Low-level reading
|
||||||
|
;;
|
||||||
|
;; We will need some low-level reading helpers to read the bytes from
|
||||||
|
;; the input stream. These are `read-byte` as well as `read-bytes`. They
|
||||||
|
;; are split out, because doing such a simple task as reading a byte is
|
||||||
|
;; mild catastrophe in Java. So it would add some clutter to the algorithm
|
||||||
|
;; `read-netstring`.
|
||||||
|
;;
|
||||||
|
;; On the other hand they might be also useful elsewhere.
|
||||||
|
;;
|
||||||
|
;; To remove some magic numbers from the code below.
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(def #^{:const true} i 105)
|
||||||
|
(def #^{:const true} l 108)
|
||||||
|
(def #^{:const true} d 100)
|
||||||
|
(def #^{:const true} comma 44)
|
||||||
|
(def #^{:const true} minus 45)
|
||||||
|
|
||||||
|
;; These two are only used boxed. So we keep them extra here.
|
||||||
|
|
||||||
|
(def e 101)
|
||||||
|
(def colon 58)
|
||||||
|
|
||||||
|
(defn #^{:private true} read-byte
|
||||||
|
#^long [#^InputStream input]
|
||||||
|
(let [c (.read input)]
|
||||||
|
(when (neg? c)
|
||||||
|
(throw (EOFException. "Invalid netstring. Unexpected end of input.")))
|
||||||
|
;; Here we have a quirk for example. `.read` returns -1 on end of
|
||||||
|
;; input. However the Java `Byte` has only a range from -128 to 127.
|
||||||
|
;; How does the fit together?
|
||||||
|
;;
|
||||||
|
;; The whole thing is shifted. `.read` actually returns an int
|
||||||
|
;; between zero and 255. Everything below the value 128 stands
|
||||||
|
;; for itself. But larger values are actually negative byte values.
|
||||||
|
;;
|
||||||
|
;; So we have to do some translation here. `Byte/byteValue` would
|
||||||
|
;; do that for us, but we want to avoid boxing here.
|
||||||
|
(if (< 127 c) (- c 256) c)))
|
||||||
|
|
||||||
|
(defn #^{:private true :tag "[B"} read-bytes
|
||||||
|
#^Object [#^InputStream input n]
|
||||||
|
(let [content (byte-array n)]
|
||||||
|
(loop [offset (int 0)
|
||||||
|
len (int n)]
|
||||||
|
(let [result (.read input content offset len)]
|
||||||
|
(when (neg? result)
|
||||||
|
(throw
|
||||||
|
(EOFException.
|
||||||
|
"Invalid netstring. Less data available than expected.")))
|
||||||
|
(when (not= result len)
|
||||||
|
(recur (+ offset result) (- len result)))))
|
||||||
|
content))
|
||||||
|
|
||||||
|
;; `read-long` is used for reading integers from the stream as well
|
||||||
|
;; as the byte count prefixes of byte strings. The delimiter is \:
|
||||||
|
;; for byte count prefixes and \e for integers.
|
||||||
|
|
||||||
|
(defn #^{:private true} read-long
|
||||||
|
#^long [#^InputStream input delim]
|
||||||
|
(loop [n (long 0)]
|
||||||
|
;; We read repeatedly a byte from the input…
|
||||||
|
(let [b (read-byte input)]
|
||||||
|
;; …and stop at the delimiter.
|
||||||
|
(cond
|
||||||
|
(= b minus) (- (read-long input delim))
|
||||||
|
(= b delim) n
|
||||||
|
:else (recur (+ (* n (long 10)) (- (long b) (long 48))))))))
|
||||||
|
|
||||||
|
;; ## Reading a netstring
|
||||||
|
;;
|
||||||
|
;; Let's dive straight into reading a netstring from an `InputStream`.
|
||||||
|
;;
|
||||||
|
;; For convenience we split the function into two subfunctions. The
|
||||||
|
;; public `read-netstring` is the normal entry point, which also checks
|
||||||
|
;; for the trailing comma after reading the payload data with the
|
||||||
|
;; private `read-netstring*`.
|
||||||
|
;;
|
||||||
|
;; The reason we need the less strict `read-netstring*` is that with
|
||||||
|
;; bencode we don't have a trailing comma. So a check would not be
|
||||||
|
;; beneficial here.
|
||||||
|
;;
|
||||||
|
;; However the consumer doesn't have to care. `read-netstring` as
|
||||||
|
;; well as `read-bencode` provide the public entry points, which do
|
||||||
|
;; the right thing. Although they both may reference the `read-netstring*`
|
||||||
|
;; underneath.
|
||||||
|
;;
|
||||||
|
;; With this in mind we define the inner helper function first.
|
||||||
|
|
||||||
|
(declare #^"[B" string>payload
|
||||||
|
#^String string<payload)
|
||||||
|
|
||||||
|
(defn #^{:private true} read-netstring*
|
||||||
|
[input]
|
||||||
|
(read-bytes input (read-long input colon)))
|
||||||
|
|
||||||
|
;; And the public facing API: `read-netstring`.
|
||||||
|
|
||||||
|
(defn #^"[B" read-netstring
|
||||||
|
"Reads a classic netstring from input—an InputStream. Returns the
|
||||||
|
contained binary data as byte array."
|
||||||
|
[input]
|
||||||
|
(let [content (read-netstring* input)]
|
||||||
|
(when (not= (read-byte input) comma)
|
||||||
|
(throw (IOException. "Invalid netstring. ',' expected.")))
|
||||||
|
content))
|
||||||
|
|
||||||
|
;; Similarly the `string>payload` and `string<payload` functions
|
||||||
|
;; are defined as follows to simplify the conversion between strings
|
||||||
|
;; and byte arrays in various parts of the code.
|
||||||
|
|
||||||
|
(defn #^{:private true :tag "[B"} string>payload
|
||||||
|
[#^String s]
|
||||||
|
(.getBytes s "UTF-8"))
|
||||||
|
|
||||||
|
(defn #^{:private true :tag String} string<payload
|
||||||
|
[#^"[B" b]
|
||||||
|
(String. b "UTF-8"))
|
||||||
|
|
||||||
|
;; ## Writing a netstring
|
||||||
|
;;
|
||||||
|
;; This opposite operation – writing a netstring – is just as important.
|
||||||
|
;;
|
||||||
|
;; *Note:* We take here a byte array, just as we returned a byte
|
||||||
|
;; array in `read-netstring`. The netstring should not be concerned
|
||||||
|
;; about the actual contents. It just sees binary data.
|
||||||
|
;;
|
||||||
|
;; Similar to `read-netstring` we also split `write-netstring` into
|
||||||
|
;; the entry point itself and a helper function.
|
||||||
|
|
||||||
|
(defn #^{:private true} write-netstring*
|
||||||
|
[#^OutputStream output #^"[B" content]
|
||||||
|
(doto output
|
||||||
|
(.write (string>payload (str (alength content))))
|
||||||
|
(.write (int colon))
|
||||||
|
(.write content)))
|
||||||
|
|
||||||
|
(defn write-netstring
|
||||||
|
"Write the given binary data to the output stream in form of a classic
|
||||||
|
netstring."
|
||||||
|
[#^OutputStream output content]
|
||||||
|
(doto output
|
||||||
|
(write-netstring* content)
|
||||||
|
(.write (int comma))))
|
||||||
|
|
||||||
|
;; # Bencode
|
||||||
|
;;
|
||||||
|
;; However most of the time we don't want to send simple blobs of data
|
||||||
|
;; back and forth. The data sent between the communication peers usually
|
||||||
|
;; have some structure, which has to be carried along the way to the
|
||||||
|
;; other side. Here [bencode][bc] come into play.
|
||||||
|
;;
|
||||||
|
;; Bencode defines additionally to netstrings easily parseable structures
|
||||||
|
;; for lists, maps and numbers. It allows to communicate information
|
||||||
|
;; about the data structure to the peer on the other side.
|
||||||
|
;;
|
||||||
|
;; ## Tokens
|
||||||
|
;;
|
||||||
|
;; The data is encoded in tokens in bencode. There are several types of
|
||||||
|
;; tokens:
|
||||||
|
;;
|
||||||
|
;; * A netstring without trailing comma for string data.
|
||||||
|
;; * A tag specifiyng the type of the following tokens.
|
||||||
|
;; The tag may be one of these:
|
||||||
|
;; * `\i` to encode integers.
|
||||||
|
;; * `\l` to encode lists of items.
|
||||||
|
;; * `\d` to encode maps of item pairs.
|
||||||
|
;; * `\e` to end the a previously started tag.
|
||||||
|
;;
|
||||||
|
;; ## Reading bencode
|
||||||
|
;;
|
||||||
|
;; Reading bencode encoded data is basically parsing a stream of tokens
|
||||||
|
;; from the input. Hence we need a read-token helper which allows to
|
||||||
|
;; retrieve the next token.
|
||||||
|
|
||||||
|
(defn #^{:private true} read-token
|
||||||
|
[#^PushbackInputStream input]
|
||||||
|
(let [ch (read-byte input)]
|
||||||
|
(cond
|
||||||
|
(= (long e) ch) nil
|
||||||
|
(= i ch) :integer
|
||||||
|
(= l ch) :list
|
||||||
|
(= d ch) :map
|
||||||
|
:else (do
|
||||||
|
(.unread input (int ch))
|
||||||
|
(read-netstring* input)))))
|
||||||
|
|
||||||
|
;; To read the bencode encoded data we walk a long the sequence of tokens
|
||||||
|
;; and act according to the found tags.
|
||||||
|
|
||||||
|
(declare read-integer read-list read-map)
|
||||||
|
|
||||||
|
(defn read-bencode
|
||||||
|
"Read bencode token from the input stream."
|
||||||
|
[input]
|
||||||
|
(let [token (read-token input)]
|
||||||
|
(case token
|
||||||
|
:integer (read-integer input)
|
||||||
|
:list (read-list input)
|
||||||
|
:map (read-map input)
|
||||||
|
token)))
|
||||||
|
|
||||||
|
;; Of course integers and the collection types are have to treated specially.
|
||||||
|
;;
|
||||||
|
;; Integers for example consist of a sequence of decimal digits.
|
||||||
|
|
||||||
|
(defn #^{:private true} read-integer
|
||||||
|
[input]
|
||||||
|
(read-long input e))
|
||||||
|
|
||||||
|
;; *Note:* integers are an ugly special case, which cannot be
|
||||||
|
;; handled with `read-token` or `read-netstring*`.
|
||||||
|
;;
|
||||||
|
;; Lists are just a sequence of other tokens.
|
||||||
|
|
||||||
|
(declare token-seq)
|
||||||
|
|
||||||
|
(defn #^{:private true} read-list
|
||||||
|
[input]
|
||||||
|
(vec (token-seq input)))
|
||||||
|
|
||||||
|
;; Maps are sequences of key/value pairs. The keys are always
|
||||||
|
;; decoded into strings. The values are kept as is.
|
||||||
|
|
||||||
|
(defn #^{:private true} read-map
|
||||||
|
[input]
|
||||||
|
(->> (token-seq input)
|
||||||
|
(into {} (comp (partition-all 2)
|
||||||
|
(map (fn [[k v]]
|
||||||
|
[(string<payload k) v]))))))
|
||||||
|
|
||||||
|
;; The final missing piece is `token-seq`. This a just a simple
|
||||||
|
;; sequence which reads tokens until the next `\e`.
|
||||||
|
|
||||||
|
(defn #^{:private true} token-seq
|
||||||
|
[input]
|
||||||
|
(->> #(read-bencode input)
|
||||||
|
repeatedly
|
||||||
|
(take-while identity)))
|
||||||
|
|
||||||
|
;; ## Writing bencode
|
||||||
|
;;
|
||||||
|
;; Writing bencode is similar easy as reading it. The main entry point
|
||||||
|
;; takes a string, map, sequence or integer and writes it according to
|
||||||
|
;; the rules to the given OutputStream.
|
||||||
|
|
||||||
|
(defmulti write-bencode
|
||||||
|
"Write the given thing to the output stream. “Thing” means here a
|
||||||
|
string, map, sequence or integer. Alternatively an ByteArray may
|
||||||
|
be provided whose contents are written as a bytestring. Similar
|
||||||
|
the contents of a given InputStream are written as a byte string.
|
||||||
|
Named things (symbols or keywords) are written in the form
|
||||||
|
'namespace/name'."
|
||||||
|
(fn [_output thing]
|
||||||
|
(cond
|
||||||
|
(bytes? thing) :bytes
|
||||||
|
(instance? InputStream thing) :input-stream
|
||||||
|
(integer? thing) :integer
|
||||||
|
(string? thing) :string
|
||||||
|
(symbol? thing) :named
|
||||||
|
(keyword? thing) :named
|
||||||
|
(map? thing) :map
|
||||||
|
(or (nil? thing) (coll? thing) (.isArray (class thing))) :list
|
||||||
|
:else (type thing))))
|
||||||
|
|
||||||
|
(defmethod write-bencode :default
|
||||||
|
[output x]
|
||||||
|
(throw (IllegalArgumentException. (str "Cannot write value of type " (class x)))))
|
||||||
|
|
||||||
|
;; The following methods should be pretty straight-forward.
|
||||||
|
;;
|
||||||
|
;; The easiest case is of course when we already have a byte array.
|
||||||
|
;; We can simply pass it on to the underlying machinery.
|
||||||
|
|
||||||
|
(defmethod write-bencode :bytes
|
||||||
|
[output bytes]
|
||||||
|
(write-netstring* output bytes))
|
||||||
|
|
||||||
|
;; For strings we simply write the string as a netstring without
|
||||||
|
;; trailing comma after encoding the string as UTF-8 bytes.
|
||||||
|
|
||||||
|
(defmethod write-bencode :string
|
||||||
|
[output string]
|
||||||
|
(write-netstring* output (string>payload string)))
|
||||||
|
|
||||||
|
;; Streaming does not really work, since we need to know the
|
||||||
|
;; number of bytes to write upfront. So we read in everything
|
||||||
|
;; for InputStreams and pass on the byte array.
|
||||||
|
|
||||||
|
(defmethod write-bencode :input-stream
|
||||||
|
[output stream]
|
||||||
|
(let [bytes (ByteArrayOutputStream.)]
|
||||||
|
(io/copy stream bytes)
|
||||||
|
(write-netstring* output (.toByteArray bytes))))
|
||||||
|
|
||||||
|
;; Integers are again the ugly special case.
|
||||||
|
|
||||||
|
(defmethod write-bencode :integer
|
||||||
|
[#^OutputStream output n]
|
||||||
|
(doto output
|
||||||
|
(.write (int i))
|
||||||
|
(.write (string>payload (str n)))
|
||||||
|
(.write (int e))))
|
||||||
|
|
||||||
|
;; Symbols and keywords are converted to a string of the
|
||||||
|
;; form 'namespace/name' or just 'name' in case its not
|
||||||
|
;; qualified. We do not add colons for keywords since the
|
||||||
|
;; other side might not have the notion of keywords.
|
||||||
|
|
||||||
|
(defmethod write-bencode :named
|
||||||
|
[output thing]
|
||||||
|
(let [nspace (namespace thing)
|
||||||
|
name (name thing)]
|
||||||
|
(->> (str (when nspace (str nspace "/")) name)
|
||||||
|
string>payload
|
||||||
|
(write-netstring* output))))
|
||||||
|
|
||||||
|
;; Lists as well as maps work recursively to print their elements.
|
||||||
|
|
||||||
|
(defmethod write-bencode :list
|
||||||
|
[#^OutputStream output lst]
|
||||||
|
(.write output (int l))
|
||||||
|
(doseq [elt lst]
|
||||||
|
(write-bencode output elt))
|
||||||
|
(.write output (int e)))
|
||||||
|
|
||||||
|
;; However, maps are a bit special because their keys are sorted
|
||||||
|
;; lexicographically based on their byte string represantation.
|
||||||
|
|
||||||
|
(declare lexicographically)
|
||||||
|
|
||||||
|
(defmethod write-bencode :map
|
||||||
|
[#^OutputStream output m]
|
||||||
|
(let [translation (into {} (map (juxt string>payload identity) (keys m)))
|
||||||
|
key-strings (sort lexicographically (keys translation))
|
||||||
|
>value (comp m translation)]
|
||||||
|
(.write output (int d))
|
||||||
|
(doseq [k key-strings]
|
||||||
|
(write-netstring* output k)
|
||||||
|
(write-bencode output (>value k)))
|
||||||
|
(.write output (int e))))
|
||||||
|
|
||||||
|
;; However, since byte arrays are not `Comparable` we need a custom
|
||||||
|
;; comparator which we can feed to `sort`.
|
||||||
|
|
||||||
|
(defn #^{:private true} lexicographically
|
||||||
|
[#^"[B" a #^"[B" b]
|
||||||
|
(let [alen (alength a)
|
||||||
|
blen (alength b)
|
||||||
|
len (min alen blen)]
|
||||||
|
(loop [i 0]
|
||||||
|
(if (== i len)
|
||||||
|
(- alen blen)
|
||||||
|
(let [x (- (int (aget a i)) (int (aget b i)))]
|
||||||
|
(if (zero? x)
|
||||||
|
(recur (inc i))
|
||||||
|
x))))))
|
||||||
|
|
@ -1,19 +1,11 @@
|
||||||
(ns babashka.impl.classes
|
(ns babashka.impl.classes
|
||||||
{:no-doc true}
|
{:no-doc true}
|
||||||
(:require
|
(:require
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]))
|
||||||
#_[clojure.string :as str]))
|
|
||||||
|
|
||||||
;; (def os-name (str/lower-case (System/getProperty "os.name")))
|
|
||||||
;; (def os (cond (str/includes? os-name "mac") :mac
|
|
||||||
;; (or (str/includes? os-name "nix")
|
|
||||||
;; (str/includes? os-name "nux")) :linux
|
|
||||||
;; (str/includes? os-name "win") :windows))
|
|
||||||
;; (def unix-like? (or (identical? os :linux)
|
|
||||||
;; (identical? os :mac)))
|
|
||||||
|
|
||||||
(def classes
|
(def classes
|
||||||
'{:all [java.io.BufferedReader
|
`{:all [clojure.lang.ExceptionInfo
|
||||||
|
java.io.BufferedReader
|
||||||
java.io.BufferedWriter
|
java.io.BufferedWriter
|
||||||
java.io.ByteArrayInputStream
|
java.io.ByteArrayInputStream
|
||||||
java.io.ByteArrayOutputStream
|
java.io.ByteArrayOutputStream
|
||||||
|
|
@ -21,19 +13,27 @@
|
||||||
java.io.InputStream
|
java.io.InputStream
|
||||||
java.io.IOException
|
java.io.IOException
|
||||||
java.io.OutputStream
|
java.io.OutputStream
|
||||||
|
java.io.FileReader
|
||||||
|
java.io.PushbackInputStream
|
||||||
java.io.Reader
|
java.io.Reader
|
||||||
|
java.io.SequenceInputStream
|
||||||
java.io.StringReader
|
java.io.StringReader
|
||||||
java.io.StringWriter
|
java.io.StringWriter
|
||||||
java.io.Writer
|
java.io.Writer
|
||||||
java.lang.ArithmeticException
|
java.lang.ArithmeticException
|
||||||
java.lang.AssertionError
|
java.lang.AssertionError
|
||||||
java.lang.Boolean
|
java.lang.Boolean
|
||||||
|
java.lang.Byte
|
||||||
|
java.lang.Comparable
|
||||||
java.lang.Class
|
java.lang.Class
|
||||||
java.lang.Double
|
java.lang.Double
|
||||||
java.lang.Exception
|
java.lang.Exception
|
||||||
java.lang.Integer
|
java.lang.Integer
|
||||||
java.lang.Long
|
java.lang.Long
|
||||||
|
java.lang.NumberFormatException
|
||||||
java.lang.Math
|
java.lang.Math
|
||||||
|
java.lang.Runtime
|
||||||
|
java.lang.RuntimeException
|
||||||
java.util.concurrent.LinkedBlockingQueue
|
java.util.concurrent.LinkedBlockingQueue
|
||||||
java.lang.Object
|
java.lang.Object
|
||||||
java.lang.String
|
java.lang.String
|
||||||
|
|
@ -44,15 +44,21 @@
|
||||||
java.lang.ProcessBuilder
|
java.lang.ProcessBuilder
|
||||||
java.lang.ProcessBuilder$Redirect
|
java.lang.ProcessBuilder$Redirect
|
||||||
java.math.BigInteger
|
java.math.BigInteger
|
||||||
java.net.URI
|
java.net.DatagramSocket
|
||||||
|
java.net.DatagramPacket
|
||||||
java.net.HttpURLConnection
|
java.net.HttpURLConnection
|
||||||
|
java.net.InetAddress
|
||||||
java.net.ServerSocket
|
java.net.ServerSocket
|
||||||
java.net.Socket
|
java.net.Socket
|
||||||
java.net.UnknownHostException
|
java.net.UnknownHostException
|
||||||
|
java.net.URI
|
||||||
|
;; java.net.URL, see below
|
||||||
java.net.URLEncoder
|
java.net.URLEncoder
|
||||||
java.net.URLDecoder
|
java.net.URLDecoder
|
||||||
java.nio.file.CopyOption
|
java.nio.file.CopyOption
|
||||||
java.nio.file.FileAlreadyExistsException
|
java.nio.file.FileAlreadyExistsException
|
||||||
|
java.nio.file.FileSystem
|
||||||
|
java.nio.file.FileSystems
|
||||||
java.nio.file.Files
|
java.nio.file.Files
|
||||||
java.nio.file.LinkOption
|
java.nio.file.LinkOption
|
||||||
java.nio.file.NoSuchFileException
|
java.nio.file.NoSuchFileException
|
||||||
|
|
@ -83,18 +89,24 @@
|
||||||
java.time.ZonedDateTime
|
java.time.ZonedDateTime
|
||||||
java.time.ZoneId
|
java.time.ZoneId
|
||||||
java.time.ZoneOffset
|
java.time.ZoneOffset
|
||||||
|
java.time.temporal.ChronoUnit
|
||||||
java.time.temporal.TemporalAccessor
|
java.time.temporal.TemporalAccessor
|
||||||
java.util.regex.Pattern
|
java.util.regex.Pattern
|
||||||
java.util.Base64
|
java.util.Base64
|
||||||
java.util.Base64$Decoder
|
java.util.Base64$Decoder
|
||||||
java.util.Base64$Encoder
|
java.util.Base64$Encoder
|
||||||
java.util.Date
|
java.util.Date
|
||||||
|
java.util.MissingResourceException
|
||||||
|
java.util.Properties
|
||||||
java.util.UUID
|
java.util.UUID
|
||||||
java.util.concurrent.TimeUnit
|
java.util.concurrent.TimeUnit
|
||||||
java.util.zip.InflaterInputStream
|
java.util.zip.InflaterInputStream
|
||||||
java.util.zip.DeflaterInputStream
|
java.util.zip.DeflaterInputStream
|
||||||
java.util.zip.GZIPInputStream
|
java.util.zip.GZIPInputStream
|
||||||
java.util.zip.GZIPOutputStream]
|
java.util.zip.GZIPOutputStream
|
||||||
|
org.yaml.snakeyaml.error.YAMLException
|
||||||
|
~(symbol "[B")
|
||||||
|
]
|
||||||
:constructors [clojure.lang.Delay
|
:constructors [clojure.lang.Delay
|
||||||
clojure.lang.MapEntry
|
clojure.lang.MapEntry
|
||||||
clojure.lang.LineNumberingPushbackReader
|
clojure.lang.LineNumberingPushbackReader
|
||||||
|
|
@ -104,8 +116,7 @@
|
||||||
:methods [borkdude.graal.LockFix ;; support for locking
|
:methods [borkdude.graal.LockFix ;; support for locking
|
||||||
]
|
]
|
||||||
:fields [clojure.lang.PersistentQueue]
|
:fields [clojure.lang.PersistentQueue]
|
||||||
:instance-checks [clojure.lang.ExceptionInfo
|
:instance-checks [clojure.lang.IObj
|
||||||
clojure.lang.IObj
|
|
||||||
clojure.lang.IEditableCollection]
|
clojure.lang.IEditableCollection]
|
||||||
:custom {clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true
|
:custom {clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true
|
||||||
:allPublicMethods true}
|
:allPublicMethods true}
|
||||||
|
|
@ -197,7 +208,13 @@
|
||||||
(instance? java.io.ByteArrayOutputStream v)
|
(instance? java.io.ByteArrayOutputStream v)
|
||||||
java.io.ByteArrayOutputStream
|
java.io.ByteArrayOutputStream
|
||||||
(instance? java.security.MessageDigest v)
|
(instance? java.security.MessageDigest v)
|
||||||
java.security.MessageDigest)))))
|
java.security.MessageDigest
|
||||||
|
(instance? java.io.InputStream v)
|
||||||
|
java.io.InputStream
|
||||||
|
(instance? java.io.OutputStream v)
|
||||||
|
java.io.OutputStream
|
||||||
|
(instance? java.nio.file.FileSystem v)
|
||||||
|
java.nio.file.FileSystem)))))
|
||||||
|
|
||||||
(def class-map (gen-class-map))
|
(def class-map (gen-class-map))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
(ns babashka.impl.clojure.core
|
(ns babashka.impl.clojure.core
|
||||||
{:no-doc true}
|
{:no-doc true}
|
||||||
(:refer-clojure :exclude [future])
|
(:refer-clojure :exclude [future read read-string])
|
||||||
(:require [borkdude.graal.locking :as locking]))
|
(:require [borkdude.graal.locking :as locking]
|
||||||
|
[sci.core :as sci]
|
||||||
|
[sci.impl.namespaces :refer [copy-core-var]]))
|
||||||
|
|
||||||
(defn locking* [form bindings v f & args]
|
(defn locking* [form bindings v f & args]
|
||||||
(apply @#'locking/locking form bindings v f args))
|
(apply @#'locking/locking form bindings v f args))
|
||||||
|
|
@ -16,17 +18,17 @@
|
||||||
ret#))
|
ret#))
|
||||||
|
|
||||||
(def core-extras
|
(def core-extras
|
||||||
{'file-seq file-seq
|
{'file-seq (copy-core-var file-seq)
|
||||||
'agent agent
|
'agent (copy-core-var agent)
|
||||||
'instance? instance? ;; TODO: move to sci
|
'send (copy-core-var send)
|
||||||
'send send
|
'send-off (copy-core-var send-off)
|
||||||
'send-off send-off
|
'promise (copy-core-var promise)
|
||||||
'promise promise
|
'deliver (copy-core-var deliver)
|
||||||
'deliver deliver
|
|
||||||
'locking (with-meta locking* {:sci/macro true})
|
'locking (with-meta locking* {:sci/macro true})
|
||||||
'shutdown-agents shutdown-agents
|
'shutdown-agents (copy-core-var shutdown-agents)
|
||||||
'slurp slurp
|
'slurp (copy-core-var slurp)
|
||||||
'spit spit
|
'spit (copy-core-var spit)
|
||||||
'time (with-meta time* {:sci/macro true})
|
'time (with-meta time* {:sci/macro true})
|
||||||
'Throwable->map Throwable->map
|
'Throwable->map (copy-core-var Throwable->map)
|
||||||
'compare-and-set! compare-and-set!})
|
'compare-and-set! (copy-core-var compare-and-set!)
|
||||||
|
'*data-readers* (sci/new-dynamic-var '*data-readers* nil)})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
;; Modified / stripped version of clojure.core.server for use with babashka on
|
;; Modified / stripped version of clojure.core.server for use with babashka on
|
||||||
;; GraalVM.
|
;; GraalVM.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) Rich Hickey. All rights reserved.
|
;; Copyright (c) Rich Hickey. All rights reserved.
|
||||||
;; The use and distribution terms for this software are covered by the
|
;; The use and distribution terms for this software are covered by the
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,11 @@
|
||||||
*e nil]
|
*e nil]
|
||||||
~@body))
|
~@body))
|
||||||
|
|
||||||
|
(def ^{:doc "A sequence of lib specs that are applied to `require`
|
||||||
|
by default when a new command-line REPL is started."} repl-requires
|
||||||
|
'[[clojure.repl :refer (dir doc)]
|
||||||
|
[clojure.pprint :refer (pprint)]])
|
||||||
|
|
||||||
(defn repl
|
(defn repl
|
||||||
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
|
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
|
||||||
writes to *out*, and prints exception summaries to *err*. If you use the
|
writes to *out*, and prints exception summaries to *err*. If you use the
|
||||||
|
|
|
||||||
12
src/babashka/impl/clojure/pprint.clj
Normal file
12
src/babashka/impl/clojure/pprint.clj
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
(ns babashka.impl.clojure.pprint
|
||||||
|
{:no-doc true}
|
||||||
|
(:require [fipp.edn :as fipp]))
|
||||||
|
|
||||||
|
(defn pprint
|
||||||
|
([edn]
|
||||||
|
(fipp/pprint edn))
|
||||||
|
([edn writer]
|
||||||
|
(fipp/pprint edn {:writer writer})))
|
||||||
|
|
||||||
|
(def pprint-namespace
|
||||||
|
{'pprint pprint})
|
||||||
|
|
@ -1,88 +1,16 @@
|
||||||
;; Copyright (c) Rich Hickey. All rights reserved.
|
(ns babashka.impl.clojure.stacktrace
|
||||||
;; The use and distribution terms for this software are covered by the
|
{:no-doc true}
|
||||||
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
(:require [clojure.stacktrace :as stacktrace]
|
||||||
;; which can be found in the file epl-v10.html at the root of this distribution.
|
[sci.core :as sci]))
|
||||||
;; By using this software in any fashion, you are agreeing to be bound by
|
|
||||||
;; the terms of this license.
|
|
||||||
;; You must not remove this notice, or any other, from this software.
|
|
||||||
|
|
||||||
;;; stacktrace.clj: print Clojure-centric stack traces
|
(defmacro wrap-out [f]
|
||||||
|
`(fn [& ~'args]
|
||||||
;; by Stuart Sierra
|
(binding [*out* @sci/out]
|
||||||
;; January 6, 2009
|
(apply ~f ~'args))))
|
||||||
|
|
||||||
(ns ^{:doc "Print stack traces oriented towards Clojure, not Java."
|
|
||||||
:author "Stuart Sierra"
|
|
||||||
:no-doc true}
|
|
||||||
babashka.impl.clojure.stacktrace)
|
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
|
||||||
|
|
||||||
(defn root-cause
|
|
||||||
"Returns the last 'cause' Throwable in a chain of Throwables."
|
|
||||||
{:added "1.1"}
|
|
||||||
[^Throwable tr]
|
|
||||||
(if-let [cause (.getCause tr)]
|
|
||||||
(recur cause)
|
|
||||||
tr))
|
|
||||||
|
|
||||||
(defn print-trace-element
|
|
||||||
"Prints a Clojure-oriented view of one element in a stack trace."
|
|
||||||
{:added "1.1"}
|
|
||||||
[^StackTraceElement e]
|
|
||||||
(let [class (.getClassName e)
|
|
||||||
method (.getMethodName e)]
|
|
||||||
(let [match (re-matches #"^([A-Za-z0-9_.-]+)\$(\w+)__\d+$" (str class))]
|
|
||||||
(if (and match (= "invoke" method))
|
|
||||||
(apply printf "%s/%s" (rest match))
|
|
||||||
(printf "%s.%s" class method))))
|
|
||||||
(printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e)))
|
|
||||||
|
|
||||||
(defn print-throwable
|
|
||||||
"Prints the class and message of a Throwable. Prints the ex-data map
|
|
||||||
if present."
|
|
||||||
{:added "1.1"}
|
|
||||||
[^Throwable tr]
|
|
||||||
(printf "%s: %s" (.getName (class tr)) (.getMessage tr))
|
|
||||||
(when-let [info (ex-data tr)]
|
|
||||||
(newline)
|
|
||||||
(pr info)))
|
|
||||||
|
|
||||||
(defn print-stack-trace
|
|
||||||
"Prints a Clojure-oriented stack trace of tr, a Throwable.
|
|
||||||
Prints a maximum of n stack frames (default: unlimited).
|
|
||||||
Does not print chained exceptions (causes)."
|
|
||||||
{:added "1.1"}
|
|
||||||
([tr] (print-stack-trace tr nil))
|
|
||||||
([^Throwable tr n]
|
|
||||||
(let [st (.getStackTrace tr)]
|
|
||||||
(print-throwable tr)
|
|
||||||
(newline)
|
|
||||||
(print " at ")
|
|
||||||
(if-let [e (first st)]
|
|
||||||
(print-trace-element e)
|
|
||||||
(print "[empty stack trace]"))
|
|
||||||
(newline)
|
|
||||||
(doseq [e (if (nil? n)
|
|
||||||
(rest st)
|
|
||||||
(take (dec n) (rest st)))]
|
|
||||||
(print " ")
|
|
||||||
(print-trace-element e)
|
|
||||||
(newline)))))
|
|
||||||
|
|
||||||
(defn print-cause-trace
|
|
||||||
"Like print-stack-trace but prints chained exceptions (causes)."
|
|
||||||
{:added "1.1"}
|
|
||||||
([tr] (print-cause-trace tr nil))
|
|
||||||
([^Throwable tr n]
|
|
||||||
(print-stack-trace tr n)
|
|
||||||
(when-let [cause (.getCause tr)]
|
|
||||||
(print "Caused by: " )
|
|
||||||
(recur cause n))))
|
|
||||||
|
|
||||||
(def stacktrace-namespace
|
(def stacktrace-namespace
|
||||||
{'root-cause root-cause
|
{'root-cause stacktrace/root-cause
|
||||||
'print-trace-element print-trace-element
|
'print-trace-element (wrap-out stacktrace/print-trace-element)
|
||||||
'print-throwable print-throwable
|
'print-throwable (wrap-out stacktrace/print-throwable)
|
||||||
'print-stack-trace print-stack-trace
|
'print-stack-trace (wrap-out stacktrace/print-stack-trace)
|
||||||
'print-cause-trace print-cause-trace})
|
'print-cause-trace (wrap-out stacktrace/print-cause-trace)})
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
; Copyright (c) Rich Hickey. All rights reserved.
|
;; Copyright (c) Rich Hickey. All rights reserved.
|
||||||
; The use and distribution terms for this software are covered by the
|
;; The use and distribution terms for this software are covered by the
|
||||||
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
||||||
; which can be found in the file epl-v10.html at the root of this distribution.
|
;; which can be found in the file epl-v10.html at the root of this distribution.
|
||||||
; By using this software in any fashion, you are agreeing to be bound by
|
;; By using this software in any fashion, you are agreeing to be bound by
|
||||||
; the terms of this license.
|
;; the terms of this license.
|
||||||
; You must not remove this notice, or any other, from this software.
|
;; You must not remove this notice, or any other, from this software.
|
||||||
|
|
||||||
;;; test.clj: test framework for Clojure
|
;;; test.clj: test framework for Clojure
|
||||||
|
|
||||||
|
|
@ -232,9 +232,8 @@
|
||||||
For additional event types, see the examples in the code.
|
For additional event types, see the examples in the code.
|
||||||
"}
|
"}
|
||||||
babashka.impl.clojure.test
|
babashka.impl.clojure.test
|
||||||
(:require [babashka.impl.clojure.stacktrace :as stack]
|
(:require [babashka.impl.common :refer [ctx]]
|
||||||
[babashka.impl.common :refer [ctx]]
|
[clojure.stacktrace :as stack]
|
||||||
[clojure.string :as str]
|
|
||||||
[clojure.template :as temp]
|
[clojure.template :as temp]
|
||||||
[sci.core :as sci]
|
[sci.core :as sci]
|
||||||
[sci.impl.analyzer :as ana]
|
[sci.impl.analyzer :as ana]
|
||||||
|
|
@ -430,9 +429,9 @@
|
||||||
result# (apply ~pred values#)]
|
result# (apply ~pred values#)]
|
||||||
(if result#
|
(if result#
|
||||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||||
:expected '~form, :actual (cons ~pred values#)})
|
:expected '~form, :actual (cons ~pred values#)})
|
||||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||||
:expected '~form, :actual (list '~'not (cons '~pred values#))}))
|
:expected '~form, :actual (list '~'not (cons '~pred values#))}))
|
||||||
result#)))
|
result#)))
|
||||||
|
|
||||||
(defn assert-any
|
(defn assert-any
|
||||||
|
|
@ -443,9 +442,9 @@
|
||||||
`(let [value# ~form]
|
`(let [value# ~form]
|
||||||
(if value#
|
(if value#
|
||||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||||
:expected '~form, :actual value#})
|
:expected '~form, :actual value#})
|
||||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||||
:expected '~form, :actual value#}))
|
:expected '~form, :actual value#}))
|
||||||
value#))
|
value#))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -479,9 +478,9 @@
|
||||||
(let [result# (instance? klass# object#)]
|
(let [result# (instance? klass# object#)]
|
||||||
(if result#
|
(if result#
|
||||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||||
:expected '~form, :actual (class object#)})
|
:expected '~form, :actual (class object#)})
|
||||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||||
:expected '~form, :actual (class object#)}))
|
:expected '~form, :actual (class object#)}))
|
||||||
result#)))
|
result#)))
|
||||||
|
|
||||||
(defmethod assert-expr 'thrown? [msg form]
|
(defmethod assert-expr 'thrown? [msg form]
|
||||||
|
|
@ -492,10 +491,10 @@
|
||||||
body (nthnext form 2)]
|
body (nthnext form 2)]
|
||||||
`(try ~@body
|
`(try ~@body
|
||||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||||
:expected '~form, :actual nil})
|
:expected '~form, :actual nil})
|
||||||
(catch ~klass e#
|
(catch ~klass e#
|
||||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||||
:expected '~form, :actual e#})
|
:expected '~form, :actual e#})
|
||||||
e#))))
|
e#))))
|
||||||
|
|
||||||
(defmethod assert-expr 'thrown-with-msg? [msg form]
|
(defmethod assert-expr 'thrown-with-msg? [msg form]
|
||||||
|
|
@ -512,7 +511,7 @@
|
||||||
(let [m# (.getMessage e#)]
|
(let [m# (.getMessage e#)]
|
||||||
(if (re-find ~re m#)
|
(if (re-find ~re m#)
|
||||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||||
:expected '~form, :actual e#})
|
:expected '~form, :actual e#})
|
||||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||||
:expected '~form, :actual e#})))
|
:expected '~form, :actual e#})))
|
||||||
e#))))
|
e#))))
|
||||||
|
|
|
||||||
16
src/babashka/impl/curl.clj
Normal file
16
src/babashka/impl/curl.clj
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
(ns babashka.impl.curl
|
||||||
|
{:no-doc true}
|
||||||
|
(:require [babashka.curl :as curl]
|
||||||
|
[sci.impl.namespaces :refer [copy-var]]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
(def tns (vars/->SciNamespace 'babashka.curl nil))
|
||||||
|
|
||||||
|
(def curl-namespace
|
||||||
|
{'request (copy-var curl/request tns)
|
||||||
|
'get (copy-var curl/get tns)
|
||||||
|
'patch (copy-var curl/patch tns)
|
||||||
|
'post (copy-var curl/post tns)
|
||||||
|
'put (copy-var curl/put tns)
|
||||||
|
'head (copy-var curl/head tns)
|
||||||
|
'curl-command (copy-var curl/curl-command tns)})
|
||||||
198
src/babashka/impl/nrepl_server.clj
Normal file
198
src/babashka/impl/nrepl_server.clj
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
(ns babashka.impl.nrepl-server
|
||||||
|
{:no-doc true}
|
||||||
|
(:refer-clojure :exclude [send future binding])
|
||||||
|
(:require [babashka.impl.bencode.core :refer [read-bencode]]
|
||||||
|
[babashka.impl.nrepl-server.utils :refer [dev? response-for send send-exception
|
||||||
|
replying-print-writer]]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.tools.reader.reader-types :as r]
|
||||||
|
[sci.core :as sci]
|
||||||
|
[sci.impl.interpreter :refer [eval-string* eval-form]]
|
||||||
|
[sci.impl.parser :as p]
|
||||||
|
[sci.impl.utils :as sci-utils]
|
||||||
|
[sci.impl.vars :as vars])
|
||||||
|
(:import [java.io InputStream PushbackInputStream EOFException BufferedOutputStream]
|
||||||
|
[java.net ServerSocket]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(defn eval-msg [ctx o msg]
|
||||||
|
(try
|
||||||
|
(let [code-str (get msg :code)
|
||||||
|
reader (r/indexing-push-back-reader (r/string-push-back-reader code-str))
|
||||||
|
ns-str (get msg :ns)
|
||||||
|
sci-ns (when ns-str (sci-utils/namespace-object (:env ctx) (symbol ns-str) true nil))]
|
||||||
|
(when @dev? (println "current ns" (vars/current-ns-name)))
|
||||||
|
(sci/with-bindings (cond-> {}
|
||||||
|
sci-ns (assoc vars/current-ns sci-ns))
|
||||||
|
(loop []
|
||||||
|
(let [pw (replying-print-writer o msg)
|
||||||
|
form (p/parse-next ctx reader)
|
||||||
|
value (if (identical? :edamame.impl.parser/eof form) ::nil
|
||||||
|
(sci/with-bindings {sci/out pw}
|
||||||
|
(eval-form ctx form)))
|
||||||
|
env (:env ctx)]
|
||||||
|
(swap! env update-in [:namespaces 'clojure.core]
|
||||||
|
(fn [core]
|
||||||
|
(assoc core
|
||||||
|
'*1 value
|
||||||
|
'*2 (get core '*1)
|
||||||
|
'*3 (get core '*2))))
|
||||||
|
(send o (response-for msg (cond-> {"ns" (vars/current-ns-name)}
|
||||||
|
(not (identical? value ::nil)) (assoc "value" (pr-str value)))))
|
||||||
|
(when (not (identical? ::nil value))
|
||||||
|
(recur)))))
|
||||||
|
(send o (response-for msg {"status" #{"done"}})))
|
||||||
|
(catch Exception ex
|
||||||
|
(swap! (:env ctx) update-in [:namespaces 'clojure.core]
|
||||||
|
assoc '*e ex)
|
||||||
|
(send-exception o msg ex))))
|
||||||
|
|
||||||
|
(defn fully-qualified-syms [ctx ns-sym]
|
||||||
|
(let [syms (eval-string* ctx (format "(keys (ns-map '%s))" ns-sym))
|
||||||
|
sym-strs (map #(str "`" %) syms)
|
||||||
|
sym-expr (str "[" (str/join " " sym-strs) "]")
|
||||||
|
syms (eval-string* ctx sym-expr)]
|
||||||
|
syms))
|
||||||
|
|
||||||
|
(defn match [_alias->ns ns->alias query [sym-ns sym-name qualifier]]
|
||||||
|
(let [pat (re-pattern query)]
|
||||||
|
(or (when (and (identical? :unqualified qualifier) (re-find pat sym-name))
|
||||||
|
[sym-ns sym-name])
|
||||||
|
(when sym-ns
|
||||||
|
(or (when (re-find pat (str (get ns->alias (symbol sym-ns)) "/" sym-name))
|
||||||
|
[sym-ns (str (get ns->alias (symbol sym-ns)) "/" sym-name)])
|
||||||
|
(when (re-find pat (str sym-ns "/" sym-name))
|
||||||
|
[sym-ns (str sym-ns "/" sym-name)]))))))
|
||||||
|
|
||||||
|
(defn complete [ctx o msg]
|
||||||
|
(try
|
||||||
|
(let [ns-str (get msg :ns)
|
||||||
|
sci-ns (when ns-str
|
||||||
|
(sci-utils/namespace-object (:env ctx) (symbol ns-str) nil false))]
|
||||||
|
(sci/binding [vars/current-ns (or sci-ns @vars/current-ns)]
|
||||||
|
(let [query (:symbol msg)
|
||||||
|
from-current-ns (fully-qualified-syms ctx (eval-string* ctx "(ns-name *ns*)"))
|
||||||
|
from-current-ns (map (fn [sym]
|
||||||
|
[(namespace sym) (name sym) :unqualified])
|
||||||
|
from-current-ns)
|
||||||
|
alias->ns (eval-string* ctx "(let [m (ns-aliases *ns*)] (zipmap (keys m) (map ns-name (vals m))))")
|
||||||
|
ns->alias (zipmap (vals alias->ns) (keys alias->ns))
|
||||||
|
from-aliased-nss (doall (mapcat
|
||||||
|
(fn [alias]
|
||||||
|
(let [ns (get alias->ns alias)
|
||||||
|
syms (eval-string* ctx (format "(keys (ns-publics '%s))" ns))]
|
||||||
|
(map (fn [sym]
|
||||||
|
[(str ns) (str sym) :qualified])
|
||||||
|
syms)))
|
||||||
|
(keys alias->ns)))
|
||||||
|
svs (concat from-current-ns from-aliased-nss)
|
||||||
|
completions (keep (fn [entry]
|
||||||
|
(match alias->ns ns->alias query entry))
|
||||||
|
svs)
|
||||||
|
completions (mapv (fn [[namespace name]]
|
||||||
|
{"candidate" (str name) "ns" (str namespace) #_"type" #_"function"})
|
||||||
|
completions)]
|
||||||
|
(when @dev? (prn "completions" completions))
|
||||||
|
(send o (response-for msg {"completions" completions
|
||||||
|
"status" #{"done"}})))))
|
||||||
|
(catch Throwable e
|
||||||
|
(println e)
|
||||||
|
(send o (response-for msg {"completions" []
|
||||||
|
"status" #{"done"}})))))
|
||||||
|
|
||||||
|
(defn close-session [ctx msg _is os]
|
||||||
|
(let [session (:session msg)]
|
||||||
|
(swap! (:sessions ctx) disj session))
|
||||||
|
(send os (response-for msg {"status" #{"done" "session-closed"}})))
|
||||||
|
|
||||||
|
(defn ls-sessions [ctx msg os]
|
||||||
|
(let [sessions @(:sessions ctx)]
|
||||||
|
(send os (response-for msg {"sessions" sessions
|
||||||
|
"status" #{"done"}}))))
|
||||||
|
|
||||||
|
(defn read-msg [msg]
|
||||||
|
(-> (zipmap (map keyword (keys msg))
|
||||||
|
(map #(if (bytes? %)
|
||||||
|
(String. (bytes %))
|
||||||
|
%) (vals msg)))
|
||||||
|
(update :op keyword)))
|
||||||
|
|
||||||
|
(defn session-loop [ctx ^InputStream is os id]
|
||||||
|
(when @dev? (println "Reading!" id (.available is)))
|
||||||
|
(when-let [msg (try (read-bencode is)
|
||||||
|
(catch EOFException _
|
||||||
|
(println "Client closed connection.")))]
|
||||||
|
(let [msg (read-msg msg)]
|
||||||
|
(when @dev? (prn "Received" msg))
|
||||||
|
(case (get msg :op)
|
||||||
|
:clone (do
|
||||||
|
(when @dev? (println "Cloning!"))
|
||||||
|
(let [id (str (java.util.UUID/randomUUID))]
|
||||||
|
(swap! (:sessions ctx) (fnil conj #{}) id)
|
||||||
|
(send os (response-for msg {"new-session" id "status" #{"done"}}))
|
||||||
|
(recur ctx is os id)))
|
||||||
|
:close (do (close-session ctx msg is os)
|
||||||
|
(recur ctx is os id))
|
||||||
|
:eval (do
|
||||||
|
(eval-msg ctx os msg)
|
||||||
|
(recur ctx is os id))
|
||||||
|
:load-file (let [file (:file msg)
|
||||||
|
msg (assoc msg :code file)]
|
||||||
|
(eval-msg ctx os msg)
|
||||||
|
(recur ctx is os id))
|
||||||
|
:complete (do
|
||||||
|
(complete ctx os msg)
|
||||||
|
(recur ctx is os id))
|
||||||
|
:describe
|
||||||
|
(do (send os (response-for msg {"status" #{"done"}
|
||||||
|
"ops" (zipmap #{"clone" "close" "eval" "load-file"
|
||||||
|
"complete" "describe" "ls-sessions"}
|
||||||
|
(repeat {}))}))
|
||||||
|
(recur ctx is os id))
|
||||||
|
:ls-sessions (do (ls-sessions ctx msg os)
|
||||||
|
(recur ctx is os id))
|
||||||
|
;; fallback
|
||||||
|
(do (when @dev?
|
||||||
|
(println "Unhandled message" msg))
|
||||||
|
(send os (response-for msg {"status" #{"error" "unknown-op" "done"}}))
|
||||||
|
(recur ctx is os id))))))
|
||||||
|
|
||||||
|
(defn listen [ctx ^ServerSocket listener]
|
||||||
|
(when @dev? (println "Listening"))
|
||||||
|
(let [client-socket (.accept listener)
|
||||||
|
in (.getInputStream client-socket)
|
||||||
|
in (PushbackInputStream. in)
|
||||||
|
out (.getOutputStream client-socket)
|
||||||
|
out (BufferedOutputStream. out)]
|
||||||
|
(when @dev? (println "Connected."))
|
||||||
|
(sci/future
|
||||||
|
(sci/binding
|
||||||
|
;; allow *ns* to be set! inside future
|
||||||
|
[vars/current-ns (vars/->SciNamespace 'user nil)
|
||||||
|
sci/print-length @sci/print-length]
|
||||||
|
(session-loop ctx in out "pre-init")))
|
||||||
|
(recur ctx listener)))
|
||||||
|
|
||||||
|
(def server (atom nil))
|
||||||
|
|
||||||
|
(defn stop-server! []
|
||||||
|
(when-let [s @server]
|
||||||
|
(.close ^ServerSocket s)
|
||||||
|
(reset! server nil)))
|
||||||
|
|
||||||
|
(defn start-server! [ctx host+port]
|
||||||
|
(vreset! dev? (= "true" (System/getenv "BABASHKA_DEV")))
|
||||||
|
(let [ctx (assoc ctx :sessions (atom #{}))
|
||||||
|
parts (str/split host+port #":")
|
||||||
|
[address port] (if (= 1 (count parts))
|
||||||
|
[nil (Integer. ^String (first parts))]
|
||||||
|
[(java.net.InetAddress/getByName (first parts))
|
||||||
|
(Integer. ^String (second parts))])
|
||||||
|
host+port (if-not address (str "localhost:" port)
|
||||||
|
host+port)
|
||||||
|
socket-server (new ServerSocket port 0 address)]
|
||||||
|
(println "Started nREPL server at" host+port)
|
||||||
|
(println "For more info visit https://github.com/borkdude/babashka/blob/master/doc/repl.md#nrepl.")
|
||||||
|
(reset! server socket-server)
|
||||||
|
(listen ctx socket-server)))
|
||||||
63
src/babashka/impl/nrepl_server/utils.clj
Normal file
63
src/babashka/impl/nrepl_server/utils.clj
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
(ns babashka.impl.nrepl-server.utils
|
||||||
|
{:no-doc true}
|
||||||
|
(:refer-clojure :exclude [send])
|
||||||
|
(:require [babashka.impl.bencode.core :refer [write-bencode]])
|
||||||
|
(:import [java.io Writer PrintWriter StringWriter OutputStream BufferedWriter]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(def dev? (volatile! nil))
|
||||||
|
|
||||||
|
(defn response-for [old-msg msg]
|
||||||
|
(let [session (get old-msg :session "none")
|
||||||
|
id (get old-msg :id "unknown")]
|
||||||
|
(assoc msg "session" session "id" id)))
|
||||||
|
|
||||||
|
(defn send [^OutputStream os msg]
|
||||||
|
;;(when @dev? (prn "Sending" msg))
|
||||||
|
(write-bencode os msg)
|
||||||
|
(.flush os))
|
||||||
|
|
||||||
|
(defn send-exception [os msg ^Throwable ex]
|
||||||
|
(let [ex-map (Throwable->map ex)
|
||||||
|
ex-name (-> ex-map :via first :type)
|
||||||
|
cause (:cause ex-map)]
|
||||||
|
(when @dev? (prn "sending exception" ex-map))
|
||||||
|
(send os (response-for msg {"err" (str ex-name ": " cause "\n")}))
|
||||||
|
(send os (response-for msg {"ex" (str "class " ex-name)
|
||||||
|
"root-ex" (str "class " ex-name)
|
||||||
|
"status" #{"eval-error"}}))
|
||||||
|
(send os (response-for msg {"status" #{"done"}}))))
|
||||||
|
|
||||||
|
;; from https://github.com/nrepl/nrepl/blob/1cc9baae631703c184894559a2232275dc50dff6/src/clojure/nrepl/middleware/print.clj#L63
|
||||||
|
(defn- to-char-array
|
||||||
|
^chars
|
||||||
|
[x]
|
||||||
|
(cond
|
||||||
|
(string? x) (.toCharArray ^String x)
|
||||||
|
(integer? x) (char-array [(char x)])
|
||||||
|
:else x))
|
||||||
|
|
||||||
|
;; from https://github.com/nrepl/nrepl/blob/1cc9baae631703c184894559a2232275dc50dff6/src/clojure/nrepl/middleware/print.clj#L99
|
||||||
|
(defn replying-print-writer
|
||||||
|
"Returns a `java.io.PrintWriter` suitable for binding as `*out*` or `*err*`. All
|
||||||
|
of the content written to that `PrintWriter` will be sent as messages on the
|
||||||
|
transport of `msg`, keyed by `key`."
|
||||||
|
^java.io.PrintWriter
|
||||||
|
[o msg]
|
||||||
|
(-> (proxy [Writer] []
|
||||||
|
(write
|
||||||
|
([x]
|
||||||
|
(let [cbuf (to-char-array x)]
|
||||||
|
(.write ^Writer this cbuf (int 0) (count cbuf))))
|
||||||
|
([x off len]
|
||||||
|
(let [cbuf (to-char-array x)
|
||||||
|
text (str (doto (StringWriter.)
|
||||||
|
(.write cbuf ^int off ^int len)))]
|
||||||
|
(when (pos? (count text))
|
||||||
|
(when @dev? (println "out str:" text))
|
||||||
|
(send o (response-for msg {"out" text}))))))
|
||||||
|
(flush [])
|
||||||
|
(close []))
|
||||||
|
(BufferedWriter. 1024)
|
||||||
|
(PrintWriter. true)))
|
||||||
|
|
@ -9,8 +9,9 @@
|
||||||
(identical? :PIPE @pipe-state))
|
(identical? :PIPE @pipe-state))
|
||||||
|
|
||||||
(defn handle-pipe! []
|
(defn handle-pipe! []
|
||||||
(Signal/handle
|
(when-not (= "true" (System/getenv "BABASHKA_DISABLE_PIPE_SIGNAL_HANDLER"))
|
||||||
(Signal. "PIPE")
|
(Signal/handle
|
||||||
(reify SignalHandler
|
(Signal. "PIPE")
|
||||||
(handle [_ _]
|
(reify SignalHandler
|
||||||
(vreset! pipe-state :PIPE)))))
|
(handle [_ _]
|
||||||
|
(vreset! pipe-state :PIPE))))))
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,21 @@
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.reader.reader-types :as r]
|
[clojure.tools.reader.reader-types :as r]
|
||||||
[sci.impl.interpreter :refer [eval-form]]
|
|
||||||
[sci.impl.parser :as parser]
|
|
||||||
[sci.impl.vars :as vars]
|
|
||||||
[sci.core :as sci]
|
[sci.core :as sci]
|
||||||
[sci.impl.io :as sio]))
|
[sci.impl.interpreter :refer [eval-form]]
|
||||||
|
[sci.impl.io :as sio]
|
||||||
|
[sci.impl.parser :as parser]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn repl-caught
|
(defn repl-caught
|
||||||
"Default :caught hook for repl"
|
"Default :caught hook for repl"
|
||||||
[e]
|
[^Throwable e]
|
||||||
(sci/with-bindings {sci/out @sci/err}
|
(sci/with-bindings {sci/out @sci/err}
|
||||||
(sio/println (.getMessage ^Exception e))
|
(sio/println (str (.. e getClass getName)
|
||||||
|
(when-let [m (.getMessage e)]
|
||||||
|
(str ": " m)) ))
|
||||||
(sio/flush)))
|
(sio/flush)))
|
||||||
|
|
||||||
(defn repl
|
(defn repl
|
||||||
|
|
@ -31,19 +35,15 @@
|
||||||
(sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
|
(sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
|
||||||
(sio/println "Clojure rocks, Bash reaches.")
|
(sio/println "Clojure rocks, Bash reaches.")
|
||||||
(sio/println)
|
(sio/println)
|
||||||
(eval-form sci-ctx '(require '[clojure.repl :refer [dir doc]]))))
|
(eval-form sci-ctx '(use 'clojure.repl))))
|
||||||
:read (or read
|
:read (or read
|
||||||
(fn [_request-prompt request-exit]
|
(fn [_request-prompt request-exit]
|
||||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in))
|
(let [v (parser/parse-next sci-ctx in)]
|
||||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in)) this works fine
|
(if (or (identical? :repl/quit v)
|
||||||
(if (r/peek-char in) ;; if this is nil, we reached EOF
|
(identical? :repl/exit v)
|
||||||
(let [v (parser/parse-next sci-ctx in)]
|
(identical? :edamame.impl.parser/eof v))
|
||||||
(if (or (identical? :repl/quit v)
|
request-exit
|
||||||
(identical? :repl/exit v)
|
v))))
|
||||||
(identical? :edamame.impl.parser/eof v))
|
|
||||||
request-exit
|
|
||||||
v))
|
|
||||||
request-exit)))
|
|
||||||
:eval (or eval
|
:eval (or eval
|
||||||
(fn [expr]
|
(fn [expr]
|
||||||
(let [ret (eval-form (update sci-ctx
|
(let [ret (eval-form (update sci-ctx
|
||||||
|
|
|
||||||
14
src/babashka/impl/sigint_handler.clj
Normal file
14
src/babashka/impl/sigint_handler.clj
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
(ns babashka.impl.sigint-handler
|
||||||
|
{:no-doc true}
|
||||||
|
(:import [sun.misc Signal]
|
||||||
|
[sun.misc SignalHandler]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(defn handle-sigint! []
|
||||||
|
(Signal/handle
|
||||||
|
(Signal. "INT")
|
||||||
|
(reify SignalHandler
|
||||||
|
(handle [_ _]
|
||||||
|
;; This is needed to run shutdown hooks on interrupt, System/exit triggers those
|
||||||
|
(System/exit 0)))))
|
||||||
13
src/babashka/impl/transit.clj
Normal file
13
src/babashka/impl/transit.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns babashka.impl.transit
|
||||||
|
(:require [cognitect.transit :as transit]
|
||||||
|
[sci.impl.namespaces :refer [copy-var]]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
|
||||||
|
(def tns (vars/->SciNamespace 'cognitect.transit nil))
|
||||||
|
|
||||||
|
(def transit-namespace
|
||||||
|
{'write (copy-var transit/write tns)
|
||||||
|
'writer (copy-var transit/writer tns)
|
||||||
|
'read (copy-var transit/read tns)
|
||||||
|
'reader (copy-var transit/reader tns)})
|
||||||
13
src/babashka/impl/yaml.clj
Normal file
13
src/babashka/impl/yaml.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns babashka.impl.yaml
|
||||||
|
{:no-doc true}
|
||||||
|
(:require [clj-yaml.core :as yaml]
|
||||||
|
[sci.impl.namespaces :refer [copy-var]]
|
||||||
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
|
(def yns (vars/->SciNamespace 'clj-yaml.core nil))
|
||||||
|
|
||||||
|
(def yaml-namespace
|
||||||
|
{'mark (copy-var yaml/mark yns)
|
||||||
|
'unmark (copy-var yaml/unmark yns)
|
||||||
|
'generate-string (copy-var yaml/generate-string yns)
|
||||||
|
'parse-string (copy-var yaml/parse-string yns)})
|
||||||
|
|
@ -2,31 +2,39 @@
|
||||||
{:no-doc true}
|
{:no-doc true}
|
||||||
(:require
|
(:require
|
||||||
[babashka.impl.async :refer [async-namespace async-protocols-namespace]]
|
[babashka.impl.async :refer [async-namespace async-protocols-namespace]]
|
||||||
|
[babashka.impl.bencode :refer [bencode-namespace]]
|
||||||
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
|
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
|
||||||
[babashka.impl.classes :as classes]
|
[babashka.impl.classes :as classes]
|
||||||
[babashka.impl.classpath :as cp]
|
[babashka.impl.classpath :as cp]
|
||||||
[babashka.impl.clojure.core :refer [core-extras]]
|
[babashka.impl.clojure.core :refer [core-extras]]
|
||||||
[babashka.impl.clojure.java.io :refer [io-namespace]]
|
[babashka.impl.clojure.java.io :refer [io-namespace]]
|
||||||
[babashka.impl.clojure.java.shell :refer [shell-namespace]]
|
[babashka.impl.clojure.java.shell :refer [shell-namespace]]
|
||||||
[babashka.impl.clojure.main :refer [demunge]]
|
[babashka.impl.clojure.main :as clojure-main :refer [demunge]]
|
||||||
[babashka.impl.clojure.stacktrace :refer [stacktrace-namespace print-stack-trace]]
|
[babashka.impl.clojure.pprint :refer [pprint-namespace]]
|
||||||
|
[babashka.impl.clojure.stacktrace :refer [stacktrace-namespace]]
|
||||||
[babashka.impl.common :as common]
|
[babashka.impl.common :as common]
|
||||||
[babashka.impl.csv :as csv]
|
[babashka.impl.csv :as csv]
|
||||||
|
[babashka.impl.curl :refer [curl-namespace]]
|
||||||
|
[babashka.impl.nrepl-server :as nrepl-server]
|
||||||
[babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]]
|
[babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]]
|
||||||
[babashka.impl.repl :as repl]
|
[babashka.impl.repl :as repl]
|
||||||
|
[babashka.impl.sigint-handler :as sigint-handler]
|
||||||
[babashka.impl.socket-repl :as socket-repl]
|
[babashka.impl.socket-repl :as socket-repl]
|
||||||
[babashka.impl.test :as t]
|
[babashka.impl.test :as t]
|
||||||
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
||||||
[babashka.impl.xml :as xml]
|
[babashka.impl.xml :as xml]
|
||||||
|
[babashka.impl.transit :refer [transit-namespace]]
|
||||||
|
[babashka.impl.yaml :refer [yaml-namespace]]
|
||||||
[babashka.wait :as wait]
|
[babashka.wait :as wait]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
|
[clojure.stacktrace :refer [print-stack-trace]]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[fipp.edn :as fipp]
|
|
||||||
[sci.addons :as addons]
|
[sci.addons :as addons]
|
||||||
[sci.core :as sci]
|
[sci.core :as sci]
|
||||||
[sci.impl.interpreter :refer [eval-string*]]
|
[sci.impl.interpreter :refer [eval-string*]]
|
||||||
[sci.impl.opts :as sci-opts]
|
[sci.impl.opts :as sci-opts]
|
||||||
|
[sci.impl.types :as sci-types]
|
||||||
[sci.impl.unrestrict :refer [*unrestricted*]]
|
[sci.impl.unrestrict :refer [*unrestricted*]]
|
||||||
[sci.impl.vars :as vars])
|
[sci.impl.vars :as vars])
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
@ -110,7 +118,14 @@
|
||||||
(let [options (next options)]
|
(let [options (next options)]
|
||||||
(recur (next options)
|
(recur (next options)
|
||||||
(assoc opts-map
|
(assoc opts-map
|
||||||
:socket-repl (first options))))
|
:socket-repl (or (first options)
|
||||||
|
"1666"))))
|
||||||
|
("--nrepl-server")
|
||||||
|
(let [options (next options)]
|
||||||
|
(recur (next options)
|
||||||
|
(assoc opts-map
|
||||||
|
:nrepl (or (first options)
|
||||||
|
"1667"))))
|
||||||
("--eval", "-e")
|
("--eval", "-e")
|
||||||
(let [options (next options)]
|
(let [options (next options)]
|
||||||
(recur (next options)
|
(recur (next options)
|
||||||
|
|
@ -154,7 +169,7 @@
|
||||||
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
||||||
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
||||||
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
||||||
--repl | --socket-repl [<host>:]<port> ]
|
--repl | --socket-repl [<host>:]<port> | --nrepl-server [<host>:]<port> ]
|
||||||
[ arg* ]")
|
[ arg* ]")
|
||||||
(defn print-usage []
|
(defn print-usage []
|
||||||
(println usage-string))
|
(println usage-string))
|
||||||
|
|
@ -184,6 +199,7 @@
|
||||||
-m, --main <ns> Call the -main function from namespace with args.
|
-m, --main <ns> Call the -main function from namespace with args.
|
||||||
--repl Start REPL. Use rlwrap for history.
|
--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).
|
--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).
|
||||||
--time Print execution time before exiting.
|
--time Print execution time before exiting.
|
||||||
-- Stop parsing args and pass everything after -- to *command-line-args*
|
-- Stop parsing args and pass everything after -- to *command-line-args*
|
||||||
|
|
||||||
|
|
@ -198,26 +214,27 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(str/replace x #"^#!.*" ""))
|
(str/replace x #"^#!.*" ""))
|
||||||
(throw (Exception. (str "File does not exist: " file))))))
|
(throw (Exception. (str "File does not exist: " file))))))
|
||||||
|
|
||||||
(defn read-edn []
|
|
||||||
(edn/read {;;:readers *data-readers*
|
|
||||||
:eof ::EOF} *in*))
|
|
||||||
|
|
||||||
(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false))
|
(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false))
|
||||||
|
|
||||||
(defn load-file* [sci-ctx f]
|
(defn load-file* [sci-ctx f]
|
||||||
(let [f (io/file f)
|
(let [f (io/file f)
|
||||||
s (slurp f)]
|
s (slurp f)
|
||||||
|
prev-ns @vars/current-ns]
|
||||||
(sci/with-bindings {vars/current-file (.getCanonicalPath f)}
|
(sci/with-bindings {vars/current-file (.getCanonicalPath f)}
|
||||||
(eval-string* sci-ctx s))))
|
(try
|
||||||
|
(eval-string* sci-ctx s)
|
||||||
(defn eval* [sci-ctx form]
|
(finally (sci-types/setVal vars/current-ns prev-ns))))))
|
||||||
(eval-string* sci-ctx (pr-str form)))
|
|
||||||
|
|
||||||
(defn start-socket-repl! [address ctx]
|
(defn start-socket-repl! [address ctx]
|
||||||
(socket-repl/start-repl! address ctx)
|
(socket-repl/start-repl! address ctx)
|
||||||
;; hang until SIGINT
|
;; hang until SIGINT
|
||||||
@(promise))
|
@(promise))
|
||||||
|
|
||||||
|
(defn start-nrepl! [address ctx]
|
||||||
|
(nrepl-server/start-server! ctx address)
|
||||||
|
;; hang until SIGINT
|
||||||
|
#_@(promise))
|
||||||
|
|
||||||
(defn exit [n]
|
(defn exit [n]
|
||||||
(throw (ex-info "" {:bb/exit-code n})))
|
(throw (ex-info "" {:bb/exit-code n})))
|
||||||
|
|
||||||
|
|
@ -231,7 +248,11 @@ Everything after that is bound to *command-line-args*."))
|
||||||
async clojure.core.async
|
async clojure.core.async
|
||||||
csv clojure.data.csv
|
csv clojure.data.csv
|
||||||
json cheshire.core
|
json cheshire.core
|
||||||
xml clojure.data.xml})
|
xml clojure.data.xml
|
||||||
|
yaml clj-yaml.core
|
||||||
|
curl babashka.curl
|
||||||
|
transit cognitect.transit
|
||||||
|
bencode bencode.core})
|
||||||
|
|
||||||
(def cp-state (atom nil))
|
(def cp-state (atom nil))
|
||||||
|
|
||||||
|
|
@ -257,12 +278,17 @@ Everything after that is bound to *command-line-args*."))
|
||||||
'clojure.data.csv csv/csv-namespace
|
'clojure.data.csv csv/csv-namespace
|
||||||
'cheshire.core cheshire-core-namespace
|
'cheshire.core cheshire-core-namespace
|
||||||
'clojure.stacktrace stacktrace-namespace
|
'clojure.stacktrace stacktrace-namespace
|
||||||
'clojure.main {'demunge demunge}
|
'clojure.main {'demunge demunge
|
||||||
|
'repl-requires clojure-main/repl-requires}
|
||||||
'clojure.repl {'demunge demunge}
|
'clojure.repl {'demunge demunge}
|
||||||
'clojure.test t/clojure-test-namespace
|
'clojure.test t/clojure-test-namespace
|
||||||
'babashka.classpath {'add-classpath add-classpath*}
|
'babashka.classpath {'add-classpath add-classpath*}
|
||||||
'clojure.pprint {'pprint fipp/pprint}
|
'clojure.data.xml xml/xml-namespace
|
||||||
'clojure.data.xml xml/xml-namespace})
|
'clj-yaml.core yaml-namespace
|
||||||
|
'clojure.pprint pprint-namespace
|
||||||
|
'babashka.curl curl-namespace
|
||||||
|
'cognitect.transit transit-namespace
|
||||||
|
'bencode.core bencode-namespace})
|
||||||
|
|
||||||
(def bindings
|
(def bindings
|
||||||
{'java.lang.System/exit exit ;; override exit, so we have more control
|
{'java.lang.System/exit exit ;; override exit, so we have more control
|
||||||
|
|
@ -284,6 +310,7 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(defn main
|
(defn main
|
||||||
[& args]
|
[& args]
|
||||||
(handle-pipe!)
|
(handle-pipe!)
|
||||||
|
(sigint-handler/handle-sigint!)
|
||||||
#_(binding [*out* *err*]
|
#_(binding [*out* *err*]
|
||||||
(prn "M" (meta (get bindings 'future))))
|
(prn "M" (meta (get bindings 'future))))
|
||||||
(binding [*unrestricted* true]
|
(binding [*unrestricted* true]
|
||||||
|
|
@ -293,16 +320,18 @@ Everything after that is bound to *command-line-args*."))
|
||||||
{:keys [:version :shell-in :edn-in :shell-out :edn-out
|
{:keys [:version :shell-in :edn-in :shell-out :edn-out
|
||||||
:help? :file :command-line-args
|
:help? :file :command-line-args
|
||||||
:expressions :stream? :time?
|
:expressions :stream? :time?
|
||||||
:repl :socket-repl
|
:repl :socket-repl :nrepl
|
||||||
:verbose? :classpath
|
:verbose? :classpath
|
||||||
:main :uberscript] :as _opts}
|
:main :uberscript] :as _opts}
|
||||||
(parse-opts args)
|
(parse-opts args)
|
||||||
|
_ (when main (System/setProperty "babashka.main" main))
|
||||||
read-next (fn [*in*]
|
read-next (fn [*in*]
|
||||||
(if (pipe-signal-received?)
|
(if (pipe-signal-received?)
|
||||||
::EOF
|
::EOF
|
||||||
(if stream?
|
(if stream?
|
||||||
(if shell-in (or (read-line) ::EOF)
|
(if shell-in (or (read-line) ::EOF)
|
||||||
(read-edn))
|
(edn/read {;;:readers *data-readers*
|
||||||
|
:eof ::EOF} *in*))
|
||||||
(delay (cond shell-in
|
(delay (cond shell-in
|
||||||
(shell-seq *in*)
|
(shell-seq *in*)
|
||||||
edn-in
|
edn-in
|
||||||
|
|
@ -337,6 +366,7 @@ Everything after that is bound to *command-line-args*."))
|
||||||
:imports '{ArithmeticException java.lang.ArithmeticException
|
:imports '{ArithmeticException java.lang.ArithmeticException
|
||||||
AssertionError java.lang.AssertionError
|
AssertionError java.lang.AssertionError
|
||||||
Boolean java.lang.Boolean
|
Boolean java.lang.Boolean
|
||||||
|
Byte java.lang.Byte
|
||||||
Class java.lang.Class
|
Class java.lang.Class
|
||||||
Double java.lang.Double
|
Double java.lang.Double
|
||||||
Exception java.lang.Exception
|
Exception java.lang.Exception
|
||||||
|
|
@ -345,7 +375,10 @@ Everything after that is bound to *command-line-args*."))
|
||||||
File java.io.File
|
File java.io.File
|
||||||
Long java.lang.Long
|
Long java.lang.Long
|
||||||
Math java.lang.Math
|
Math java.lang.Math
|
||||||
|
NumberFormatException java.lang.NumberFormatException
|
||||||
Object java.lang.Object
|
Object java.lang.Object
|
||||||
|
Runtime java.lang.Runtime
|
||||||
|
RuntimeException java.lang.RuntimeException
|
||||||
ProcessBuilder java.lang.ProcessBuilder
|
ProcessBuilder java.lang.ProcessBuilder
|
||||||
String java.lang.String
|
String java.lang.String
|
||||||
StringBuilder java.lang.StringBuilder
|
StringBuilder java.lang.StringBuilder
|
||||||
|
|
@ -357,17 +390,20 @@ Everything after that is bound to *command-line-args*."))
|
||||||
ctx (addons/future ctx)
|
ctx (addons/future ctx)
|
||||||
sci-ctx (sci-opts/init ctx)
|
sci-ctx (sci-opts/init ctx)
|
||||||
_ (vreset! common/ctx sci-ctx)
|
_ (vreset! common/ctx sci-ctx)
|
||||||
|
input-var (sci/new-dynamic-var '*input* nil)
|
||||||
_ (swap! (:env sci-ctx)
|
_ (swap! (:env sci-ctx)
|
||||||
(fn [env]
|
(fn [env]
|
||||||
(update-in env [:namespaces 'clojure.core] assoc
|
(update env :namespaces
|
||||||
'eval #(eval* sci-ctx %)
|
(fn [namespaces] [:namespaces 'clojure.main 'repl]
|
||||||
'load-file #(load-file* sci-ctx %))))
|
(-> namespaces
|
||||||
_ (swap! (:env sci-ctx)
|
(assoc-in ['clojure.core 'load-file] #(load-file* sci-ctx %))
|
||||||
(fn [env]
|
(assoc-in ['clojure.main 'repl]
|
||||||
(assoc-in env [:namespaces 'clojure.main 'repl]
|
(fn [& opts]
|
||||||
(fn [& opts]
|
(let [opts (apply hash-map opts)]
|
||||||
(let [opts (apply hash-map opts)]
|
(repl/start-repl! sci-ctx opts))))
|
||||||
(repl/start-repl! sci-ctx opts))))))
|
(assoc-in ['user (with-meta '*input*
|
||||||
|
(when-not stream?
|
||||||
|
{:sci.impl/deref! true}))] input-var))))))
|
||||||
preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim))
|
preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim))
|
||||||
[expressions exit-code]
|
[expressions exit-code]
|
||||||
(cond expressions [expressions nil]
|
(cond expressions [expressions nil]
|
||||||
|
|
@ -395,17 +431,16 @@ Everything after that is bound to *command-line-args*."))
|
||||||
[(print-help) 0]
|
[(print-help) 0]
|
||||||
repl [(repl/start-repl! sci-ctx) 0]
|
repl [(repl/start-repl! sci-ctx) 0]
|
||||||
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
|
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
|
||||||
|
nrepl [(start-nrepl! nrepl sci-ctx) 0]
|
||||||
(not (str/blank? expression))
|
(not (str/blank? expression))
|
||||||
(try
|
(try
|
||||||
(loop [in (read-next *in*)]
|
(loop []
|
||||||
(let [_ (swap! env update-in [:namespaces 'user]
|
(let [in (read-next *in*)]
|
||||||
assoc (with-meta '*input*
|
|
||||||
(when-not stream?
|
|
||||||
{:sci.impl/deref! true}))
|
|
||||||
(sci/new-dynamic-var '*input* in))]
|
|
||||||
(if (identical? ::EOF in)
|
(if (identical? ::EOF in)
|
||||||
[nil 0] ;; done streaming
|
[nil 0] ;; done streaming
|
||||||
(let [res [(let [res (eval-string* sci-ctx expression)]
|
(let [res [(let [res
|
||||||
|
(sci/binding [input-var in]
|
||||||
|
(eval-string* sci-ctx expression))]
|
||||||
(when (some? res)
|
(when (some? res)
|
||||||
(if-let [pr-f (cond shell-out println
|
(if-let [pr-f (cond shell-out println
|
||||||
edn-out prn)]
|
edn-out prn)]
|
||||||
|
|
@ -416,7 +451,7 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(pr-f res))
|
(pr-f res))
|
||||||
(prn res)))) 0]]
|
(prn res)))) 0]]
|
||||||
(if stream?
|
(if stream?
|
||||||
(recur (read-next *in*))
|
(recur)
|
||||||
res)))))
|
res)))))
|
||||||
(catch Throwable e
|
(catch Throwable e
|
||||||
(error-handler* e verbose?)))
|
(error-handler* e verbose?)))
|
||||||
|
|
@ -440,7 +475,8 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(defn -main
|
(defn -main
|
||||||
[& args]
|
[& args]
|
||||||
(if-let [dev-opts (System/getenv "BABASHKA_DEV")]
|
(if-let [dev-opts (System/getenv "BABASHKA_DEV")]
|
||||||
(let [{:keys [:n]} (edn/read-string dev-opts)
|
(let [{:keys [:n]} (if (= "true" dev-opts) {:n 1}
|
||||||
|
(edn/read-string dev-opts))
|
||||||
last-iteration (dec n)]
|
last-iteration (dec n)]
|
||||||
(dotimes [i n]
|
(dotimes [i n]
|
||||||
(if (< i last-iteration)
|
(if (< i last-iteration)
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
opts)
|
opts)
|
||||||
t0 (System/currentTimeMillis)]
|
t0 (System/currentTimeMillis)]
|
||||||
(loop []
|
(loop []
|
||||||
(let [v (try (with-open [_ (Socket. host port)]
|
(let [v (try (.close (Socket. host port))
|
||||||
(- (System/currentTimeMillis) t0))
|
(- (System/currentTimeMillis) t0)
|
||||||
(catch ConnectException _e
|
(catch ConnectException _e
|
||||||
(let [took (- (System/currentTimeMillis) t0)]
|
(let [took (- (System/currentTimeMillis) t0)]
|
||||||
(if (and timeout (>= took timeout))
|
(if (and timeout (>= took timeout))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
(ns my.main2)
|
||||||
|
|
||||||
|
(defn -main [& _args]
|
||||||
|
(System/getProperty "babashka.main"))
|
||||||
49
test-resources/babashka/statsd.clj
Normal file
49
test-resources/babashka/statsd.clj
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
(ns statsd-client
|
||||||
|
"a simple StatsD client written in Clojure
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
statsd-client/increment 'foo
|
||||||
|
statsd-client/decrement 'foo
|
||||||
|
statsd-client/increment 'foo 1
|
||||||
|
statsd-client/decrement 'foo 1
|
||||||
|
statsd-client/gauge 'foo 1
|
||||||
|
statsd-client/timing 'foo 1
|
||||||
|
"
|
||||||
|
(:import (java.net InetAddress DatagramPacket DatagramSocket)))
|
||||||
|
|
||||||
|
(def server-address "127.0.0.1")
|
||||||
|
(def server-port 8125)
|
||||||
|
|
||||||
|
; UDP helper functions
|
||||||
|
(defn make-socket
|
||||||
|
([] (new DatagramSocket))
|
||||||
|
([port] (new DatagramSocket port)))
|
||||||
|
|
||||||
|
(defn send-data [send-socket ip port data]
|
||||||
|
(let [ipaddress (InetAddress/getByName ip),
|
||||||
|
send-packet (new DatagramPacket (.getBytes data) (.length data) ipaddress port)]
|
||||||
|
(.send send-socket send-packet)))
|
||||||
|
|
||||||
|
(defn make-send [ip port]
|
||||||
|
(let [send-socket (make-socket)]
|
||||||
|
(fn [data] (send-data send-socket ip port data))))
|
||||||
|
|
||||||
|
(def send-msg (make-send server-address server-port))
|
||||||
|
|
||||||
|
; statsd client functions
|
||||||
|
(defn increment
|
||||||
|
([metric] (increment metric 1))
|
||||||
|
([metric value]
|
||||||
|
(send-msg (str metric ":" value "|c"))))
|
||||||
|
|
||||||
|
(defn decrement
|
||||||
|
([metric] (increment metric -1))
|
||||||
|
([metric value]
|
||||||
|
(send-msg (str metric ":" value "|c"))))
|
||||||
|
|
||||||
|
(defn timing [metric value]
|
||||||
|
(send-msg (str metric ":" value "|ms")))
|
||||||
|
|
||||||
|
(defn gauge [metric value]
|
||||||
|
(send-msg (str metric ":" value "|g")))
|
||||||
|
|
||||||
18
test-resources/babashka/transit.clj
Normal file
18
test-resources/babashka/transit.clj
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
(require '[cognitect.transit :as transit])
|
||||||
|
(import [java.io ByteArrayInputStream ByteArrayOutputStream])
|
||||||
|
|
||||||
|
;; Write data to a stream
|
||||||
|
(def out (ByteArrayOutputStream. 4096))
|
||||||
|
(def writer (transit/writer out :json))
|
||||||
|
(transit/write writer "foo")
|
||||||
|
(transit/write writer {:a [1 2]})
|
||||||
|
|
||||||
|
;; Take a peek at the JSON
|
||||||
|
(.toString out)
|
||||||
|
;; => "{\"~#'\":\"foo\"} [\"^ \",\"~:a\",[1,2]]"
|
||||||
|
|
||||||
|
;; Read data from a stream
|
||||||
|
(def in (ByteArrayInputStream. (.toByteArray out)))
|
||||||
|
(def reader (transit/reader in :json))
|
||||||
|
(prn (transit/read reader)) ;; => "foo"
|
||||||
|
(prn (transit/read reader)) ;; => {:a [1 2]}
|
||||||
203
test-resources/lib_tests/clj_yaml/core_test.clj
Normal file
203
test-resources/lib_tests/clj_yaml/core_test.clj
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
(ns clj-yaml.core-test
|
||||||
|
(:require [clojure.test :refer (deftest testing is)]
|
||||||
|
[clojure.string :as string]
|
||||||
|
[clj-yaml.core :refer [parse-string unmark generate-string]])
|
||||||
|
(:import [java.util Date]))
|
||||||
|
|
||||||
|
(def nested-hash-yaml
|
||||||
|
"root:\n childa: a\n childb: \n grandchild: \n greatgrandchild: bar\n")
|
||||||
|
|
||||||
|
(def list-yaml
|
||||||
|
"--- # Favorite Movies\n- Casablanca\n- North by Northwest\n- The Man Who Wasn't There")
|
||||||
|
|
||||||
|
(def hashes-lists-yaml "
|
||||||
|
items:
|
||||||
|
- part_no: A4786
|
||||||
|
descrip: Water Bucket (Filled)
|
||||||
|
price: 1.47
|
||||||
|
quantity: 4
|
||||||
|
|
||||||
|
- part_no: E1628
|
||||||
|
descrip: High Heeled \"Ruby\" Slippers
|
||||||
|
price: 100.27
|
||||||
|
quantity: 1
|
||||||
|
owners:
|
||||||
|
- Dorthy
|
||||||
|
- Wicked Witch of the East
|
||||||
|
")
|
||||||
|
|
||||||
|
(def inline-list-yaml
|
||||||
|
"--- # Shopping list
|
||||||
|
[milk, pumpkin pie, eggs, juice]
|
||||||
|
")
|
||||||
|
|
||||||
|
(def inline-hash-yaml
|
||||||
|
"{name: John Smith, age: 33}")
|
||||||
|
|
||||||
|
(def list-of-hashes-yaml "
|
||||||
|
- {name: John Smith, age: 33}
|
||||||
|
- name: Mary Smith
|
||||||
|
age: 27
|
||||||
|
")
|
||||||
|
|
||||||
|
(def hashes-of-lists-yaml "
|
||||||
|
men: [John Smith, Bill Jones]
|
||||||
|
women:
|
||||||
|
- Mary Smith
|
||||||
|
- Susan Williams
|
||||||
|
")
|
||||||
|
|
||||||
|
(def typed-data-yaml "
|
||||||
|
the-bin: !!binary 0101")
|
||||||
|
|
||||||
|
(def io-file-typed-data-yaml "
|
||||||
|
!!java.io.File")
|
||||||
|
|
||||||
|
(def set-yaml "
|
||||||
|
--- !!set
|
||||||
|
? Mark McGwire
|
||||||
|
? Sammy Sosa
|
||||||
|
? Ken Griff")
|
||||||
|
|
||||||
|
(deftest parse-hash
|
||||||
|
(let [parsed (parse-string "foo: bar")]
|
||||||
|
(is (= "bar" (parsed :foo)))))
|
||||||
|
|
||||||
|
(deftest parse-hash-with-numeric-key
|
||||||
|
(let [parsed (parse-string "123: 456")]
|
||||||
|
(is (= 456 (parsed 123)))))
|
||||||
|
|
||||||
|
(deftest parse-hash-with-complex-key
|
||||||
|
(let [parsed (parse-string "[1, 2]: 3")]
|
||||||
|
(is (= 3 (parsed [1, 2])))))
|
||||||
|
|
||||||
|
(deftest parse-nested-hash
|
||||||
|
(let [parsed (parse-string nested-hash-yaml)]
|
||||||
|
(is (= "a" ((parsed :root) :childa)))
|
||||||
|
(is (= "bar" ((((parsed :root) :childb) :grandchild) :greatgrandchild)))))
|
||||||
|
|
||||||
|
(deftest parse-list
|
||||||
|
(let [parsed (parse-string list-yaml)]
|
||||||
|
(is (= "Casablanca" (first parsed)))
|
||||||
|
(is (= "North by Northwest" (nth parsed 1)))
|
||||||
|
(is (= "The Man Who Wasn't There" (nth parsed 2)))))
|
||||||
|
|
||||||
|
(deftest parse-nested-hash-and-list
|
||||||
|
(let [parsed (parse-string hashes-lists-yaml)]
|
||||||
|
(is (= "A4786" ((first (parsed :items)) :part_no)))
|
||||||
|
(is (= "Dorthy" (first ((nth (parsed :items) 1) :owners))))))
|
||||||
|
|
||||||
|
(deftest parse-inline-list
|
||||||
|
(let [parsed (parse-string inline-list-yaml)]
|
||||||
|
(is (= "milk" (first parsed)))
|
||||||
|
(is (= "pumpkin pie" (nth parsed 1)))
|
||||||
|
(is (= "eggs" (nth parsed 2)))
|
||||||
|
(is (= "juice" (last parsed)))))
|
||||||
|
|
||||||
|
(deftest parse-inline-hash
|
||||||
|
(let [parsed (parse-string inline-hash-yaml)]
|
||||||
|
(is (= "John Smith" (parsed :name)))
|
||||||
|
(is (= 33 (parsed :age)))))
|
||||||
|
|
||||||
|
(deftest parse-list-of-hashes
|
||||||
|
(let [parsed (parse-string list-of-hashes-yaml)]
|
||||||
|
(is (= "John Smith" ((first parsed) :name)))
|
||||||
|
(is (= 33 ((first parsed) :age)))
|
||||||
|
(is (= "Mary Smith" ((nth parsed 1) :name)))
|
||||||
|
(is (= 27 ((nth parsed 1) :age)))))
|
||||||
|
|
||||||
|
(deftest hashes-of-lists
|
||||||
|
(let [parsed (parse-string hashes-of-lists-yaml)]
|
||||||
|
(is (= "John Smith" (first (parsed :men))))
|
||||||
|
(is (= "Bill Jones" (last (parsed :men))))
|
||||||
|
(is (= "Mary Smith" (first (parsed :women))))
|
||||||
|
(is (= "Susan Williams" (last (parsed :women))))))
|
||||||
|
|
||||||
|
(deftest h-set
|
||||||
|
(is (= #{"Mark McGwire" "Ken Griff" "Sammy Sosa"}
|
||||||
|
(parse-string set-yaml))))
|
||||||
|
|
||||||
|
(deftest typed-data
|
||||||
|
(let [parsed (parse-string typed-data-yaml)]
|
||||||
|
(is (= (Class/forName "[B") (type (:the-bin parsed))))))
|
||||||
|
|
||||||
|
(deftest disallow-arbitrary-typed-data
|
||||||
|
(is (thrown? org.yaml.snakeyaml.error.YAMLException
|
||||||
|
(parse-string io-file-typed-data-yaml))))
|
||||||
|
|
||||||
|
(deftest keywordized
|
||||||
|
(is (= "items"
|
||||||
|
(-> hashes-lists-yaml
|
||||||
|
(parse-string :keywords false)
|
||||||
|
ffirst))))
|
||||||
|
|
||||||
|
(deftest not-keywordized-in-lists
|
||||||
|
(is (every? string?
|
||||||
|
(-> "[{b: c, c: d}]"
|
||||||
|
(parse-string :keywords false)
|
||||||
|
first
|
||||||
|
keys))))
|
||||||
|
|
||||||
|
(deftest marking-source-position-works
|
||||||
|
(let [parsed (parse-string inline-list-yaml :mark true)]
|
||||||
|
;; The list starts at the beginning of line 1.
|
||||||
|
(is (= 1 (-> parsed :start :line)))
|
||||||
|
(is (= 0 (-> parsed :start :column)))
|
||||||
|
;; The first item starts at the second character of line 1.
|
||||||
|
(is (= 1 (-> parsed unmark first :start :line)))
|
||||||
|
(is (= 1 (-> parsed unmark first :start :column)))
|
||||||
|
;; The first item ends at the fifth character of line 1.
|
||||||
|
(is (= 1 (-> parsed unmark first :end :line)))
|
||||||
|
(is (= 5 (-> parsed unmark first :end :column)))))
|
||||||
|
|
||||||
|
(deftest text-wrapping
|
||||||
|
(let [data
|
||||||
|
{:description
|
||||||
|
"Big-picture diagram showing how our top-level systems and stakeholders interact"}]
|
||||||
|
(testing "long lines of text should not be wrapped"
|
||||||
|
;; clj-yaml 0.5.6 used SnakeYAML 1.13 which by default did *not* split long lines.
|
||||||
|
;; clj-yaml 0.6.0 upgraded to SnakeYAML 1.23 which by default *did* split long lines.
|
||||||
|
;; This test ensures that generate-string uses the older behavior by default, for the sake
|
||||||
|
;; of stability, i.e. backwards compatibility.
|
||||||
|
(is
|
||||||
|
(= "{description: Big-picture diagram showing how our top-level systems and stakeholders interact}\n"
|
||||||
|
(generate-string data))))))
|
||||||
|
|
||||||
|
(deftest dump-opts
|
||||||
|
(let [data [{:age 33 :name "jon"} {:age 44 :name "boo"}]]
|
||||||
|
(is (= "- age: 33\n name: jon\n- age: 44\n name: boo\n"
|
||||||
|
(generate-string data :dumper-options {:flow-style :block})))
|
||||||
|
(is (= "[{age: 33, name: jon}, {age: 44, name: boo}]\n"
|
||||||
|
(generate-string data :dumper-options {:flow-style :flow})))))
|
||||||
|
|
||||||
|
;; TODO: this test is failing in GraalVM
|
||||||
|
;; Could be related to https://github.com/oracle/graal/issues/2234
|
||||||
|
#_(deftest parse-time
|
||||||
|
(testing "clj-time parses timestamps with more than millisecond precision correctly."
|
||||||
|
(let [timestamp "2001-11-23 15:02:31.123456 -04:00"
|
||||||
|
expected 1006542151123]
|
||||||
|
(is (= (.getTime ^Date (parse-string timestamp)) expected)))))
|
||||||
|
|
||||||
|
(deftest maps-are-ordered
|
||||||
|
(let [parsed (parse-string hashes-lists-yaml)
|
||||||
|
[first second] (:items parsed)]
|
||||||
|
(is (= (keys first) '(:part_no :descrip :price :quantity)))
|
||||||
|
(is (= (keys second)'(:part_no :descrip :price :quantity :owners)))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest nulls-are-fine
|
||||||
|
(testing "nil does not blow up"
|
||||||
|
(let [res (parse-string "- f:")]
|
||||||
|
(is (= [{:f nil}] res))
|
||||||
|
(is (str res)))))
|
||||||
|
|
||||||
|
(deftest emoji-can-be-parsed
|
||||||
|
(let [yaml "{emoji: 💣}"]
|
||||||
|
(is (= yaml (-> yaml
|
||||||
|
(generate-string)
|
||||||
|
(parse-string)
|
||||||
|
(string/trim)))))
|
||||||
|
|
||||||
|
(testing "emoji in comments are OK too"
|
||||||
|
(let [yaml "# 💣 emoji in a comment\n42"]
|
||||||
|
(is (= 42 (parse-string yaml))))))
|
||||||
79
test-resources/lib_tests/clojure/data/csv_test.clj
Normal file
79
test-resources/lib_tests/clojure/data/csv_test.clj
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
(ns clojure.data.csv-test
|
||||||
|
(:use
|
||||||
|
[clojure.test :only (deftest is)]
|
||||||
|
[clojure.data.csv :only (read-csv write-csv)])
|
||||||
|
(:import
|
||||||
|
[java.io Reader StringReader StringWriter EOFException]))
|
||||||
|
|
||||||
|
(def ^{:private true} simple
|
||||||
|
"Year,Make,Model
|
||||||
|
1997,Ford,E350
|
||||||
|
2000,Mercury,Cougar
|
||||||
|
")
|
||||||
|
|
||||||
|
(def ^{:private true} simple-alt-sep
|
||||||
|
"Year;Make;Model
|
||||||
|
1997;Ford;E350
|
||||||
|
2000;Mercury;Cougar
|
||||||
|
")
|
||||||
|
|
||||||
|
(def ^{:private true} complicated
|
||||||
|
"1997,Ford,E350,\"ac, abs, moon\",3000.00
|
||||||
|
1999,Chevy,\"Venture \"\"Extended Edition\"\"\",\"\",4900.00
|
||||||
|
1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",\"\",5000.00
|
||||||
|
1996,Jeep,Grand Cherokee,\"MUST SELL!
|
||||||
|
air, moon roof, loaded\",4799.00")
|
||||||
|
|
||||||
|
(deftest reading
|
||||||
|
(let [csv (read-csv simple)]
|
||||||
|
(is (= (count csv) 3))
|
||||||
|
(is (= (count (first csv)) 3))
|
||||||
|
(is (= (first csv) ["Year" "Make" "Model"]))
|
||||||
|
(is (= (last csv) ["2000" "Mercury" "Cougar"])))
|
||||||
|
(let [csv (read-csv simple-alt-sep :separator \;)]
|
||||||
|
(is (= (count csv) 3))
|
||||||
|
(is (= (count (first csv)) 3))
|
||||||
|
(is (= (first csv) ["Year" "Make" "Model"]))
|
||||||
|
(is (= (last csv) ["2000" "Mercury" "Cougar"])))
|
||||||
|
(let [csv (read-csv complicated)]
|
||||||
|
(is (= (count csv) 4))
|
||||||
|
(is (= (count (first csv)) 5))
|
||||||
|
(is (= (first csv)
|
||||||
|
["1997" "Ford" "E350" "ac, abs, moon" "3000.00"]))
|
||||||
|
(is (= (last csv)
|
||||||
|
["1996" "Jeep" "Grand Cherokee", "MUST SELL!\nair, moon roof, loaded" "4799.00"]))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest reading-and-writing
|
||||||
|
(let [string-writer (StringWriter.)]
|
||||||
|
(->> simple read-csv (write-csv string-writer))
|
||||||
|
(is (= simple
|
||||||
|
(str string-writer)))))
|
||||||
|
|
||||||
|
(deftest throw-if-quoted-on-eof
|
||||||
|
(let [s "ab,\"de,gh\nij,kl,mn"]
|
||||||
|
(try
|
||||||
|
(doall (read-csv s))
|
||||||
|
(is false "No exception thrown")
|
||||||
|
(catch Exception e
|
||||||
|
(is (or (instance? java.io.EOFException e)
|
||||||
|
(and (instance? RuntimeException e)
|
||||||
|
(instance? java.io.EOFException (.getCause e)))))))))
|
||||||
|
|
||||||
|
(deftest parse-line-endings
|
||||||
|
(let [csv (read-csv "Year,Make,Model\n1997,Ford,E350")]
|
||||||
|
(is (= 2 (count csv)))
|
||||||
|
(is (= ["Year" "Make" "Model"] (first csv)))
|
||||||
|
(is (= ["1997" "Ford" "E350"] (second csv))))
|
||||||
|
(let [csv (read-csv "Year,Make,Model\r\n1997,Ford,E350")]
|
||||||
|
(is (= 2 (count csv)))
|
||||||
|
(is (= ["Year" "Make" "Model"] (first csv)))
|
||||||
|
(is (= ["1997" "Ford" "E350"] (second csv))))
|
||||||
|
(let [csv (read-csv "Year,Make,Model\r1997,Ford,E350")]
|
||||||
|
(is (= 2 (count csv)))
|
||||||
|
(is (= ["Year" "Make" "Model"] (first csv)))
|
||||||
|
(is (= ["1997" "Ford" "E350"] (second csv))))
|
||||||
|
(let [csv (read-csv "Year,Make,\"Model\"\r1997,Ford,E350")]
|
||||||
|
(is (= 2 (count csv)))
|
||||||
|
(is (= ["Year" "Make" "Model"] (first csv)))
|
||||||
|
(is (= ["1997" "Ford" "E350"] (second csv)))))
|
||||||
35
test/babashka/async_test.clj
Normal file
35
test/babashka/async_test.clj
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
(ns babashka.async-test
|
||||||
|
(:require
|
||||||
|
[babashka.test-utils :as test-utils]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
|
(deftest alts!!-test
|
||||||
|
(is (= "process 2\n" (test-utils/bb nil "
|
||||||
|
(defn async-command [& args]
|
||||||
|
(async/thread (apply shell/sh \"bash\" \"-c\" args)))
|
||||||
|
|
||||||
|
(-> (async/alts!! [(async-command \"sleep 2 && echo process 1\")
|
||||||
|
(async-command \"sleep 1 && echo process 2\")])
|
||||||
|
first :out str/trim println)"))))
|
||||||
|
|
||||||
|
(deftest go-test
|
||||||
|
(is (number? (edn/read-string (test-utils/bb nil "
|
||||||
|
(defn calculation-go []
|
||||||
|
(async/go
|
||||||
|
;; wait for some stuff
|
||||||
|
(rand-int 1000)))
|
||||||
|
|
||||||
|
(defn get-result-go []
|
||||||
|
(async/go
|
||||||
|
(->>
|
||||||
|
(repeatedly 10 calculation-go)
|
||||||
|
(map async/<!)
|
||||||
|
(reduce +))))
|
||||||
|
|
||||||
|
(async/<!! (get-result-go))")))))
|
||||||
|
|
||||||
|
(deftest binding-conveyance-test
|
||||||
|
(is (number? (edn/read-string (test-utils/bb nil "
|
||||||
|
(def ^:dynamic x 0)
|
||||||
|
(binding [x 10] (async/<!! (async/thread x)))")))))
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
[babashka.test-utils :as tu]
|
[babashka.test-utils :as tu]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.test :as t :refer [deftest is]]))
|
[clojure.test :as t :refer [deftest is testing]]
|
||||||
|
[clojure.string :as str]))
|
||||||
|
|
||||||
(defn bb [input & args]
|
(defn bb [input & args]
|
||||||
(edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
|
(edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
|
||||||
|
|
@ -28,7 +29,10 @@
|
||||||
|
|
||||||
(deftest main-test
|
(deftest main-test
|
||||||
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
|
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
|
||||||
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4"))))
|
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4")))
|
||||||
|
(testing "system property"
|
||||||
|
(is (= "\"my.main2\""
|
||||||
|
(str/trim (tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main2"))))))
|
||||||
|
|
||||||
(deftest uberscript-test
|
(deftest uberscript-test
|
||||||
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
|
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
(ns babashka.file-var-test
|
(ns babashka.file-var-test
|
||||||
(:require
|
(:require
|
||||||
[babashka.test-utils :as tu]
|
[babashka.test-utils :as tu]
|
||||||
[clojure.test :as t :refer [deftest is]]
|
[clojure.string :as str]
|
||||||
[clojure.string :as str]))
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
(defn bb [input & args]
|
(defn bb [input & args]
|
||||||
(apply tu/bb (when (some? input) (str input)) (map str args)))
|
(apply tu/bb (when (some? input) (str input)) (map str args)))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
(ns babashka.http-connection-test
|
(ns babashka.http-connection-test
|
||||||
(:require
|
(:require
|
||||||
[babashka.test-utils :as tu]
|
[babashka.test-utils :as tu]
|
||||||
[clojure.test :as t :refer [deftest is]]
|
[clojure.string :as str]
|
||||||
[clojure.string :as str]))
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
(defn bb [& args]
|
(defn bb [& args]
|
||||||
(apply tu/bb nil (map str args)))
|
(apply tu/bb nil (map str args)))
|
||||||
|
|
||||||
(deftest open-connection-test
|
(deftest open-connection-test
|
||||||
(is (= "\"1\"" (str/trim (bb "-e" "
|
(is (try (= "\"1\"" (str/trim (bb "-e" "
|
||||||
(require '[cheshire.core :as json])
|
(require '[cheshire.core :as json])
|
||||||
(let [conn ^java.net.HttpURLConnection (.openConnection (java.net.URL. \"https://postman-echo.com/get?foo=1\"))]
|
(let [conn ^java.net.HttpURLConnection (.openConnection (java.net.URL. \"https://postman-echo.com/get?foo=1\"))]
|
||||||
(.setConnectTimeout conn 1000)
|
(.setConnectTimeout conn 1000)
|
||||||
|
|
@ -18,4 +18,6 @@
|
||||||
err (.getErrorStream conn)
|
err (.getErrorStream conn)
|
||||||
response (json/decode (slurp is) true)]
|
response (json/decode (slurp is) true)]
|
||||||
(-> response :args :foo)))
|
(-> response :args :foo)))
|
||||||
")))))
|
")))
|
||||||
|
(catch Exception e
|
||||||
|
(str/includes? (str e) "timed out")))))
|
||||||
|
|
|
||||||
203
test/babashka/impl/nrepl_server_test.clj
Normal file
203
test/babashka/impl/nrepl_server_test.clj
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
(ns babashka.impl.nrepl-server-test
|
||||||
|
(:require
|
||||||
|
[babashka.impl.bencode.core :as bencode]
|
||||||
|
[babashka.impl.nrepl-server :refer [start-server! stop-server!]]
|
||||||
|
[babashka.main :as main]
|
||||||
|
[babashka.test-utils :as tu]
|
||||||
|
[babashka.wait :as wait]
|
||||||
|
[clojure.test :as t :refer [deftest is testing]]
|
||||||
|
[sci.impl.opts :refer [init]])
|
||||||
|
(:import [java.lang ProcessBuilder$Redirect]))
|
||||||
|
|
||||||
|
(def debug? false)
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(defn bytes->str [x]
|
||||||
|
(if (bytes? x) (String. (bytes x))
|
||||||
|
(str x)))
|
||||||
|
|
||||||
|
(defn read-msg [msg]
|
||||||
|
(let [res (zipmap (map keyword (keys msg))
|
||||||
|
(map #(if (bytes? %)
|
||||||
|
(String. (bytes %))
|
||||||
|
%)
|
||||||
|
(vals msg)))
|
||||||
|
res (if-let [status (:status res)]
|
||||||
|
(assoc res :status (mapv bytes->str status))
|
||||||
|
res)
|
||||||
|
res (if-let [status (:sessions res)]
|
||||||
|
(assoc res :sessions (mapv bytes->str status))
|
||||||
|
res)]
|
||||||
|
res))
|
||||||
|
|
||||||
|
(defn read-reply [in session id]
|
||||||
|
(loop []
|
||||||
|
(let [msg (read-msg (bencode/read-bencode in))]
|
||||||
|
(if (and (= (:session msg) session)
|
||||||
|
(= (:id msg) id))
|
||||||
|
(do
|
||||||
|
(when debug? (prn "received" msg))
|
||||||
|
msg)
|
||||||
|
(do
|
||||||
|
(when debug? (prn "skipping over msg" msg))
|
||||||
|
(recur))))))
|
||||||
|
|
||||||
|
(defn nrepl-test []
|
||||||
|
(with-open [socket (java.net.Socket. "127.0.0.1" 1667)
|
||||||
|
in (.getInputStream socket)
|
||||||
|
in (java.io.PushbackInputStream. in)
|
||||||
|
os (.getOutputStream socket)]
|
||||||
|
(bencode/write-bencode os {"op" "clone"})
|
||||||
|
(let [session (:new-session (read-msg (bencode/read-bencode in)))
|
||||||
|
id (atom 0)
|
||||||
|
new-id! #(swap! id inc)]
|
||||||
|
(testing "session"
|
||||||
|
(is session))
|
||||||
|
(testing "eval"
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(+ 1 2 3)" "session" session "id" (new-id!)})
|
||||||
|
(let [msg (read-reply in session @id)
|
||||||
|
id (:id msg)
|
||||||
|
value (:value msg)]
|
||||||
|
(is (= 1 id))
|
||||||
|
(is (= value "6")))
|
||||||
|
(testing "creating a namespace and evaluating something in it"
|
||||||
|
(bencode/write-bencode os {"op" "eval"
|
||||||
|
"code" "(ns ns0) (defn foo [] :foo0) (ns ns1) (defn foo [] :foo1)"
|
||||||
|
"session" session
|
||||||
|
"id" (new-id!)})
|
||||||
|
(read-reply in session @id)
|
||||||
|
(testing "not providing the ns key evaluates in the last defined namespace"
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(foo)" "session" session "id" (new-id!)})
|
||||||
|
(is (= ":foo1" (:value (read-reply in session @id)))))
|
||||||
|
(testing "explicitly providing the ns key evaluates in that namespace"
|
||||||
|
(bencode/write-bencode os {"op" "eval"
|
||||||
|
"code" "(foo)"
|
||||||
|
"session" session
|
||||||
|
"id" (new-id!)
|
||||||
|
"ns" "ns0"})
|
||||||
|
(is (= ":foo0" (:value (read-reply in session @id)))))
|
||||||
|
(testing "providing an ns value of a non-existing namespace creates the namespace"
|
||||||
|
(bencode/write-bencode os {"op" "eval"
|
||||||
|
"code" "(ns-name *ns*)"
|
||||||
|
"session" session
|
||||||
|
"id" (new-id!)
|
||||||
|
"ns" "unicorn"})
|
||||||
|
(let [reply (read-reply in session @id)]
|
||||||
|
(is (= "unicorn" (:value reply))))))
|
||||||
|
(testing "multiple top level expressions results in two value replies"
|
||||||
|
(bencode/write-bencode os {"op" "eval"
|
||||||
|
"code" "(+ 1 2 3) (+ 1 2 3)"
|
||||||
|
"session" session
|
||||||
|
"id" (new-id!)})
|
||||||
|
(let [reply-1 (read-reply in session @id)
|
||||||
|
reply-2 (read-reply in session @id)]
|
||||||
|
(is (= "6" (:value reply-1) (:value reply-2))))))
|
||||||
|
(testing "load-file"
|
||||||
|
(bencode/write-bencode os {"op" "load-file" "file" "(ns foo) (defn foo [] :foo)" "session" session "id" (new-id!)})
|
||||||
|
(read-reply in session @id)
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(foo)" "ns" "foo" "session" session "id" (new-id!)})
|
||||||
|
(is (= ":foo" (:value (read-reply in session @id)))))
|
||||||
|
(testing "complete"
|
||||||
|
(testing "completions for fo"
|
||||||
|
(bencode/write-bencode os {"op" "complete"
|
||||||
|
"symbol" "fo"
|
||||||
|
"session" session
|
||||||
|
"id" (new-id!)
|
||||||
|
"ns" "foo"})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
completions (:completions reply)
|
||||||
|
completions (mapv read-msg completions)
|
||||||
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
||||||
|
(is (contains? completions ["foo" "foo"]))
|
||||||
|
(is (contains? completions ["clojure.core" "format"]))))
|
||||||
|
(testing "completions for quux should be empty"
|
||||||
|
(bencode/write-bencode os {"op" "complete"
|
||||||
|
"symbol" "quux"
|
||||||
|
"session" session "id" (new-id!)
|
||||||
|
"ns" "foo"})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
completions (:completions reply)]
|
||||||
|
(is (empty? completions)))
|
||||||
|
(testing "unless quux is an alias"
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(require '[cheshire.core :as quux])" "session" session "id" (new-id!)})
|
||||||
|
(read-reply in session @id)
|
||||||
|
(bencode/write-bencode os {"op" "complete" "symbol" "quux" "session" session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
completions (:completions reply)
|
||||||
|
completions (mapv read-msg completions)
|
||||||
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
||||||
|
(is (contains? completions ["cheshire.core" "quux/generate-string"])))))
|
||||||
|
(testing "completions for clojure.test"
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(require '[clojure.test :as test])" "session" session "id" (new-id!)})
|
||||||
|
(read-reply in session @id)
|
||||||
|
(bencode/write-bencode os {"op" "complete" "symbol" "test" "session" session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
completions (:completions reply)
|
||||||
|
completions (mapv read-msg completions)
|
||||||
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
||||||
|
(is (contains? completions ["clojure.test" "test/deftest"])))))
|
||||||
|
(testing "close + ls-sessions"
|
||||||
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
sessions (set (:sessions reply))]
|
||||||
|
(is (contains? sessions session))
|
||||||
|
(let [new-sessions (loop [i 0
|
||||||
|
sessions #{}]
|
||||||
|
(bencode/write-bencode os {"op" "clone" "session" session "id" (new-id!)})
|
||||||
|
(let [new-session (:new-session (read-reply in session @id))
|
||||||
|
sessions (conj sessions new-session)]
|
||||||
|
(if (= i 4)
|
||||||
|
sessions
|
||||||
|
(recur (inc i) sessions))))]
|
||||||
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
sessions (set (:sessions reply))]
|
||||||
|
(is (= 6 (count sessions)))
|
||||||
|
(is (contains? sessions session))
|
||||||
|
(is (= new-sessions (disj sessions session)))
|
||||||
|
(testing "close"
|
||||||
|
(doseq [close-session (disj sessions session)]
|
||||||
|
(bencode/write-bencode os {"op" "close" "session" close-session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in close-session @id)]
|
||||||
|
(is (contains? (set (:status reply)) "session-closed")))))
|
||||||
|
(testing "session not listen in ls-sessions after close"
|
||||||
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
||||||
|
(let [reply (read-reply in session @id)
|
||||||
|
sessions (set (:sessions reply))]
|
||||||
|
(is (contains? sessions session))
|
||||||
|
(is (not (some #(contains? sessions %) new-sessions)))))))))
|
||||||
|
(testing "output"
|
||||||
|
(bencode/write-bencode os {"op" "eval" "code" "(dotimes [i 3] (println \"Hello\"))"
|
||||||
|
"session" session "id" (new-id!)})
|
||||||
|
(dotimes [_ 3]
|
||||||
|
(let [reply (read-reply in session @id)]
|
||||||
|
(is (= "Hello\n" (:out reply)))))))))
|
||||||
|
|
||||||
|
(deftest nrepl-server-test
|
||||||
|
(let [proc-state (atom nil)]
|
||||||
|
(try
|
||||||
|
(if tu/jvm?
|
||||||
|
(future
|
||||||
|
(start-server!
|
||||||
|
(init {:namespaces main/namespaces
|
||||||
|
:features #{:bb}}) "0.0.0.0:1667"))
|
||||||
|
(let [pb (ProcessBuilder. ["./bb" "--nrepl-server" "0.0.0.0:1667"])
|
||||||
|
_ (.redirectError pb ProcessBuilder$Redirect/INHERIT)
|
||||||
|
;; _ (.redirectOutput pb ProcessBuilder$Redirect/INHERIT)
|
||||||
|
;; env (.environment pb)
|
||||||
|
;; _ (.put env "BABASHKA_DEV" "true")
|
||||||
|
proc (.start pb)]
|
||||||
|
(reset! proc-state proc)))
|
||||||
|
(babashka.wait/wait-for-port "localhost" 1667)
|
||||||
|
(nrepl-test)
|
||||||
|
(finally
|
||||||
|
(if tu/jvm?
|
||||||
|
(stop-server!)
|
||||||
|
(when-let [proc @proc-state]
|
||||||
|
(.destroy ^Process proc)))))))
|
||||||
|
|
||||||
|
;;;; Scratch
|
||||||
|
|
||||||
|
(comment
|
||||||
|
)
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
[babashka.impl.repl :refer [start-repl!]]
|
[babashka.impl.repl :refer [start-repl!]]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.test :as t :refer [deftest is]]
|
[clojure.test :as t :refer [deftest is]]
|
||||||
[sci.impl.opts :refer [init]]
|
|
||||||
[sci.core :as sci]
|
[sci.core :as sci]
|
||||||
|
[sci.impl.opts :refer [init]]
|
||||||
[sci.impl.vars :as vars]))
|
[sci.impl.vars :as vars]))
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
@ -24,6 +24,15 @@
|
||||||
(sci/with-in-str (str expr "\n:repl/quit")
|
(sci/with-in-str (str expr "\n:repl/quit")
|
||||||
(repl!))) expected)))
|
(repl!))) expected)))
|
||||||
|
|
||||||
|
(defn assert-repl-error [expr expected]
|
||||||
|
(is (str/includes?
|
||||||
|
(let [sw (java.io.StringWriter.)]
|
||||||
|
(sci/binding [sci/out (java.io.StringWriter.)
|
||||||
|
sci/err sw]
|
||||||
|
(sci/with-in-str (str expr "\n:repl/quit")
|
||||||
|
(repl!)))
|
||||||
|
(str sw)) expected)))
|
||||||
|
|
||||||
(deftest repl-test
|
(deftest repl-test
|
||||||
(assert-repl "1" "1")
|
(assert-repl "1" "1")
|
||||||
(assert-repl "[1 2 3]" "[1 2 3]")
|
(assert-repl "[1 2 3]" "[1 2 3]")
|
||||||
|
|
@ -34,7 +43,8 @@
|
||||||
(assert-repl "1\n(inc *1)" "2")
|
(assert-repl "1\n(inc *1)" "2")
|
||||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||||
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]"))
|
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]")
|
||||||
|
(assert-repl-error "(+ 1 nil)" "NullPointerException"))
|
||||||
|
|
||||||
;;;; Scratch
|
;;;; Scratch
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
(:require
|
(:require
|
||||||
[babashka.impl.socket-repl :refer [start-repl! stop-repl!]]
|
[babashka.impl.socket-repl :refer [start-repl! stop-repl!]]
|
||||||
[babashka.test-utils :as tu]
|
[babashka.test-utils :as tu]
|
||||||
|
[clojure.java.io :as io]
|
||||||
[clojure.java.shell :refer [sh]]
|
[clojure.java.shell :refer [sh]]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.test :as t :refer [deftest is testing]]
|
[clojure.test :as t :refer [deftest is testing]]
|
||||||
[clojure.java.io :as io]
|
|
||||||
[sci.impl.opts :refer [init]]))
|
[sci.impl.opts :refer [init]]))
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,8 @@
|
||||||
(is (= "18-12-2019 16:01:41"
|
(is (= "18-12-2019 16:01:41"
|
||||||
(bb '(.format
|
(bb '(.format
|
||||||
(java.time.LocalDateTime/parse "2019-12-18T16:01:41.485")
|
(java.time.LocalDateTime/parse "2019-12-18T16:01:41.485")
|
||||||
(java.time.format.DateTimeFormatter/ofPattern "dd-MM-yyyy HH:mm:ss"))))))
|
(java.time.format.DateTimeFormatter/ofPattern "dd-MM-yyyy HH:mm:ss")))))
|
||||||
|
(is (number? (bb "
|
||||||
|
(let [x (java.time.LocalDateTime/parse \"2019-12-18T16:01:41.485\")
|
||||||
|
y (java.time.LocalDateTime/now)]
|
||||||
|
(.between java.time.temporal.ChronoUnit/MINUTES x y))"))))
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
[clojure.test :as test :refer [deftest is testing]]
|
[clojure.test :as test :refer [deftest is testing]]
|
||||||
[sci.core :as sci]))
|
[sci.core :as sci]))
|
||||||
|
|
||||||
|
(defmethod clojure.test/report :begin-test-var [m]
|
||||||
|
(println "===" (-> m :var meta :name))
|
||||||
|
(println))
|
||||||
|
|
||||||
(defn bb [input & args]
|
(defn bb [input & args]
|
||||||
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
|
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
|
||||||
|
|
||||||
|
|
@ -29,7 +33,7 @@
|
||||||
|
|
||||||
(deftest main-test
|
(deftest main-test
|
||||||
(testing "-io behaves as identity"
|
(testing "-io behaves as identity"
|
||||||
(= "foo\nbar\n" (test-utils/bb "foo\nbar\n" "-io" "*input*")))
|
(is (= "foo\nbar\n" (test-utils/bb "foo\nbar\n" "-io" "*input*"))))
|
||||||
(testing "if and when"
|
(testing "if and when"
|
||||||
(is (= 1 (bb 0 '(if (zero? *input*) 1 2))))
|
(is (= 1 (bb 0 '(if (zero? *input*) 1 2))))
|
||||||
(is (= 2 (bb 1 '(if (zero? *input*) 1 2))))
|
(is (= 2 (bb 1 '(if (zero? *input*) 1 2))))
|
||||||
|
|
@ -118,9 +122,39 @@
|
||||||
|
|
||||||
(deftest load-file-test
|
(deftest load-file-test
|
||||||
(let [tmp (java.io.File/createTempFile "script" ".clj")]
|
(let [tmp (java.io.File/createTempFile "script" ".clj")]
|
||||||
(spit tmp "(defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
|
(.deleteOnExit tmp)
|
||||||
(is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (bar (foo 10 30) 3)"
|
(spit tmp "(ns foo) (defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
|
||||||
(.getPath tmp)))))))
|
(is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (foo/bar (foo/foo 10 30) 3)"
|
||||||
|
(.getPath tmp)))))
|
||||||
|
(testing "namespace is restored after load file"
|
||||||
|
(is (= 'start-ns
|
||||||
|
(bb nil (format "(ns start-ns) (load-file \"%s\") (ns-name *ns*)"
|
||||||
|
(.getPath tmp))))))))
|
||||||
|
|
||||||
|
(deftest repl-source-test
|
||||||
|
(let [tmp (java.io.File/createTempFile "lib" ".clj")
|
||||||
|
name (str/replace (.getName tmp) ".clj" "")
|
||||||
|
dir (.getParent tmp)]
|
||||||
|
(.deleteOnExit tmp)
|
||||||
|
(testing "print source from loaded file"
|
||||||
|
(spit tmp (format "
|
||||||
|
(ns %s)
|
||||||
|
|
||||||
|
(defn foo [x y]
|
||||||
|
(+ x y))" name))
|
||||||
|
(is (= "(defn foo [x y]\n (+ x y))\n"
|
||||||
|
(bb nil (format "
|
||||||
|
(load-file \"%s\")
|
||||||
|
(require '[clojure.repl :refer [source]])
|
||||||
|
(with-out-str (source %s/foo))"
|
||||||
|
(.getPath tmp)
|
||||||
|
name)))))
|
||||||
|
(testing "print source from file on classpath"
|
||||||
|
(is (= "(defn foo [x y]\n (+ x y))\n"
|
||||||
|
(bb nil
|
||||||
|
"-cp" dir
|
||||||
|
"-e" (format "(require '[clojure.repl :refer [source]] '[%s])" name)
|
||||||
|
"-e" (format "(with-out-str (source %s/foo))" name)))))))
|
||||||
|
|
||||||
(deftest eval-test
|
(deftest eval-test
|
||||||
(is (= "120\n" (test-utils/bb nil "(eval '(do (defn foo [x y] (+ x y))
|
(is (= "120\n" (test-utils/bb nil "(eval '(do (defn foo [x y] (+ x y))
|
||||||
|
|
@ -220,23 +254,26 @@
|
||||||
{:default :timed-out :timeout 100}))"
|
{:default :timed-out :timeout 100}))"
|
||||||
temp-dir-path))))))
|
temp-dir-path))))))
|
||||||
|
|
||||||
(deftest async-test
|
|
||||||
(is (= "process 2\n" (test-utils/bb nil "
|
|
||||||
(defn async-command [& args]
|
|
||||||
(async/thread (apply shell/sh \"bash\" \"-c\" args)))
|
|
||||||
|
|
||||||
(-> (async/alts!! [(async-command \"sleep 2 && echo process 1\")
|
|
||||||
(async-command \"sleep 1 && echo process 2\")])
|
|
||||||
first :out str/trim println)"))))
|
|
||||||
|
|
||||||
(deftest tools-cli-test
|
(deftest tools-cli-test
|
||||||
(is (= {:result 8080} (bb nil "test/babashka/scripts/tools.cli.bb"))))
|
(is (= {:result 8080} (bb nil "test/babashka/scripts/tools.cli.bb"))))
|
||||||
|
|
||||||
(deftest try-catch-test
|
(deftest try-catch-test
|
||||||
(is (zero? (bb nil "(try (/ 1 0) (catch ArithmeticException _ 0))"))))
|
(is (zero? (bb nil "(try (/ 1 0) (catch ArithmeticException _ 0))")))
|
||||||
|
(is (= :got-it (bb nil "
|
||||||
|
(defn foo []
|
||||||
|
(throw (java.util.MissingResourceException. \"o noe!\" \"\" \"\")))
|
||||||
|
|
||||||
|
(defn bar
|
||||||
|
[]
|
||||||
|
(try (foo)
|
||||||
|
(catch java.util.MissingResourceException _
|
||||||
|
:got-it)))
|
||||||
|
(bar)
|
||||||
|
"))))
|
||||||
|
|
||||||
(deftest reader-conditionals-test
|
(deftest reader-conditionals-test
|
||||||
(is (= :hello (bb nil "#?(:bb :hello :default :bye)")))
|
(is (= :hello (bb nil "#?(:bb :hello :default :bye)")))
|
||||||
|
(is (= :hello (bb nil "#? (:bb :hello :default :bye)")))
|
||||||
(is (= :hello (bb nil "#?(:clj :hello :bb :bye)")))
|
(is (= :hello (bb nil "#?(:clj :hello :bb :bye)")))
|
||||||
(is (= [1 2] (bb nil "[1 2 #?@(:bb [] :clj [1])]"))))
|
(is (= [1 2] (bb nil "[1 2 #?@(:bb [] :clj [1])]"))))
|
||||||
|
|
||||||
|
|
@ -355,8 +392,52 @@
|
||||||
(alter-var-root #'clojure.core/inc (constantly inc2))
|
(alter-var-root #'clojure.core/inc (constantly inc2))
|
||||||
res)")))))
|
res)")))))
|
||||||
|
|
||||||
|
(deftest pprint-test
|
||||||
|
(testing "writer"
|
||||||
|
(is (string? (bb nil "(let [sw (java.io.StringWriter.)] (clojure.pprint/pprint (range 10) sw) (str sw))")))))
|
||||||
|
|
||||||
|
(deftest read-string-test
|
||||||
|
(testing "namespaced keyword via alias"
|
||||||
|
(is (= :clojure.string/foo
|
||||||
|
(bb nil "(ns foo (:require [clojure.string :as str])) (read-string \"::str/foo\")")))))
|
||||||
|
|
||||||
|
(deftest available-stream-test
|
||||||
|
(is (= 0 (bb nil "(.available System/in)"))))
|
||||||
|
|
||||||
|
(deftest file-reader-test
|
||||||
|
(when (str/includes? (str/lower-case (System/getProperty "os.name")) "linux")
|
||||||
|
(let [v (bb nil "(slurp (io/reader (java.io.FileReader. \"/proc/loadavg\")))")]
|
||||||
|
(prn "output:" v)
|
||||||
|
(is v))))
|
||||||
|
|
||||||
|
(deftest download-and-extract-test
|
||||||
|
(is (try (= 6 (bb nil (io/file "test" "babashka" "scripts" "download_and_extract_zip.bb")))
|
||||||
|
(catch Exception e
|
||||||
|
(is (str/includes? (str e) "timed out"))))))
|
||||||
|
|
||||||
|
(deftest get-message-on-exception-info-test
|
||||||
|
(is "foo" (bb nil "(try (throw (ex-info \"foo\" {})) (catch Exception e (.getMessage e)))")))
|
||||||
|
|
||||||
|
(deftest pushback-reader-test
|
||||||
|
(is (= "foo" (bb nil "
|
||||||
|
(require '[clojure.java.io :as io])
|
||||||
|
(let [pb (java.io.PushbackInputStream. (java.io.ByteArrayInputStream. (.getBytes \"foo\")))]
|
||||||
|
(.unread pb (.read pb))
|
||||||
|
(slurp pb))"))))
|
||||||
|
|
||||||
|
(deftest delete-on-exit-test
|
||||||
|
(when test-utils/native?
|
||||||
|
(let [f (java.io.File/createTempFile "foo" "bar")
|
||||||
|
p (.getPath f)]
|
||||||
|
(bb nil (format "(.deleteOnExit (io/file \"%s\"))" p))
|
||||||
|
(is (false? (.exists f))))))
|
||||||
|
|
||||||
|
(deftest yaml-test
|
||||||
|
(is (str/starts-with?
|
||||||
|
(bb nil "(yaml/generate-string [{:name \"John Smith\", :age 33} {:name \"Mary Smith\", :age 27}])")
|
||||||
|
"-")))
|
||||||
|
|
||||||
;;;; Scratch
|
;;;; Scratch
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(dotimes [_ 10] (wait-for-port-test))
|
(dotimes [_ 10] (wait-for-port-test)))
|
||||||
)
|
|
||||||
|
|
|
||||||
30
test/babashka/scripts/download_and_extract_zip.bb
Normal file
30
test/babashka/scripts/download_and_extract_zip.bb
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
(require '[clojure.java.io :as io] '[clojure.java.shell :refer [sh]] '[clojure.string :as str])
|
||||||
|
(import '[java.net URL HttpURLConnection])
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(let [os-name (System/getProperty "os.name")
|
||||||
|
os-name (str/lower-case os-name)
|
||||||
|
os (cond (str/includes? os-name "linux") "linux"
|
||||||
|
(str/includes? os-name "mac") "macos"
|
||||||
|
(str/includes? os-name "win") "windows")
|
||||||
|
tmp-dir (System/getProperty "java.io.tmpdir")
|
||||||
|
zip-file (io/file tmp-dir "bb-0.0.78.zip")
|
||||||
|
source (URL. (format "https://github.com/borkdude/babashka/releases/download/v0.0.78/babashka-0.0.78-%s-amd64.zip" os))
|
||||||
|
conn ^HttpURLConnection (.openConnection ^URL source)
|
||||||
|
_ (.setConnectTimeout conn 2000)
|
||||||
|
_ (.setReadTimeout conn 2000)]
|
||||||
|
(.connect conn)
|
||||||
|
(with-open [is (.getInputStream conn)]
|
||||||
|
(io/copy is zip-file))
|
||||||
|
(let [bb-file (io/file tmp-dir "bb-extracted")
|
||||||
|
fs (java.nio.file.FileSystems/newFileSystem (.toPath zip-file) nil)
|
||||||
|
to-extract (.getPath fs "bb" (into-array String []))]
|
||||||
|
(java.nio.file.Files/copy to-extract (.toPath bb-file)
|
||||||
|
^"[Ljava.nio.file.CopyOption;"
|
||||||
|
(into-array java.nio.file.CopyOption []))
|
||||||
|
(.setExecutable bb-file true)
|
||||||
|
(let [out (:out (sh (.getPath bb-file) "(+ 1 2 3)"))]
|
||||||
|
(.delete bb-file)
|
||||||
|
(.delete zip-file)
|
||||||
|
(println out))))
|
||||||
6
test/babashka/scripts/interrupt_handler.bb
Normal file
6
test/babashka/scripts/interrupt_handler.bb
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
(require '[babashka.signal :as signal])
|
||||||
|
|
||||||
|
(signal/add-interrupt-handler! :quit (fn [k] (println "bye1" k)))
|
||||||
|
(signal/add-interrupt-handler! :quit2 (fn [k] (println "bye2" k)))
|
||||||
|
|
||||||
|
(System/exit 0)
|
||||||
24
test/babashka/shutdown_hook_test.clj
Normal file
24
test/babashka/shutdown_hook_test.clj
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
(ns babashka.shutdown-hook-test
|
||||||
|
{:no-doc true}
|
||||||
|
(:import [java.nio.charset Charset])
|
||||||
|
(:require [babashka.test-utils :as tu]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.test :refer [deftest is]]))
|
||||||
|
|
||||||
|
(defn- stream-to-string
|
||||||
|
([in] (stream-to-string in (.name (Charset/defaultCharset))))
|
||||||
|
([in enc]
|
||||||
|
(with-open [bout (java.io.StringWriter.)]
|
||||||
|
(io/copy in bout :encoding enc)
|
||||||
|
(.toString bout))))
|
||||||
|
|
||||||
|
(deftest shutdown-hook-test
|
||||||
|
(let [script "(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println \"bye\"))))"
|
||||||
|
pb (ProcessBuilder. (if tu/jvm?
|
||||||
|
["lein" "bb" "-e" script]
|
||||||
|
["./bb" "-e" script]))
|
||||||
|
process (.start pb)
|
||||||
|
output (.getInputStream process)]
|
||||||
|
(when-let [s (not-empty (stream-to-string (.getErrorStream process)))]
|
||||||
|
(prn "ERROR:" s))
|
||||||
|
(is (= "bye\n" (stream-to-string output)))))
|
||||||
13
test/babashka/transit_test.clj
Normal file
13
test/babashka/transit_test.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns babashka.transit-test
|
||||||
|
(:require
|
||||||
|
[babashka.test-utils :as test-utils]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
|
(defn bb [& args]
|
||||||
|
(apply test-utils/bb nil (map str args)))
|
||||||
|
|
||||||
|
(deftest transit-test
|
||||||
|
(is (= "\"foo\"\n{:a [1 2]}\n"
|
||||||
|
(bb (format "(load-file \"%s\")"
|
||||||
|
(.getPath (io/file "test-resources" "babashka" "transit.clj")))))))
|
||||||
25
test/babashka/udp_test.clj
Normal file
25
test/babashka/udp_test.clj
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
(ns babashka.udp-test
|
||||||
|
(:require [babashka.test-utils :as tu]
|
||||||
|
[clojure.test :refer [deftest is]])
|
||||||
|
(:import [java.io StringWriter]
|
||||||
|
[java.net DatagramPacket DatagramSocket]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(deftest udp-test
|
||||||
|
(let [server (DatagramSocket. 8125)
|
||||||
|
sw (StringWriter.)
|
||||||
|
fut (future
|
||||||
|
(let [buf (byte-array 1024)
|
||||||
|
packet (DatagramPacket. buf 1024)
|
||||||
|
_ (.receive server packet)
|
||||||
|
non-zero-bytes (filter #(not (zero? %)) (.getData packet))
|
||||||
|
non-zero-bytes (byte-array non-zero-bytes)]
|
||||||
|
(binding [*out* sw]
|
||||||
|
(println (String. non-zero-bytes "UTF-8")))))]
|
||||||
|
(while (not (realized? fut))
|
||||||
|
(tu/bb nil
|
||||||
|
"-e" "(load-file (io/file \"test-resources\" \"babashka\" \"statsd.clj\"))"
|
||||||
|
"-e" "(require '[statsd-client :as c])"
|
||||||
|
"-e" "(c/increment :foo)"))
|
||||||
|
(is (= ":foo:1|c\n" (str sw)))))
|
||||||
Loading…
Reference in a new issue