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:
|
||||
LEIN_ROOT: "true"
|
||||
BABASHKA_PLATFORM: linux # could be used in jar name
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
|
@ -67,9 +68,11 @@ jobs:
|
|||
working_directory: ~/repo
|
||||
environment:
|
||||
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_TEST_ENV: native
|
||||
BABASHKA_XMX: "-J-Xmx7g"
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
|
@ -99,14 +102,10 @@ jobs:
|
|||
name: Download GraalVM
|
||||
command: |
|
||||
cd ~
|
||||
if ! [ -d graalvm-ce-java8-19.3.0 ]; 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
|
||||
tar xzf graalvm-ce-java8-linux-amd64-19.3.0.tar.gz
|
||||
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: Install GraalVM SSL libs
|
||||
# command: |
|
||||
# .circleci/script/graalvm_ssl
|
||||
- run:
|
||||
name: Build binary
|
||||
command: |
|
||||
|
|
@ -117,10 +116,6 @@ jobs:
|
|||
command: |
|
||||
script/test
|
||||
script/run_lib_tests
|
||||
# - run:
|
||||
# name: Performance report
|
||||
# command: |
|
||||
# .circleci/script/performance
|
||||
- run:
|
||||
name: Release
|
||||
command: |
|
||||
|
|
@ -128,7 +123,78 @@ jobs:
|
|||
- save_cache:
|
||||
paths:
|
||||
- ~/.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" }}
|
||||
- store_artifacts:
|
||||
path: /tmp/release
|
||||
|
|
@ -141,9 +207,11 @@ jobs:
|
|||
macos:
|
||||
xcode: "9.0"
|
||||
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_TEST_ENV: native
|
||||
BABASHKA_XMX: "-J-Xmx7g"
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
|
@ -157,24 +225,20 @@ jobs:
|
|||
- run:
|
||||
name: Install Clojure
|
||||
command: |
|
||||
.circleci/script/install-clojure /usr/local
|
||||
script/install-clojure /usr/local
|
||||
- run:
|
||||
name: Install Leiningen
|
||||
command: |
|
||||
.circleci/script/install-leiningen
|
||||
script/install-leiningen
|
||||
- run:
|
||||
name: Download GraalVM
|
||||
command: |
|
||||
cd ~
|
||||
ls -la
|
||||
if ! [ -d graalvm-ce-java8-19.3.0 ]; 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
|
||||
tar xzf graalvm-ce-java8-darwin-amd64-19.3.0.tar.gz
|
||||
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
|
||||
# - run:
|
||||
# name: Install GraalVM SSL libs
|
||||
# command: |
|
||||
# .circleci/script/graalvm_ssl
|
||||
- run:
|
||||
name: Build binary
|
||||
command: |
|
||||
|
|
@ -185,10 +249,6 @@ jobs:
|
|||
command: |
|
||||
script/test
|
||||
script/run_lib_tests
|
||||
# - run:
|
||||
# name: Performance report
|
||||
# command: |
|
||||
# .circleci/script/performance
|
||||
- run:
|
||||
name: Release
|
||||
command: |
|
||||
|
|
@ -196,7 +256,7 @@ jobs:
|
|||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/graalvm-ce-java8-19.3.0
|
||||
- ~/graalvm-ce-java8-19.3.1
|
||||
key: mac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }}
|
||||
- store_artifacts:
|
||||
path: /tmp/release
|
||||
|
|
@ -206,6 +266,7 @@ jobs:
|
|||
command: |
|
||||
./bb .circleci/script/publish_artifact.clj
|
||||
deploy:
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: circleci/clojure:lein-2.8.1
|
||||
working_directory: ~/repo
|
||||
|
|
@ -229,6 +290,7 @@ jobs:
|
|||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "project.clj" }}
|
||||
docker:
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:stretch
|
||||
steps:
|
||||
|
|
@ -249,6 +311,7 @@ workflows:
|
|||
jobs:
|
||||
- jvm
|
||||
- linux
|
||||
- linux-static
|
||||
- mac
|
||||
- deploy:
|
||||
filters:
|
||||
|
|
@ -257,6 +320,7 @@ workflows:
|
|||
requires:
|
||||
- jvm
|
||||
- linux
|
||||
- linux-static
|
||||
- mac
|
||||
- docker:
|
||||
filters:
|
||||
|
|
@ -265,4 +329,5 @@ workflows:
|
|||
requires:
|
||||
- jvm
|
||||
- linux
|
||||
- linux-static
|
||||
- mac
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fi
|
|||
if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then
|
||||
echo "Building Docker image $image_name:$image_tag"
|
||||
echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin
|
||||
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"
|
||||
# we only update latest when it's not a SNAPSHOT version
|
||||
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]
|
||||
'[cheshire.core :refer [generate-string]]
|
||||
'[clojure.java.shell :refer [sh]]
|
||||
'[clojure.string :as str])
|
||||
|
||||
(def channel "#babashka_circleci_builds")
|
||||
#_(def channel "#_test")
|
||||
(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"))
|
||||
(when slack-hook-url
|
||||
(let [json (generate-string {:username "borkdude"
|
||||
:channel channel
|
||||
:text text})]
|
||||
(sh "curl" "-X" "POST" "-H" "Content-Type: application/json" "-d" json slack-hook-url)))
|
||||
|
||||
(defn slack! [text]
|
||||
(when slack-hook-url
|
||||
(let [json (generate-string {:username "borkdude"
|
||||
:channel channel
|
||||
:text text})]
|
||||
(sh "curl" "-X" "POST" "-H" "Content-Type: application/json" "-d" json slack-hook-url))))
|
||||
|
||||
(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)
|
||||
|
||||
cd /tmp/release
|
||||
mkdir -p /tmp/bb_size
|
||||
./bb '(spit "/tmp/bb_size/size" (.length (io/file "bb")))'
|
||||
|
||||
## release binary as zip archive
|
||||
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
babashka.impl.File/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}
|
||||
: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]
|
||||
patreon: borkdude
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
open_collective: babashka
|
||||
ko_fi: borkdude
|
||||
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
|
||||
|
|
|
|||
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
|
||||
!test-resources/babashka/src_for_classpath_test/foo.jar
|
||||
.cpcache
|
||||
reflection.json
|
||||
*reflection.json
|
||||
/tmp
|
||||
/reports
|
||||
|
|
|
|||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -2,3 +2,6 @@
|
|||
path = sci
|
||||
url = https://github.com/borkdude/sci
|
||||
branch = master
|
||||
[submodule "babashka.curl"]
|
||||
path = babashka.curl
|
||||
url = https://github.com/borkdude/babashka.curl
|
||||
|
|
|
|||
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
## 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
|
||||
- #267 Change behavior of reader conditionals: the `:clj` branch is taken when
|
||||
it occurs before a `:bb` branch.
|
||||
|
||||
## v0.0.44 - 0.0.45
|
||||
- #173: Rename `*in*` to `*input*` (in the `user` namespace). The reason for
|
||||
this is that itt shadowed `clojure.core/*in*` when used unqualified.
|
||||
this is that it shadowed `clojure.core/*in*` when used unqualified.
|
||||
|
||||
## v0.0.43
|
||||
- #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-get install -yy curl unzip build-essential zlib1g-dev
|
||||
RUN apt update
|
||||
RUN apt install --no-install-recommends -yy curl unzip build-essential zlib1g-dev
|
||||
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 tar -xzf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
|
||||
|
||||
ENV GRAALVM_HOME="/opt/graalvm-ce-java8-19.3.1"
|
||||
ENV JAVA_HOME="/opt/graalvm-ce-java8-19.3.1/bin"
|
||||
ENV PATH="$PATH:$JAVA_HOME"
|
||||
ENV BABASHKA_STATIC="true"
|
||||
ENV BABASHKA_XMX=$BABASHKA_XMX
|
||||
|
||||
COPY . .
|
||||
RUN apt install -y sudo
|
||||
RUN ./.circleci/script/install-leiningen
|
||||
RUN ./script/compile
|
||||
RUN cp bb /usr/local/bin
|
||||
|
||||
|
||||
FROM ubuntu:bionic
|
||||
COPY --from=BASE /usr/local/bin/bb /usr/local/bin
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
RUN mkdir -p /usr/local/bin
|
||||
COPY --from=BASE /opt/bb /usr/local/bin/bb
|
||||
CMD ["bb"]
|
||||
|
|
|
|||
559
README.md
559
README.md
|
|
@ -1,10 +1,8 @@
|
|||
<img src="logo/babashka.svg" width="425px">
|
||||
|
||||
[](https://circleci.com/gh/borkdude/babashka/tree/master)
|
||||
[](https://clojars.org/borkdude/babashka)
|
||||
[](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
|
||||
|
||||
<!-- [](https://cljdoc.org/d/borkdude/babashka/CURRENT) -->
|
||||
[](https://opencollective.com/babashka) [](https://clojars.org/borkdude/babashka)
|
||||
|
||||
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
|
||||
</blockquote>
|
||||
|
||||
## Quickstart
|
||||
## Introduction
|
||||
|
||||
``` shellsession
|
||||
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
|
||||
$ 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.
|
||||
The main idea behind babashka is to leverage Clojure in places where you would
|
||||
be using bash otherwise.
|
||||
|
||||
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.
|
||||
|
||||
Goals:
|
||||
### Goals
|
||||
|
||||
* Fast startup / low latency. This is achieved by compiling to native using [GraalVM](https://github.com/oracle/graal).
|
||||
* Familiarity and portability. Keep migration barriers between bash and Clojure as low as possible by:
|
||||
- Gradually introducing Clojure expressions to existing bash scripts
|
||||
- Scripts written in babashka should also be able to run on the JVM without major changes.
|
||||
* Multi-threading support similar to Clojure on the JVM
|
||||
* Batteries included (clojure.tools.cli, core.async, ...)
|
||||
* Low latency Clojure scripting alternative to JVM Clojure.
|
||||
* Easy installation: grab the self-contained binary and run. No JVM needed.
|
||||
* Familiarity and portability:
|
||||
- Scripts should be compatible with JVM Clojure as much as possible
|
||||
- Scripts should be platform-independent as much as possible. Babashka offers
|
||||
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
|
||||
* Provide a mixed Clojure/bash DSL (see portability).
|
||||
* Performance<sup>1<sup>
|
||||
* 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.
|
||||
|
||||
Babashka uses [sci](https://github.com/borkdude/sci) for interpreting Clojure. Sci
|
||||
implements a subset of Clojure and is not as performant as compiled code. If your script is taking more than a few seconds, Clojure on the JVM may be a better fit.
|
||||
<sup>1<sup> Babashka uses [sci](https://github.com/borkdude/sci) for
|
||||
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
|
||||
on [CHANGES.md](CHANGES.md) for a list of breaking changes.
|
||||
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)):
|
||||
|
||||
## Examples
|
||||
[](https://www.youtube.com/watch?v=Nw8aN-nrdEk)
|
||||
|
||||
## Quickstart
|
||||
|
||||
``` shellsession
|
||||
$ ls | bb -i '*input*'
|
||||
["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "resources" "script" "src" "target" "test"]
|
||||
$ curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install-babashka
|
||||
$ 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*)'
|
||||
12
|
||||
### Examples
|
||||
|
||||
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]'
|
||||
[1 2]
|
||||
```
|
||||
|
||||
$ bb '(filterv :foo *input*)' <<< '[{:foo 1} {:bar 2}]'
|
||||
[{:foo 1}]
|
||||
Read more about input and output flags
|
||||
[here](https://github.com/borkdude/babashka/#input-and-output-flags).
|
||||
|
||||
$ bb '(#(+ %1 %2 %3) 1 2 *input*)' <<< 3
|
||||
6
|
||||
Execute a script. E.g. print the current time in California using the
|
||||
`java.time` API:
|
||||
|
||||
$ ls | bb -i '(filterv #(re-find #"README" %) *input*)'
|
||||
["README.md"]
|
||||
File `pst.clj`:
|
||||
``` clojure
|
||||
#!/usr/bin/env bb
|
||||
|
||||
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
|
||||
$ ls /tmp/test | bb -i '*input*'
|
||||
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
|
||||
(def now (java.time.ZonedDateTime/now))
|
||||
(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
|
||||
(def LA-time (.withZoneSameInstant now LA-timezone))
|
||||
(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
|
||||
(println (.format LA-time pattern))
|
||||
```
|
||||
|
||||
$ bb -O '(repeat "dude")' | bb --stream '(str *input* "rino")' | bb -I '(take 3 *input*)'
|
||||
("duderino" "duderino" "duderino")
|
||||
``` shell
|
||||
$ pst.clj
|
||||
05:17
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
### Brew
|
||||
|
|
@ -130,7 +154,13 @@ $ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/inst
|
|||
|
||||
### 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
|
||||
|
||||
|
|
@ -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]
|
||||
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
||||
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
||||
--repl | --socket-repl [<host>:]<port> ]
|
||||
--repl | --socket-repl [<host>:]<port> | --nrepl-server [<host>:]<port> ]
|
||||
[ arg* ]
|
||||
|
||||
Options:
|
||||
|
|
@ -160,6 +190,7 @@ Options:
|
|||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--repl Start REPL. Use rlwrap for history.
|
||||
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
|
||||
--nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
|
||||
--time Print execution time before exiting.
|
||||
-- Stop parsing args and pass everything after -- to *command-line-args*
|
||||
|
||||
|
|
@ -184,14 +215,16 @@ enumerated explicitly.
|
|||
`make-parents`, `output-stream`, `reader`, `resource`, `writer`
|
||||
- `clojure.main`: `repl`
|
||||
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
|
||||
`async`. The `alt` and `go` macros are not available but `alts!!` does work as
|
||||
it is a function.
|
||||
`async`.
|
||||
- `clojure.stacktrace`
|
||||
- `clojure.test`
|
||||
- `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.data.csv`](https://github.com/clojure/data.csv) aliased as `csv`
|
||||
- [`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`.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
#### 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
|
||||
|
||||
Scripts may be executed from a file using `-f` or `--file`:
|
||||
|
|
@ -375,6 +414,12 @@ $ cat script.clj
|
|||
("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
|
||||
|
||||
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!
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
``` shellsession
|
||||
|
|
@ -441,6 +508,13 @@ $ bb "(my-gist-script/-main)"
|
|||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
The `--uberscript` option collects the expressions in
|
||||
|
|
@ -547,44 +661,6 @@ bb -cp "src:test:resources" \
|
|||
(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
|
||||
|
||||
Use the `java.lang.ProcessBuilder` class.
|
||||
|
|
@ -604,10 +680,9 @@ Also see this [example](examples/process_builder.clj).
|
|||
|
||||
## Async
|
||||
|
||||
Apart from `future` and `pmap` for creating threads, you may use the `async`
|
||||
namespace, which maps to `clojure.core.async`, for asynchronous scripting. The
|
||||
following example shows how to get first available value from two different
|
||||
processes:
|
||||
In addition to `future`, `pmap`, `promise` and friends, you may use the
|
||||
`clojure.core.async` namespace for asynchronous scripting. The following example
|
||||
shows how to get first available value from two different processes:
|
||||
|
||||
``` clojure
|
||||
bb '
|
||||
|
|
@ -620,30 +695,93 @@ bb '
|
|||
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
|
||||
|
||||
Babashka is implemented using the [Small Clojure
|
||||
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
|
||||
which implements a subset of Clojure. Babashka is compiled to a native binary
|
||||
using [GraalVM](https://github.com/oracle/graal). It comes with a selection of
|
||||
built-in namespaces and functions from Clojure and other useful libraries. The
|
||||
data types (numbers, strings, persistent collections) are the
|
||||
which implements a sufficiently large subset of Clojure. Babashka is compiled to
|
||||
a native binary using [GraalVM](https://github.com/oracle/graal). It comes with
|
||||
a selection of built-in namespaces and functions from Clojure and other useful
|
||||
libraries. The data types (numbers, strings, persistent collections) are the
|
||||
same. Multi-threading is supported (`pmap`, `future`).
|
||||
|
||||
Differences with Clojure:
|
||||
|
||||
- A subset of Java classes are supported.
|
||||
|
||||
- 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.
|
||||
- A pre-selected set of Java classes are supported. You cannot add Java classes
|
||||
at runtime.
|
||||
|
||||
- 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
|
||||
|
||||
|
|
@ -682,13 +820,13 @@ Ran 1 tests containing 0 assertions.
|
|||
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
|
||||
|
||||
``` clojure
|
||||
{:git/url "https://github.com/weavejester" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
|
||||
{:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
``` 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}])"
|
||||
{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:
|
||||
|
||||
``` clojure
|
||||
{:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}
|
||||
{:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
``` 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\"])"
|
||||
#"(?:\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
|
||||
since babashka v0.0.68 which has `clojure.test` built-in.
|
||||
4clojure as a babashka script!
|
||||
|
||||
#### [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
|
||||
- [Clojure Start Time in 2019](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019) by Stuart Sierra
|
||||
- [Advent of Random
|
||||
|
|
@ -837,13 +1030,18 @@ $ < /tmp/test.txt bb -io '(shuffle *input*)'
|
|||
|
||||
### 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
|
||||
$ curl -s https://api.github.com/repos/borkdude/babashka/tags |
|
||||
jet --from json --keywordize --to edn |
|
||||
bb '(-> *input* first :name (subs 1))'
|
||||
"0.0.4"
|
||||
(defn babashka-latest-version []
|
||||
(-> (sh "curl" "https://api.github.com/repos/borkdude/babashka/tags")
|
||||
:out
|
||||
(json/parse-string true)
|
||||
first
|
||||
:name))
|
||||
|
||||
(babashka-latest-version) ;;=> "v0.0.73"
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
A script with the same goal can be found [here](https://gist.github.com/swlkr/3f346c66410e5c60c59530c4413a248e#gistcomment-3232605).
|
||||
|
||||
### Print current time in California
|
||||
|
||||
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))
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
- [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
|
||||
|
||||
Copyright © 2019 Michiel Borkent
|
||||
Copyright © 2019-2020 Michiel Borkent
|
||||
|
||||
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"],
|
||||
:deps {org.clojure/clojure {:mvn/version "1.10.1"},
|
||||
{:paths ["src" "sci/src" "babashka.curl/src" "resources" "sci/resources"],
|
||||
:deps {org.clojure/clojure {:mvn/version "1.10.2-alpha1"},
|
||||
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/sci.impl.reflector {:mvn/version "0.0.1"}
|
||||
org.clojure/core.async {:mvn/version "1.0.567"},
|
||||
|
|
@ -9,7 +9,9 @@
|
|||
org.clojure/data.csv {:mvn/version "1.0.0"},
|
||||
cheshire {:mvn/version "5.10.0"}
|
||||
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
|
||||
{:main-opts ["-m" "babashka.main"]}
|
||||
:profile
|
||||
|
|
|
|||
38
doc/dev.md
38
doc/dev.md
|
|
@ -1,5 +1,9 @@
|
|||
# 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.
|
||||
|
||||
``` shellsession
|
||||
|
|
@ -12,8 +16,6 @@ To update later on:
|
|||
$ git submodule update --recursive
|
||||
```
|
||||
|
||||
You need [Leiningen](https://leiningen.org/), and for building binaries you need GraalVM.
|
||||
|
||||
## 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:
|
||||
|
||||
script/compile
|
||||
$ script/compile
|
||||
|
||||
To tweak maximum heap size:
|
||||
|
||||
```
|
||||
$ BABASHKA_XMX="-J-Xmx4g" script/compile
|
||||
```
|
||||
|
||||
## 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
|
||||
Added: `clojure.data.xml`. Growth: 1.8mb / 0.4mb zipped.
|
||||
2020/03/28 Added java.nio.file.FileSystem(s) to support extracting zip files
|
||||
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
|
||||
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"}
|
||||
:license {:name "Eclipse Public License 1.0"
|
||||
: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"]
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
|
||||
[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/sci.impl.reflector "0.0.1"]
|
||||
[org.clojure/core.async "1.0.567"]
|
||||
|
|
@ -19,7 +21,9 @@
|
|||
[org.clojure/data.csv "1.0.0"]
|
||||
[org.clojure/data.xml "0.2.0-alpha6"]
|
||||
[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"]
|
||||
[com.clojure-goes-fast/clj-async-profiler "0.4.0"]]}
|
||||
: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
|
||||
|
||||
if [ -z "$BABASHKA_XMX" ]; then
|
||||
export BABASHKA_XMX="-J-Xmx3g"
|
||||
fi
|
||||
|
||||
if [ -z "$GRAALVM_HOME" ]; then
|
||||
echo "Please set GRAALVM_HOME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$BABASHKA_XMX" ]; then
|
||||
export BABASHKA_XMX="-J-Xmx3g"
|
||||
fi
|
||||
|
||||
"$GRAALVM_HOME/bin/gu" install native-image || true
|
||||
$GRAALVM_HOME/bin/gu install native-image
|
||||
|
||||
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
|
||||
|
||||
lein with-profiles +reflection do run
|
||||
lein do clean, uberjar
|
||||
SVM_JAR=$(find "$GRAALVM_HOME" | grep svm.jar)
|
||||
$GRAALVM_HOME/bin/javac -cp "$SVM_JAR" resources/CutOffCoreServicesDependencies.java
|
||||
|
||||
$GRAALVM_HOME/bin/native-image \
|
||||
-jar target/babashka-$BABASHKA_VERSION-standalone.jar \
|
||||
-H:Name=bb \
|
||||
-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 \
|
||||
"$BABASHKA_XMX"
|
||||
if [ -z "$BABASHKA_JAR" ]; then
|
||||
lein with-profiles +reflection do run
|
||||
lein do clean, uberjar
|
||||
BABASHKA_JAR=${BABASHKA_JAR:-"target/babashka-$BABASHKA_VERSION-standalone.jar"}
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
install_dir=${1:-/tmp/clojure}
|
||||
install_dir=${1:-/usr/local}
|
||||
mkdir -p "$install_dir"
|
||||
cd /tmp
|
||||
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
|
||||
|
||||
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
|
||||
BB_CMD="./bb"
|
||||
|
|
@ -10,4 +10,13 @@ else
|
|||
BB_CMD="lein bb"
|
||||
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/regal_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
|
||||
BABASHKA_PRELOADS=""
|
||||
BABASHKA_CLASSPATH=""
|
||||
echo "running tests part 1"
|
||||
lein test "$@"
|
||||
|
||||
BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
|
||||
BABASHKA_PRELOADS_TEST=true
|
||||
echo "running tests part 2"
|
||||
lein test :only babashka.main-test/preloads-test
|
||||
|
||||
BABASHKA_PRELOADS="(require '[env-ns])"
|
||||
BABASHKA_CLASSPATH_TEST=true
|
||||
BABASHKA_CLASSPATH="test-resources/babashka/src_for_classpath_test/env"
|
||||
echo "running tests part 3"
|
||||
lein test :only babashka.classpath-test/classpath-env-test
|
||||
|
|
|
|||
|
|
@ -1,18 +1,50 @@
|
|||
(ns babashka.impl.async
|
||||
{:no-doc true}
|
||||
(: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
|
||||
[_ _ & 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
|
||||
{'<!! async/<!!
|
||||
'>!! async/>!!
|
||||
'admix async/admix
|
||||
'alts! async/alts!
|
||||
'alts!! async/alts!!
|
||||
'alt!! (with-meta alt!! {:sci/macro true})
|
||||
'buffer async/buffer
|
||||
'chan async/chan
|
||||
'close! async/close!
|
||||
|
|
@ -53,7 +85,7 @@
|
|||
'take! async/take!
|
||||
'tap async/tap
|
||||
'thread (with-meta thread {:sci/macro true})
|
||||
'thread-call async/thread-call
|
||||
'thread-call thread-call
|
||||
'timeout async/timeout
|
||||
'to-chan async/to-chan
|
||||
'toggle async/toggle
|
||||
|
|
@ -65,7 +97,13 @@
|
|||
'unsub async/unsub
|
||||
'unsub-all async/unsub-all
|
||||
'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
|
||||
{'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
|
||||
{:no-doc true}
|
||||
(:require
|
||||
[cheshire.core :as json]
|
||||
#_[clojure.string :as str]))
|
||||
|
||||
;; (def os-name (str/lower-case (System/getProperty "os.name")))
|
||||
;; (def os (cond (str/includes? os-name "mac") :mac
|
||||
;; (or (str/includes? os-name "nix")
|
||||
;; (str/includes? os-name "nux")) :linux
|
||||
;; (str/includes? os-name "win") :windows))
|
||||
;; (def unix-like? (or (identical? os :linux)
|
||||
;; (identical? os :mac)))
|
||||
[cheshire.core :as json]))
|
||||
|
||||
(def classes
|
||||
'{:all [java.io.BufferedReader
|
||||
`{:all [clojure.lang.ExceptionInfo
|
||||
java.io.BufferedReader
|
||||
java.io.BufferedWriter
|
||||
java.io.ByteArrayInputStream
|
||||
java.io.ByteArrayOutputStream
|
||||
|
|
@ -21,19 +13,27 @@
|
|||
java.io.InputStream
|
||||
java.io.IOException
|
||||
java.io.OutputStream
|
||||
java.io.FileReader
|
||||
java.io.PushbackInputStream
|
||||
java.io.Reader
|
||||
java.io.SequenceInputStream
|
||||
java.io.StringReader
|
||||
java.io.StringWriter
|
||||
java.io.Writer
|
||||
java.lang.ArithmeticException
|
||||
java.lang.AssertionError
|
||||
java.lang.Boolean
|
||||
java.lang.Byte
|
||||
java.lang.Comparable
|
||||
java.lang.Class
|
||||
java.lang.Double
|
||||
java.lang.Exception
|
||||
java.lang.Integer
|
||||
java.lang.Long
|
||||
java.lang.NumberFormatException
|
||||
java.lang.Math
|
||||
java.lang.Runtime
|
||||
java.lang.RuntimeException
|
||||
java.util.concurrent.LinkedBlockingQueue
|
||||
java.lang.Object
|
||||
java.lang.String
|
||||
|
|
@ -44,15 +44,21 @@
|
|||
java.lang.ProcessBuilder
|
||||
java.lang.ProcessBuilder$Redirect
|
||||
java.math.BigInteger
|
||||
java.net.URI
|
||||
java.net.DatagramSocket
|
||||
java.net.DatagramPacket
|
||||
java.net.HttpURLConnection
|
||||
java.net.InetAddress
|
||||
java.net.ServerSocket
|
||||
java.net.Socket
|
||||
java.net.UnknownHostException
|
||||
java.net.URI
|
||||
;; java.net.URL, see below
|
||||
java.net.URLEncoder
|
||||
java.net.URLDecoder
|
||||
java.nio.file.CopyOption
|
||||
java.nio.file.FileAlreadyExistsException
|
||||
java.nio.file.FileSystem
|
||||
java.nio.file.FileSystems
|
||||
java.nio.file.Files
|
||||
java.nio.file.LinkOption
|
||||
java.nio.file.NoSuchFileException
|
||||
|
|
@ -83,18 +89,24 @@
|
|||
java.time.ZonedDateTime
|
||||
java.time.ZoneId
|
||||
java.time.ZoneOffset
|
||||
java.time.temporal.ChronoUnit
|
||||
java.time.temporal.TemporalAccessor
|
||||
java.util.regex.Pattern
|
||||
java.util.Base64
|
||||
java.util.Base64$Decoder
|
||||
java.util.Base64$Encoder
|
||||
java.util.Date
|
||||
java.util.MissingResourceException
|
||||
java.util.Properties
|
||||
java.util.UUID
|
||||
java.util.concurrent.TimeUnit
|
||||
java.util.zip.InflaterInputStream
|
||||
java.util.zip.DeflaterInputStream
|
||||
java.util.zip.GZIPInputStream
|
||||
java.util.zip.GZIPOutputStream]
|
||||
java.util.zip.GZIPOutputStream
|
||||
org.yaml.snakeyaml.error.YAMLException
|
||||
~(symbol "[B")
|
||||
]
|
||||
:constructors [clojure.lang.Delay
|
||||
clojure.lang.MapEntry
|
||||
clojure.lang.LineNumberingPushbackReader
|
||||
|
|
@ -104,8 +116,7 @@
|
|||
:methods [borkdude.graal.LockFix ;; support for locking
|
||||
]
|
||||
:fields [clojure.lang.PersistentQueue]
|
||||
:instance-checks [clojure.lang.ExceptionInfo
|
||||
clojure.lang.IObj
|
||||
:instance-checks [clojure.lang.IObj
|
||||
clojure.lang.IEditableCollection]
|
||||
:custom {clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true
|
||||
:allPublicMethods true}
|
||||
|
|
@ -197,7 +208,13 @@
|
|||
(instance? java.io.ByteArrayOutputStream v)
|
||||
java.io.ByteArrayOutputStream
|
||||
(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))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
(ns babashka.impl.clojure.core
|
||||
{:no-doc true}
|
||||
(:refer-clojure :exclude [future])
|
||||
(:require [borkdude.graal.locking :as locking]))
|
||||
(:refer-clojure :exclude [future read read-string])
|
||||
(:require [borkdude.graal.locking :as locking]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.namespaces :refer [copy-core-var]]))
|
||||
|
||||
(defn locking* [form bindings v f & args]
|
||||
(apply @#'locking/locking form bindings v f args))
|
||||
|
|
@ -16,17 +18,17 @@
|
|||
ret#))
|
||||
|
||||
(def core-extras
|
||||
{'file-seq file-seq
|
||||
'agent agent
|
||||
'instance? instance? ;; TODO: move to sci
|
||||
'send send
|
||||
'send-off send-off
|
||||
'promise promise
|
||||
'deliver deliver
|
||||
{'file-seq (copy-core-var file-seq)
|
||||
'agent (copy-core-var agent)
|
||||
'send (copy-core-var send)
|
||||
'send-off (copy-core-var send-off)
|
||||
'promise (copy-core-var promise)
|
||||
'deliver (copy-core-var deliver)
|
||||
'locking (with-meta locking* {:sci/macro true})
|
||||
'shutdown-agents shutdown-agents
|
||||
'slurp slurp
|
||||
'spit spit
|
||||
'shutdown-agents (copy-core-var shutdown-agents)
|
||||
'slurp (copy-core-var slurp)
|
||||
'spit (copy-core-var spit)
|
||||
'time (with-meta time* {:sci/macro true})
|
||||
'Throwable->map Throwable->map
|
||||
'compare-and-set! compare-and-set!})
|
||||
'Throwable->map (copy-core-var Throwable->map)
|
||||
'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
|
||||
;; GraalVM.
|
||||
;; Modified / stripped version of clojure.core.server for use with babashka on
|
||||
;; GraalVM.
|
||||
;;
|
||||
;; Copyright (c) Rich Hickey. All rights reserved.
|
||||
;; The use and distribution terms for this software are covered by the
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@
|
|||
*e nil]
|
||||
~@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
|
||||
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
|
||||
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.
|
||||
;; 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.
|
||||
(ns babashka.impl.clojure.stacktrace
|
||||
{:no-doc true}
|
||||
(:require [clojure.stacktrace :as stacktrace]
|
||||
[sci.core :as sci]))
|
||||
|
||||
;;; stacktrace.clj: print Clojure-centric stack traces
|
||||
|
||||
;; by Stuart Sierra
|
||||
;; January 6, 2009
|
||||
|
||||
(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))))
|
||||
(defmacro wrap-out [f]
|
||||
`(fn [& ~'args]
|
||||
(binding [*out* @sci/out]
|
||||
(apply ~f ~'args))))
|
||||
|
||||
(def stacktrace-namespace
|
||||
{'root-cause root-cause
|
||||
'print-trace-element print-trace-element
|
||||
'print-throwable print-throwable
|
||||
'print-stack-trace print-stack-trace
|
||||
'print-cause-trace print-cause-trace})
|
||||
{'root-cause stacktrace/root-cause
|
||||
'print-trace-element (wrap-out stacktrace/print-trace-element)
|
||||
'print-throwable (wrap-out stacktrace/print-throwable)
|
||||
'print-stack-trace (wrap-out stacktrace/print-stack-trace)
|
||||
'print-cause-trace (wrap-out stacktrace/print-cause-trace)})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
; Copyright (c) Rich Hickey. 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.
|
||||
;; Copyright (c) Rich Hickey. 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.
|
||||
|
||||
;;; test.clj: test framework for Clojure
|
||||
|
||||
|
|
@ -232,9 +232,8 @@
|
|||
For additional event types, see the examples in the code.
|
||||
"}
|
||||
babashka.impl.clojure.test
|
||||
(:require [babashka.impl.clojure.stacktrace :as stack]
|
||||
[babashka.impl.common :refer [ctx]]
|
||||
[clojure.string :as str]
|
||||
(:require [babashka.impl.common :refer [ctx]]
|
||||
[clojure.stacktrace :as stack]
|
||||
[clojure.template :as temp]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.analyzer :as ana]
|
||||
|
|
@ -430,9 +429,9 @@
|
|||
result# (apply ~pred values#)]
|
||||
(if result#
|
||||
(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,
|
||||
:expected '~form, :actual (list '~'not (cons '~pred values#))}))
|
||||
:expected '~form, :actual (list '~'not (cons '~pred values#))}))
|
||||
result#)))
|
||||
|
||||
(defn assert-any
|
||||
|
|
@ -443,9 +442,9 @@
|
|||
`(let [value# ~form]
|
||||
(if value#
|
||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||
:expected '~form, :actual value#})
|
||||
:expected '~form, :actual value#})
|
||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||
:expected '~form, :actual value#}))
|
||||
:expected '~form, :actual value#}))
|
||||
value#))
|
||||
|
||||
|
||||
|
|
@ -479,9 +478,9 @@
|
|||
(let [result# (instance? klass# object#)]
|
||||
(if result#
|
||||
(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,
|
||||
:expected '~form, :actual (class object#)}))
|
||||
:expected '~form, :actual (class object#)}))
|
||||
result#)))
|
||||
|
||||
(defmethod assert-expr 'thrown? [msg form]
|
||||
|
|
@ -492,10 +491,10 @@
|
|||
body (nthnext form 2)]
|
||||
`(try ~@body
|
||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||
:expected '~form, :actual nil})
|
||||
:expected '~form, :actual nil})
|
||||
(catch ~klass e#
|
||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||
:expected '~form, :actual e#})
|
||||
:expected '~form, :actual e#})
|
||||
e#))))
|
||||
|
||||
(defmethod assert-expr 'thrown-with-msg? [msg form]
|
||||
|
|
@ -512,7 +511,7 @@
|
|||
(let [m# (.getMessage e#)]
|
||||
(if (re-find ~re m#)
|
||||
(clojure.test/do-report {:type :pass, :message ~msg,
|
||||
:expected '~form, :actual e#})
|
||||
:expected '~form, :actual e#})
|
||||
(clojure.test/do-report {:type :fail, :message ~msg,
|
||||
:expected '~form, :actual 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))
|
||||
|
||||
(defn handle-pipe! []
|
||||
(Signal/handle
|
||||
(Signal. "PIPE")
|
||||
(reify SignalHandler
|
||||
(handle [_ _]
|
||||
(vreset! pipe-state :PIPE)))))
|
||||
(when-not (= "true" (System/getenv "BABASHKA_DISABLE_PIPE_SIGNAL_HANDLER"))
|
||||
(Signal/handle
|
||||
(Signal. "PIPE")
|
||||
(reify SignalHandler
|
||||
(handle [_ _]
|
||||
(vreset! pipe-state :PIPE))))))
|
||||
|
|
|
|||
|
|
@ -5,17 +5,21 @@
|
|||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[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.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
|
||||
"Default :caught hook for repl"
|
||||
[e]
|
||||
[^Throwable e]
|
||||
(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)))
|
||||
|
||||
(defn repl
|
||||
|
|
@ -31,19 +35,15 @@
|
|||
(sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
|
||||
(sio/println "Clojure rocks, Bash reaches.")
|
||||
(sio/println)
|
||||
(eval-form sci-ctx '(require '[clojure.repl :refer [dir doc]]))))
|
||||
(eval-form sci-ctx '(use 'clojure.repl))))
|
||||
:read (or read
|
||||
(fn [_request-prompt request-exit]
|
||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in))
|
||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in)) this works fine
|
||||
(if (r/peek-char in) ;; if this is nil, we reached EOF
|
||||
(let [v (parser/parse-next sci-ctx in)]
|
||||
(if (or (identical? :repl/quit v)
|
||||
(identical? :repl/exit v)
|
||||
(identical? :edamame.impl.parser/eof v))
|
||||
request-exit
|
||||
v))
|
||||
request-exit)))
|
||||
(let [v (parser/parse-next sci-ctx in)]
|
||||
(if (or (identical? :repl/quit v)
|
||||
(identical? :repl/exit v)
|
||||
(identical? :edamame.impl.parser/eof v))
|
||||
request-exit
|
||||
v))))
|
||||
:eval (or eval
|
||||
(fn [expr]
|
||||
(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}
|
||||
(:require
|
||||
[babashka.impl.async :refer [async-namespace async-protocols-namespace]]
|
||||
[babashka.impl.bencode :refer [bencode-namespace]]
|
||||
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
|
||||
[babashka.impl.classes :as classes]
|
||||
[babashka.impl.classpath :as cp]
|
||||
[babashka.impl.clojure.core :refer [core-extras]]
|
||||
[babashka.impl.clojure.java.io :refer [io-namespace]]
|
||||
[babashka.impl.clojure.java.shell :refer [shell-namespace]]
|
||||
[babashka.impl.clojure.main :refer [demunge]]
|
||||
[babashka.impl.clojure.stacktrace :refer [stacktrace-namespace print-stack-trace]]
|
||||
[babashka.impl.clojure.main :as clojure-main :refer [demunge]]
|
||||
[babashka.impl.clojure.pprint :refer [pprint-namespace]]
|
||||
[babashka.impl.clojure.stacktrace :refer [stacktrace-namespace]]
|
||||
[babashka.impl.common :as common]
|
||||
[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.repl :as repl]
|
||||
[babashka.impl.sigint-handler :as sigint-handler]
|
||||
[babashka.impl.socket-repl :as socket-repl]
|
||||
[babashka.impl.test :as t]
|
||||
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
||||
[babashka.impl.xml :as xml]
|
||||
[babashka.impl.transit :refer [transit-namespace]]
|
||||
[babashka.impl.yaml :refer [yaml-namespace]]
|
||||
[babashka.wait :as wait]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.stacktrace :refer [print-stack-trace]]
|
||||
[clojure.string :as str]
|
||||
[fipp.edn :as fipp]
|
||||
[sci.addons :as addons]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.interpreter :refer [eval-string*]]
|
||||
[sci.impl.opts :as sci-opts]
|
||||
[sci.impl.types :as sci-types]
|
||||
[sci.impl.unrestrict :refer [*unrestricted*]]
|
||||
[sci.impl.vars :as vars])
|
||||
(:gen-class))
|
||||
|
|
@ -110,7 +118,14 @@
|
|||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
(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")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
|
|
@ -154,7 +169,7 @@
|
|||
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
||||
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
|
||||
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
|
||||
--repl | --socket-repl [<host>:]<port> ]
|
||||
--repl | --socket-repl [<host>:]<port> | --nrepl-server [<host>:]<port> ]
|
||||
[ arg* ]")
|
||||
(defn print-usage []
|
||||
(println usage-string))
|
||||
|
|
@ -184,6 +199,7 @@
|
|||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--repl Start REPL. Use rlwrap for history.
|
||||
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
|
||||
--nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
|
||||
--time Print execution time before exiting.
|
||||
-- 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 #"^#!.*" ""))
|
||||
(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))
|
||||
|
||||
(defn load-file* [sci-ctx 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)}
|
||||
(eval-string* sci-ctx s))))
|
||||
|
||||
(defn eval* [sci-ctx form]
|
||||
(eval-string* sci-ctx (pr-str form)))
|
||||
(try
|
||||
(eval-string* sci-ctx s)
|
||||
(finally (sci-types/setVal vars/current-ns prev-ns))))))
|
||||
|
||||
(defn start-socket-repl! [address ctx]
|
||||
(socket-repl/start-repl! address ctx)
|
||||
;; hang until SIGINT
|
||||
@(promise))
|
||||
|
||||
(defn start-nrepl! [address ctx]
|
||||
(nrepl-server/start-server! ctx address)
|
||||
;; hang until SIGINT
|
||||
#_@(promise))
|
||||
|
||||
(defn exit [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
|
||||
csv clojure.data.csv
|
||||
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))
|
||||
|
||||
|
|
@ -257,12 +278,17 @@ Everything after that is bound to *command-line-args*."))
|
|||
'clojure.data.csv csv/csv-namespace
|
||||
'cheshire.core cheshire-core-namespace
|
||||
'clojure.stacktrace stacktrace-namespace
|
||||
'clojure.main {'demunge demunge}
|
||||
'clojure.main {'demunge demunge
|
||||
'repl-requires clojure-main/repl-requires}
|
||||
'clojure.repl {'demunge demunge}
|
||||
'clojure.test t/clojure-test-namespace
|
||||
'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
|
||||
{'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
|
||||
[& args]
|
||||
(handle-pipe!)
|
||||
(sigint-handler/handle-sigint!)
|
||||
#_(binding [*out* *err*]
|
||||
(prn "M" (meta (get bindings 'future))))
|
||||
(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
|
||||
:help? :file :command-line-args
|
||||
:expressions :stream? :time?
|
||||
:repl :socket-repl
|
||||
:repl :socket-repl :nrepl
|
||||
:verbose? :classpath
|
||||
:main :uberscript] :as _opts}
|
||||
(parse-opts args)
|
||||
_ (when main (System/setProperty "babashka.main" main))
|
||||
read-next (fn [*in*]
|
||||
(if (pipe-signal-received?)
|
||||
::EOF
|
||||
(if stream?
|
||||
(if shell-in (or (read-line) ::EOF)
|
||||
(read-edn))
|
||||
(edn/read {;;:readers *data-readers*
|
||||
:eof ::EOF} *in*))
|
||||
(delay (cond shell-in
|
||||
(shell-seq *in*)
|
||||
edn-in
|
||||
|
|
@ -337,6 +366,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
:imports '{ArithmeticException java.lang.ArithmeticException
|
||||
AssertionError java.lang.AssertionError
|
||||
Boolean java.lang.Boolean
|
||||
Byte java.lang.Byte
|
||||
Class java.lang.Class
|
||||
Double java.lang.Double
|
||||
Exception java.lang.Exception
|
||||
|
|
@ -345,7 +375,10 @@ Everything after that is bound to *command-line-args*."))
|
|||
File java.io.File
|
||||
Long java.lang.Long
|
||||
Math java.lang.Math
|
||||
NumberFormatException java.lang.NumberFormatException
|
||||
Object java.lang.Object
|
||||
Runtime java.lang.Runtime
|
||||
RuntimeException java.lang.RuntimeException
|
||||
ProcessBuilder java.lang.ProcessBuilder
|
||||
String java.lang.String
|
||||
StringBuilder java.lang.StringBuilder
|
||||
|
|
@ -357,17 +390,20 @@ Everything after that is bound to *command-line-args*."))
|
|||
ctx (addons/future ctx)
|
||||
sci-ctx (sci-opts/init ctx)
|
||||
_ (vreset! common/ctx sci-ctx)
|
||||
input-var (sci/new-dynamic-var '*input* nil)
|
||||
_ (swap! (:env sci-ctx)
|
||||
(fn [env]
|
||||
(update-in env [:namespaces 'clojure.core] assoc
|
||||
'eval #(eval* sci-ctx %)
|
||||
'load-file #(load-file* sci-ctx %))))
|
||||
_ (swap! (:env sci-ctx)
|
||||
(fn [env]
|
||||
(assoc-in env [:namespaces 'clojure.main 'repl]
|
||||
(fn [& opts]
|
||||
(let [opts (apply hash-map opts)]
|
||||
(repl/start-repl! sci-ctx opts))))))
|
||||
(update env :namespaces
|
||||
(fn [namespaces] [:namespaces 'clojure.main 'repl]
|
||||
(-> namespaces
|
||||
(assoc-in ['clojure.core 'load-file] #(load-file* sci-ctx %))
|
||||
(assoc-in ['clojure.main 'repl]
|
||||
(fn [& opts]
|
||||
(let [opts (apply hash-map 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))
|
||||
[expressions exit-code]
|
||||
(cond expressions [expressions nil]
|
||||
|
|
@ -395,17 +431,16 @@ Everything after that is bound to *command-line-args*."))
|
|||
[(print-help) 0]
|
||||
repl [(repl/start-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))
|
||||
(try
|
||||
(loop [in (read-next *in*)]
|
||||
(let [_ (swap! env update-in [:namespaces 'user]
|
||||
assoc (with-meta '*input*
|
||||
(when-not stream?
|
||||
{:sci.impl/deref! true}))
|
||||
(sci/new-dynamic-var '*input* in))]
|
||||
(loop []
|
||||
(let [in (read-next *in*)]
|
||||
(if (identical? ::EOF in)
|
||||
[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)
|
||||
(if-let [pr-f (cond shell-out println
|
||||
edn-out prn)]
|
||||
|
|
@ -416,7 +451,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
(pr-f res))
|
||||
(prn res)))) 0]]
|
||||
(if stream?
|
||||
(recur (read-next *in*))
|
||||
(recur)
|
||||
res)))))
|
||||
(catch Throwable e
|
||||
(error-handler* e verbose?)))
|
||||
|
|
@ -440,7 +475,8 @@ Everything after that is bound to *command-line-args*."))
|
|||
(defn -main
|
||||
[& args]
|
||||
(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)]
|
||||
(dotimes [i n]
|
||||
(if (< i last-iteration)
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
opts)
|
||||
t0 (System/currentTimeMillis)]
|
||||
(loop []
|
||||
(let [v (try (with-open [_ (Socket. host port)]
|
||||
(- (System/currentTimeMillis) t0))
|
||||
(let [v (try (.close (Socket. host port))
|
||||
(- (System/currentTimeMillis) t0)
|
||||
(catch ConnectException _e
|
||||
(let [took (- (System/currentTimeMillis) t0)]
|
||||
(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]
|
||||
[clojure.edn :as edn]
|
||||
[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]
|
||||
(edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
|
||||
|
|
@ -28,7 +29,10 @@
|
|||
|
||||
(deftest main-test
|
||||
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
|
||||
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4"))))
|
||||
(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
|
||||
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
(ns babashka.file-var-test
|
||||
(:require
|
||||
[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]
|
||||
(apply tu/bb (when (some? input) (str input)) (map str args)))
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
(ns babashka.http-connection-test
|
||||
(:require
|
||||
[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]
|
||||
(apply tu/bb nil (map str args)))
|
||||
|
||||
(deftest open-connection-test
|
||||
(is (= "\"1\"" (str/trim (bb "-e" "
|
||||
(is (try (= "\"1\"" (str/trim (bb "-e" "
|
||||
(require '[cheshire.core :as json])
|
||||
(let [conn ^java.net.HttpURLConnection (.openConnection (java.net.URL. \"https://postman-echo.com/get?foo=1\"))]
|
||||
(.setConnectTimeout conn 1000)
|
||||
|
|
@ -18,4 +18,6 @@
|
|||
err (.getErrorStream conn)
|
||||
response (json/decode (slurp is) true)]
|
||||
(-> 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!]]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t :refer [deftest is]]
|
||||
[sci.impl.opts :refer [init]]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.opts :refer [init]]
|
||||
[sci.impl.vars :as vars]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -24,6 +24,15 @@
|
|||
(sci/with-in-str (str expr "\n:repl/quit")
|
||||
(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
|
||||
(assert-repl "1" "1")
|
||||
(assert-repl "[1 2 3]" "[1 2 3]")
|
||||
|
|
@ -34,7 +43,8 @@
|
|||
(assert-repl "1\n(inc *1)" "2")
|
||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]"))
|
||||
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]")
|
||||
(assert-repl-error "(+ 1 nil)" "NullPointerException"))
|
||||
|
||||
;;;; Scratch
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
(:require
|
||||
[babashka.impl.socket-repl :refer [start-repl! stop-repl!]]
|
||||
[babashka.test-utils :as tu]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.java.shell :refer [sh]]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t :refer [deftest is testing]]
|
||||
[clojure.java.io :as io]
|
||||
[sci.impl.opts :refer [init]]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
|
|||
|
|
@ -16,4 +16,8 @@
|
|||
(is (= "18-12-2019 16:01:41"
|
||||
(bb '(.format
|
||||
(java.time.LocalDateTime/parse "2019-12-18T16:01:41.485")
|
||||
(java.time.format.DateTimeFormatter/ofPattern "dd-MM-yyyy HH:mm:ss"))))))
|
||||
(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]]
|
||||
[sci.core :as sci]))
|
||||
|
||||
(defmethod clojure.test/report :begin-test-var [m]
|
||||
(println "===" (-> m :var meta :name))
|
||||
(println))
|
||||
|
||||
(defn bb [input & args]
|
||||
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
|
||||
|
||||
|
|
@ -29,7 +33,7 @@
|
|||
|
||||
(deftest main-test
|
||||
(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"
|
||||
(is (= 1 (bb 0 '(if (zero? *input*) 1 2))))
|
||||
(is (= 2 (bb 1 '(if (zero? *input*) 1 2))))
|
||||
|
|
@ -118,9 +122,39 @@
|
|||
|
||||
(deftest load-file-test
|
||||
(let [tmp (java.io.File/createTempFile "script" ".clj")]
|
||||
(spit tmp "(defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
|
||||
(is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (bar (foo 10 30) 3)"
|
||||
(.getPath tmp)))))))
|
||||
(.deleteOnExit tmp)
|
||||
(spit tmp "(ns foo) (defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
|
||||
(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
|
||||
(is (= "120\n" (test-utils/bb nil "(eval '(do (defn foo [x y] (+ x y))
|
||||
|
|
@ -160,7 +194,7 @@
|
|||
|
||||
(deftest future-test
|
||||
(is (= 6 (bb nil "@(future (+ 1 2 3))"))))
|
||||
|
||||
|
||||
(deftest promise-test
|
||||
(is (= :timeout (bb nil "(deref (promise) 1 :timeout)")))
|
||||
(is (= :ok (bb nil "(let [x (promise)]
|
||||
|
|
@ -220,23 +254,26 @@
|
|||
{:default :timed-out :timeout 100}))"
|
||||
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
|
||||
(is (= {:result 8080} (bb nil "test/babashka/scripts/tools.cli.bb"))))
|
||||
|
||||
(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
|
||||
(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 (= [1 2] (bb nil "[1 2 #?@(:bb [] :clj [1])]"))))
|
||||
|
||||
|
|
@ -355,8 +392,52 @@
|
|||
(alter-var-root #'clojure.core/inc (constantly inc2))
|
||||
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
|
||||
|
||||
(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