diff --git a/.build/bb.edn b/.build/bb.edn index b68f2790..6ec32294 100644 --- a/.build/bb.edn +++ b/.build/bb.edn @@ -2,5 +2,5 @@ :deps {borkdude/gh-release-artifact #_{:local/root "../gh-release-artifact"} {:git/url "https://github.com/borkdude/gh-release-artifact" - :sha "f34f3e382e6a0ef7f52748b2f27eb681f799a822"}} + :git/sha "ce060c12a25b552b864dc90f8fb344a2eb91ea9d"}} :tasks {release-artifact babashka.release-artifact/release}} diff --git a/.circleci/config.yml b/.circleci/config.yml index aac22a89..3e0293e5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,484 +3,38 @@ # Check https://circleci.com/docs/2.0/language-clojure/ for more details # version: 2.1 -commands: - setup-docker-buildx: - steps: - - run: - name: Create multi-platform capabale buildx builder - command: | - docker run --privileged --rm tonistiigi/binfmt --install all - docker buildx create --name ci-builder --use -jobs: - jvm: - docker: - - image: circleci/clojure:openjdk-11-lein-2.9.6-bullseye - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - BABASHKA_PLATFORM: linux # could be used in jar name - resource_class: large - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - - restore_cache: - keys: - - v1-dependencies-{{ checksum "project.clj" }}-{{ checksum "deps.edn" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: - name: Install Clojure - command: | - sudo script/install-clojure - - run: - name: Run JVM tests - command: | - export BABASHKA_FEATURE_JDBC=true - export BABASHKA_FEATURE_POSTGRESQL=true - script/test - script/run_lib_tests - - run: - name: Run as lein command - command: | - .circleci/script/lein - - run: - name: Create uberjar - command: | - mkdir -p /tmp/release - script/uberjar - VERSION=$(cat resources/BABASHKA_VERSION) - jar=target/babashka-$VERSION-standalone.jar - cp $jar /tmp/release - java -jar $jar script/reflection.clj - reflection="babashka-$VERSION-reflection.json" - java -jar "$jar" --config .build/bb.edn --deps-root . release-artifact "$jar" - java -jar "$jar" --config .build/bb.edn --deps-root . release-artifact "$reflection" - - store_artifacts: - path: /tmp/release - destination: release - - save_cache: - paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "project.clj" }}-{{ checksum "deps.edn" }} - linux: - docker: - - image: circleci/clojure:openjdk-11-lein-2.9.6-bullseye - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - GRAALVM_VERSION: "22.0.0.2" - GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.0.0.2 - BABASHKA_PLATFORM: linux # used in release script - BABASHKA_TEST_ENV: native - BABASHKA_XMX: "-J-Xmx6500m" - 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: | - sudo script/install-clojure - - run: - name: Install native dev tools - command: | - sudo apt-get update - sudo apt-get -y install build-essential zlib1g-dev - - run: - name: Download GraalVM - command: | - script/install-graalvm - - run: - name: Build binary - command: | - script/uberjar - script/compile - no_output_timeout: 30m - - run: - name: Run tests - command: | - script/test - script/run_lib_tests - - run: - name: Release - command: | - .circleci/script/release - - persist_to_workspace: - root: /tmp - paths: - - release - - save_cache: - paths: - - ~/.m2 - - ~/graalvm-ce-java11-22.0.0.2 - 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 || true - linux-static: - docker: - - image: circleci/clojure:openjdk-11-lein-2.9.6-bullseye - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - GRAALVM_VERSION: "22.0.0.2" - GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.0.0.2 - BABASHKA_PLATFORM: linux # used in release script - BABASHKA_TEST_ENV: native - BABASHKA_STATIC: "true" - BABASHKA_MUSL: "true" - BABASHKA_XMX: "-J-Xmx6500m" - resource_class: large - steps: - - checkout - - attach_workspace: - at: /tmp - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - # - run: - # name: "Short circuit on SNAPSHOT" - # command: | - # VERSION=$(cat resources/BABASHKA_VERSION) - # if [[ "$VERSION" == *-SNAPSHOT ]] - # then - # circleci task halt - # fi - - restore_cache: - keys: - - linux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - run: - name: Install Clojure - command: | - sudo script/install-clojure - - run: - name: Install native dev tools - command: | - sudo apt-get update - sudo apt-get -y install build-essential zlib1g-dev - sudo -E script/setup-musl - - run: - name: Download GraalVM - command: | - script/install-graalvm - - run: - name: Build binary - command: | - script/uberjar - script/compile - no_output_timeout: 30m - - run: - name: Run tests - command: | - script/test - script/run_lib_tests - - run: - name: Release - command: | - .circleci/script/release - - persist_to_workspace: - root: /tmp - paths: - - release - - save_cache: - paths: - - ~/.m2 - - ~/graalvm-ce-java11-22.0.0.2 - 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 || true - linux-aarch64: - machine: - enabled: true - image: ubuntu-2004:202101-01 - resource_class: arm.large - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - GRAALVM_VERSION: "22.0.0.2" - GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.0.0.2 - BABASHKA_PLATFORM: linux # used in release script - BABASHKA_ARCH: aarch64 - BABASHKA_TEST_ENV: native - BABASHKA_XMX: "-J-Xmx6500m" - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - - run: - name: Install Clojure - command: | - sudo script/install-clojure - - restore_cache: - keys: - - linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - run: - name: Install native dev tools - command: | - sudo apt-get update - sudo apt-get -y install build-essential zlib1g-dev - - run: - name: Download GraalVM - command: | - script/install-graalvm - - run: - name: Build binary - command: | - script/uberjar - script/compile - no_output_timeout: 30m - - run: - name: Run tests - command: | - script/test - script/run_lib_tests - - run: - name: Release - command: | - .circleci/script/release - - persist_to_workspace: - root: /tmp - paths: - - release - - save_cache: - paths: - - ~/.m2 - - ~/graalvm-ce-java11-22.0.0.2 - key: linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - store_artifacts: - path: /tmp/release - destination: release - - run: - name: Publish artifact link to Slack - command: | - ./bb .circleci/script/publish_artifact.clj || true - linux-aarch64-static: - machine: - enabled: true - image: ubuntu-2004:202101-01 - resource_class: arm.large - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - GRAALVM_VERSION: "22.0.0.2" - GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.0.0.2 - BABASHKA_PLATFORM: linux # used in release script - BABASHKA_ARCH: aarch64 - BABASHKA_TEST_ENV: native - BABASHKA_XMX: "-J-Xmx6500m" - BABASHKA_STATIC: "true" - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - # - run: - # name: "Short circuit on SNAPSHOT" - # command: | - # VERSION=$(cat resources/BABASHKA_VERSION) - # if [[ "$VERSION" == *-SNAPSHOT ]] - # then - # circleci task halt - # fi - - run: - name: Install Clojure - command: | - sudo script/install-clojure - - restore_cache: - keys: - - linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - run: - name: Install native dev tools - command: | - sudo apt-get update - sudo apt-get -y install build-essential zlib1g-dev - # sudo -E script/setup-musl - - run: - name: Download GraalVM - command: | - script/install-graalvm - - run: - name: Build binary - command: | - script/uberjar - script/compile - no_output_timeout: 30m - - run: - name: Run tests - command: | - script/test - script/run_lib_tests - - run: - name: Release - command: | - .circleci/script/release - - persist_to_workspace: - root: /tmp - paths: - - release - - save_cache: - paths: - - ~/.m2 - - ~/graalvm-ce-java11-22.0.0.2 - key: linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - store_artifacts: - path: /tmp/release - destination: release - - run: - name: Publish artifact link to Slack - command: | - ./bb .circleci/script/publish_artifact.clj || true - mac: - macos: - xcode: "12.0.0" - environment: - MACOSX_DEPLOYMENT_TARGET: 10.13 # 10.12 is EOL - GRAALVM_VERSION: "22.0.0.2" - GRAALVM_HOME: /Users/distiller/graalvm-ce-java11-22.0.0.2/Contents/Home - BABASHKA_PLATFORM: macos # used in release script - BABASHKA_TEST_ENV: native - BABASHKA_XMX: "-J-Xmx6500m" - resource_class: large - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - - restore_cache: - keys: - - mac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - run: - name: Install Clojure - command: | - script/install-clojure - - run: - name: Install Leiningen - command: | - script/install-leiningen - - run: - name: Download GraalVM - command: | - script/install-graalvm - - run: - name: Build binary - command: | - export PATH=$GRAALVM_HOME/bin:$PATH - script/uberjar - script/compile - no_output_timeout: 30m - - run: - name: Run tests - command: | - export PATH=$GRAALVM_HOME/bin:$PATH - script/test - script/run_lib_tests - - run: - name: Release - command: | - .circleci/script/release - - save_cache: - paths: - - ~/.m2 - - ~/graalvm-ce-java11-22.0.0.2/Contents/Home - key: mac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} - - store_artifacts: - path: /tmp/release - destination: release - - run: - name: Publish artifact link to Slack - command: | - ./bb .circleci/script/publish_artifact.clj || true - deploy: - resource_class: large - docker: - - image: circleci/clojure:lein-2.9.6 - working_directory: ~/repo - environment: - LEIN_ROOT: "true" - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule init - git submodule update - - restore_cache: - keys: - - v1-dependencies-{{ checksum "project.clj" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: .circleci/script/deploy - - save_cache: - paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "project.clj" }} - docker: - machine: - image: ubuntu-2004:202111-01 - steps: - - checkout - - setup-docker-buildx - - attach_workspace: - at: /tmp - - run: - name: Build Docker image - environment: - PLATFORM: linux/amd64,linux/arm64 - command: .circleci/script/docker +# this allows you to use CircleCI's dynamic configuration feature +setup: true + +# the continuation orb is required in order to use dynamic configuration +orbs: + continuation: circleci/continuation@0.1.2 + +# our defined job, and its steps +jobs: + setup: + docker: + - image: cimg/clojure:1.11.1 + steps: + - checkout + - run: + name: Bootstrap Babashka + command: | + curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install + sudo bash install --dir /tmp + - run: + name: Rename bb binary + command: mv /tmp/bb /tmp/bbb + - run: + name: Generate config + command: | + /tmp/bbb .circleci/script/short_ci.clj > generated_config.yml + - continuation/continue: + configuration_path: generated_config.yml + +# our single workflow, that triggers the setup job defined above workflows: - version: 2 - ci: + setup: jobs: - - jvm - - linux - - linux-static - - mac - - linux-aarch64 - - linux-aarch64-static - - deploy: - filters: - branches: - only: master - requires: - - jvm - - linux - - mac - - docker: - filters: - branches: - only: - - master - requires: - - linux - - linux-static - - linux-aarch64 + - setup diff --git a/.circleci/script/docker b/.circleci/script/docker deleted file mode 100755 index f16447b6..00000000 --- a/.circleci/script/docker +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -set -eo pipefail - -image_name="babashka/babashka" -image_tag=$(cat resources/BABASHKA_VERSION) -platform=${PLATFORM:-"linux/amd64"} -latest_tag="latest" -label_args=("--label" "'org.opencontainers.image.description=Native, fast starting Clojure interpreter for scripting'" - "--label" "org.opencontainers.image.title=Babashka" - "--label" "org.opencontainers.image.created=$(date -Iseconds)" - "--label" "org.opencontainers.image.url=${CIRCLE_REPOSITORY_URL}" - "--label" "org.opencontainers.image.documentation=${CIRCLE_REPOSITORY_URL}" - "--label" "org.opencontainers.image.source=${CIRCLE_REPOSITORY_URL}" - "--label" "org.opencontainers.image.revision=${CIRCLE_SHA1}" - "--label" "org.opencontainers.image.ref.name=${CIRCLE_TAG}:${CIRCLE_BRANCH}" - "--label" "org.opencontainers.image.version=${image_tag}") - -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 "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then - echo "Building & pushing $platform Docker image(s) $image_name:$image_tag" - echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin - IFS=',' read -r -a platforms <<< "$platform" - for p in "${platforms[@]}"; do - tarball_platform=${p//\//-} - if [[ $tarball_platform == "linux-arm64" ]]; then tarball_platform="linux-aarch64"; fi - mkdir -p $p - tar zxvf "/tmp/release/babashka-${image_tag}-${tarball_platform}.tar.gz" -C $p - # this overwrites, but this is to work around having built the uberjar/metabom multiple times - cp "/tmp/release/${tarball_platform}-metabom.jar" ./metabom.jar - done - docker buildx build -t "$image_name:$image_tag" --platform "$platform" "${label_args[@]}" --push -f Dockerfile.ci . - if [[ $snapshot == "false" ]]; then - echo "Building & pushing $platform Docker image(s) $image_name:$latest_tag" - docker buildx build -t "$image_name:$latest_tag" --platform "$platform" "${label_args[@]}" --push -f Dockerfile.ci . - fi - for p in "${platforms[@]}"; do - rm -rf $p - done - - # build alpine image for linux-amd64 only (no upstream arm64 support yet) - tar zxvf "/tmp/release/babashka-${image_tag}-linux-amd64-static.tar.gz" - echo "Building & pushing Docker image $image_name:$image_tag-alpine" - docker buildx build -t "$image_name:$image_tag-alpine" --platform=linux/amd64 "${label_args[@]}" --push -f Dockerfile.alpine . - - if [[ $snapshot == "false" ]]; then - echo "Building & pushing Docker image $image_name:alpine" - docker buildx build -t "$image_name:alpine" --platform=linux/amd64 "${label_args[@]}" --push -f Dockerfile.alpine . - fi -else - echo "Not publishing Docker image" -fi - -exit 0; diff --git a/.circleci/script/docker.clj b/.circleci/script/docker.clj new file mode 100644 index 00000000..a884c893 --- /dev/null +++ b/.circleci/script/docker.clj @@ -0,0 +1,98 @@ +(require '[clojure.string :as str] + '[babashka.process :as proc] + '[babashka.fs :as fs]) +(import '[java.time Instant]) + +(defn read-env + ([k] + (read-env k nil)) + ([k default] + (or (System/getenv k) + default))) + +(def image-name "babashka/babashka") + +(def image-tag (slurp "resources/BABASHKA_VERSION")) + +(def latest-tag "latest") + +(def platforms (read-env "PLATFORMS" "linux/amd64")) + +(def circle-repository-url (read-env "CIRCLE_REPOSITORY_URL")) + +(def label-args + ["--label" "'org.opencontainers.image.description=Native, fast starting Clojure interpreter for scripting'" + "--label" "org.opencontainers.image.title=Babashka" + "--label" (str "org.opencontainers.image.created=" (Instant/now)) + "--label" (str "org.opencontainers.image.url=" circle-repository-url) + "--label" (str "org.opencontainers.image.documentation=" circle-repository-url) + "--label" (str "org.opencontainers.image.source=" circle-repository-url) + "--label" (str "org.opencontainers.image.revision=" (read-env "CIRCLE_SHA1")) + "--label" + (format "org.opencontainers.image.ref.name=%s:%s" + (read-env "CIRCLE_TAG") + (read-env "CIRCLE_BRANCH")) + "--label" (str "org.opencontainers.image.version=" image-tag)]) + +(def snapshot? (str/includes? image-tag "SNAPSHOT")) + +(defn exec + [cmd] + (-> cmd + (proc/process {:out :inherit :err :inherit}) + (proc/check))) + +(defn docker-login + [username password] + (exec ["docker" "login" "-u" username "-p" password])) + +(defn build-push + [image-tag platform docker-file] + (println (format "Building and pushing %s Docker image(s) %s:%s" + platform + image-name + image-tag)) + (let [base-cmd ["docker" "buildx" "build" + "-t" (str image-name ":" image-tag) + "--platform" platform + "--push" + "-f" docker-file]] + (exec (concat base-cmd label-args ["."])))) + +(defn build-push-images + [] + (doseq [platform (str/split platforms #",")] + (let [tarball-platform (str/replace platform #"\/" "-") + tarball-platform (if (= "linux-arm64" tarball-platform) + "linux-aarch64" + tarball-platform) + tarball-path (format "/tmp/release/babashka-%s-%s.tar.gz" + image-tag + tarball-platform)] + (fs/create-dirs platform) + (exec ["tar" "zxvf" tarball-path "-C" platform]) + ; this overwrites, but this is to work around having built the uberjar/metabom multiple times + (fs/copy (format "/tmp/release/%s-metabom.jar" tarball-platform) "metabom.jar" {:replace-existing true}))) + (build-push image-tag platforms "Dockerfile.ci") + (when-not snapshot? + (build-push latest-tag platforms "Dockerfile.ci"))) + +(defn build-push-alpine-images + "Build alpine image for linux-amd64 only (no upstream arm64 support yet)" + [] + (exec ["tar" "zxvf" (str "/tmp/release/babashka-" image-tag "-linux-amd64-static.tar.gz")]) + (build-push (str image-tag "-alpine") "linux/amd64" "Dockerfile.alpine") + (when-not snapshot? + (build-push "alpine" "linux/amd64" "Dockerfile.alpine"))) + +(when (= *file* (System/getProperty "babashka.file")) + (if (and (nil? (read-env "CIRCLE_PULL_REQUEST")) + (= "master" (read-env "CIRCLE_BRANCH"))) + (do + (if snapshot? + (println "This is a snapshot version") + (println "This is a non-snapshot version")) + (docker-login (read-env "DOCKERHUB_USER") (read-env "DOCKERHUB_PASS")) + (build-push-images) + (build-push-alpine-images)) + (println "Not publishing docker image(s)."))) diff --git a/.circleci/script/short_ci.clj b/.circleci/script/short_ci.clj new file mode 100644 index 00000000..f467f36b --- /dev/null +++ b/.circleci/script/short_ci.clj @@ -0,0 +1,276 @@ +(ns short-ci + (:require + [babashka.tasks :as tasks] + [clj-yaml.core :as yaml] + [clojure.string :as str] + [flatland.ordered.map :refer [ordered-map]])) + +(defn run + ([cmd-name cmd] + (run cmd-name cmd nil)) + ([cmd-name cmd no-output-timeout] + (let [base {:run {:name cmd-name + :command cmd}}] + (if no-output-timeout + (assoc-in base [:run :no_output_timeout] no-output-timeout) + base)))) + +(defn gen-steps + [shorted? steps] + (if shorted? + [(run "Shorted" "echo 'Skipping Run'")] + steps)) + +(defn gen-job + [shorted? conf] + (if shorted? + (-> conf + (dissoc :machine :macos) + (assoc :resource_class "small" :docker [{:image "ubuntu:latest"}])) + conf)) + +(defn pull-submodules + [] + (run "Pull Submodules" "git submodule init\ngit submodule update")) + +(defn deploy + [shorted?] + (gen-job shorted? + (ordered-map + :resource_class "large" + :docker [{:image "circleci/clojure:lein-2.9.8"}] + :working_directory "~/repo" + :environment {:LEIN_ROOT "true"} + :steps (gen-steps + shorted? + [:checkout + (pull-submodules) + {:restore_cache {:keys ["v1-dependencies-{{ checksum \"project.clj\" }}" + "v1-dependencies-"]}} + {:run ".circleci/script/deploy"} + {:save_cache {:paths ["~/.m2"] + :key "v1-dependencies-{{ checksum \"project.clj\" }}"}}])))) + +(defn docker + [shorted?] + (gen-job + shorted? + (ordered-map + :machine {:image "ubuntu-2004:202111-01"} + :steps + (gen-steps + shorted? + [:checkout + (pull-submodules) + "setup-docker-buildx" + {:attach_workspace {:at "/tmp"}} + (run "Build uberjar" "script/uberjar") + {:run + {:name "Build Docker image" + :environment {:PLATFORMS "linux/amd64,linux/arm64"} + :command + "java -jar ./target/babashka-$(cat resources/BABASHKA_VERSION)-standalone.jar .circleci/script/docker.clj"}}])))) + +(defn jvm + [shorted? graalvm-home] + (gen-job + shorted? + (ordered-map + :docker [{:image "circleci/clojure:openjdk-11-lein-2.9.8-bullseye"}] + :working_directory "~/repo" + :environment {:LEIN_ROOT "true" + :BABASHKA_PLATFORM "linux" + :GRAALVM_VERSION "22.3.0" + :GRAALVM_HOME graalvm-home} + :resource_class "large" + :steps + (gen-steps + shorted? + [:checkout + (pull-submodules) + {:restore_cache {:keys ["v1-dependencies-{{ checksum \"project.clj\" }}-{{ checksum \"deps.edn\" }}" + "v1-dependencies-"]}} + (run "Install Clojure" "sudo script/install-clojure") + (run "Download GraalVM" "script/install-graalvm") + (run + "Run JVM tests" + "export BABASHKA_FEATURE_JDBC=true +export BABASHKA_FEATURE_POSTGRESQL=true +script/test\nscript/run_lib_tests") + (run "Run as lein command" ".circleci/script/lein") + (run + "Create uberjar" + "mkdir -p /tmp/release +script/uberjar +VERSION=$(cat resources/BABASHKA_VERSION) +jar=target/babashka-$VERSION-standalone.jar +cp $jar /tmp/release +export PATH=$GRAALVM_HOME/bin:$PATH +export JAVA_HOME=$GRAALVM_HOME +java -jar $jar script/reflection.clj +reflection=\"babashka-$VERSION-reflection.json\" +java -jar \"$jar\" --config .build/bb.edn --deps-root . release-artifact \"$jar\" +java -jar \"$jar\" --config .build/bb.edn --deps-root . release-artifact \"$reflection\"") + {:store_artifacts {:path "/tmp/release" + :destination "release"}} + {:save_cache {:paths ["~/.m2"] + :key "v1-dependencies-{{ checksum \"project.clj\" }}-{{ checksum \"deps.edn\" }}"}}])))) + +(defn unix + [shorted? static? musl? arch executor-conf resource-class graalvm-home platform] + (let [env {:LEIN_ROOT "true" + :GRAALVM_VERSION "22.3.0" + :GRAALVM_HOME graalvm-home + :BABASHKA_PLATFORM (if (= "mac" platform) + "macos" + platform) + :BABASHKA_TEST_ENV "native" + :BABASHKA_XMX "-J-Xmx6500m"} + env (if (= "aarch64" arch) + (assoc env :BABASHKA_ARCH arch) + env) + env (if static? + (assoc env :BABASHKA_STATIC "true") + env) + env (if musl? + (assoc env :BABASHKA_MUSL "true") + env) + env (if (= "mac" platform) + (assoc env :MACOSX_DEPLOYMENT_TARGET 10.13) + env) + base-install-cmd "sudo apt-get update\nsudo apt-get -y install build-essential zlib1g-dev" + cache-key (format "%s-%s{{ checksum \"project.clj\" }}-{{ checksum \".circleci/config.yml\" }}" + platform + (if (= "aarch64" arch) + "aarch64-" + ""))] + (gen-job shorted? + (merge + executor-conf + (ordered-map + :working_directory "~/repo" + :environment env + :resource_class resource-class + :steps (gen-steps shorted? + (filter some? + [:checkout + {:attach_workspace {:at "/tmp"}} + (run "Pull Submodules" "git submodule init\ngit submodule update") + {:restore_cache + {:keys [cache-key]}} + (run "Install Clojure" "sudo script/install-clojure") + (when (= "mac" platform) + (run "Install Leiningen" "script/install-leiningen")) + (when (not= "mac" platform) + (run "Install native dev tools" + (if (and static? musl? (not= "aarch64" arch)) + (str base-install-cmd "\nsudo -E script/setup-musl") + base-install-cmd))) + (run "Download GraalVM" "script/install-graalvm") + (run "Build binary" "script/uberjar\nscript/compile" "30m") + (run "Run tests" "script/test\nscript/run_lib_tests") + (run "Release" ".circleci/script/release") + {:persist_to_workspace {:root "/tmp" + :paths ["release"]}} + {:save_cache + {:paths ["~/.m2" "~/graalvm-ce-java11-22.3.0"] + :key cache-key}} + {:store_artifacts {:path "/tmp/release" + :destination "release"}} + (run "Publish artifact link to Slack" + "./bb .circleci/script/publish_artifact.clj || true")]))))))) + +(defn make-config + [shorted?] + (let [docker-executor-conf {:docker [{:image "circleci/clojure:openjdk-11-lein-2.9.8-bullseye"}]} + machine-executor-conf {:machine {:image "ubuntu-2004:202111-01"}} + mac-executor-conf {:macos {:xcode "14.0.0"}} + linux-graalvm-home "/home/circleci/graalvm-ce-java11-22.3.0" + mac-graalvm-home "/Users/distiller/graalvm-ce-java11-22.3.0/Contents/Home"] + (ordered-map + :version 2.1 + :commands + {:setup-docker-buildx + {:steps + [{:run + {:name "Create multi-platform capabale buildx builder" + :command + "docker run --privileged --rm tonistiigi/binfmt --install all\ndocker buildx create --name ci-builder --use"}}]}} + :jobs (ordered-map + :jvm (jvm shorted? linux-graalvm-home) + :linux (unix shorted? false false "amd64" docker-executor-conf "large" linux-graalvm-home "linux") + :linux-static + (unix shorted? true true "amd64" docker-executor-conf "large" linux-graalvm-home "linux") + :linux-aarch64 (unix shorted? + false + false + "aarch64" + machine-executor-conf + "arm.large" + linux-graalvm-home + "linux") + :linux-aarch64-static + (unix shorted? true false "aarch64" machine-executor-conf "arm.large" linux-graalvm-home "linux") + :mac (unix shorted? false false "amd64" mac-executor-conf "large" mac-graalvm-home "mac") + :deploy (deploy shorted?) + :docker (docker shorted?)) + :workflows (ordered-map + :version 2 + :ci {:jobs ["jvm" + "linux" + "linux-static" + "mac" + "linux-aarch64" + "linux-aarch64-static" + {:deploy {:filters {:branches {:only "master"}} + :requires ["jvm" "linux"]}} + {:docker {:filters {:branches {:only "master"}} + :requires ["linux" "linux-static" "linux-aarch64"]}}]})))) + +(def skip-config + {:skip-if-only [#".*.md$" + #"^logo\/.*$"]}) + +(defn get-changes + [] + (-> (tasks/shell {:out :string} "git diff --name-only HEAD~1") + (:out) + (str/split-lines))) + +(defn irrelevant-change? + [change regexes] + (some? (some #(re-matches % change) regexes))) + +(defn relevant? + [change-set regexes] + (some? (some #(not (irrelevant-change? % regexes)) change-set))) + +(defn main + [] + (let [{:keys [skip-if-only]} skip-config + changed-files (get-changes) + conf (make-config (not (relevant? changed-files skip-if-only)))] + (println (yaml/generate-string conf + :dumper-options + {:flow-style :block})))) + +(when (= *file* (System/getProperty "babashka.file")) + (main)) + +(comment + (main) + + (def regexes + [#".*.md$" + #".*.clj$" ; ignore clojure files + #"^logo\/.*$"]) + + (:out (tasks/shell {:out :string} "ls")) + + (irrelevant-change? "src/file.png" regexes) + + (re-matches #".*.clj$" "src/file.clj.dfff") + + (re-matches #"^logo\/.*$" "logo/foo/bar.jpg") + + (relevant? ["src/file.clj"] regexes)) diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..34ebcced --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,39 @@ +macos_instance: + image: ghcr.io/cirruslabs/macos-monterey-base:latest + +task: + skip: "changesIncludeOnly('logo/*', '**.md')" + env: + LEIN_ROOT: "true" + GRAALVM_VERSION: "22.3.0" + GRAALVM_HOME: ${HOME}/graalvm-ce-java11-22.3.0/Contents/Home + BABASHKA_PLATFORM: macos # used in release script + BABASHKA_ARCH: aarch64 + BABASHKA_TEST_ENV: native + BABASHKA_XMX: "-J-Xmx6500m" + GITHUB_TOKEN: ENCRYPTED[d6ff8cdc392157f211c754fa0763875434d1bfde0c00a05e48ba9470003a76c14c9213adb80623f81e13f2f0fa8fbd57] + script: | + git submodule init + git submodule update + + sudo script/install-clojure + sudo script/install-leiningen + script/install-graalvm + export PATH=$GRAALVM_HOME/bin:$PATH + export JAVA_HOME=$GRAALVM_HOME + sudo /usr/sbin/softwareupdate --install-rosetta --agree-to-license + java -version + + script/uberjar + script/compile + + # script/test + # script/run_lib_tests + + VERSION=$(cat resources/BABASHKA_VERSION) + arch=${BABASHKA_ARCH:-amd64} + archive="babashka-$VERSION-$BABASHKA_PLATFORM-$arch.tar.gz" + tar zcvf "$archive" bb + ./bb --config .build/bb.edn --deps-root . release-artifact "$archive" || true + binaries_artifacts: + path: "babashka-*.tar.gz" diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 2f0de4a9..2e9b6f40 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -5,4 +5,5 @@ 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-required-namespaces {:level :warning}}} + :linters {:unsorted-required-namespaces {:level :warning}} + :hooks {:analyze-call {clojure.core/requiring-resolve hooks.mine/req-resolve}}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57b20f95..66e1f6ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,82 +2,77 @@ name: build on: push: + paths-ignore: + - "**.md" + - "logo/**" + branches: + - master + pull_request: + paths-ignore: + - "**.md" + - "logo/**" branches: - master +# TODO: Add deploy if needed jobs: - - scratch: - if: "!contains(github.event.head_commit.message, 'skip ci')" - 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: - if: "!contains(github.event.head_commit.message, 'skip ci')" - # ubuntu 18.04 comes with lein + java8 installed - runs-on: ubuntu-18.04 + if: ${{ false }} # Disabled + # if: "!contains(github.event.head_commit.message, 'skip ci')" + runs-on: ubuntu-latest + env: + LEIN_ROOT: "true" + BABASHKA_PLATFORM: linux # could be used in jar name + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Git checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 submodules: 'true' - name: Cache deps - uses: actions/cache@v1 + uses: actions/cache@v2 id: cache-deps with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} - restore-keys: | - ${{ runner.os }}-maven- + restore-keys: ${{ runner.os }}-maven- - - name: Cache GraalVM - uses: actions/cache@v1 - id: cache-graalvm + - name: Prepare java + uses: actions/setup-java@v2 with: - path: ~/graalvm-ce-java11-22.0.0.2 - key: ${{ runner.os }}-graalvm-22.0.0.2 - restore-keys: | - ${{ runner.os }}-graalvm-22.0.0.2 + distribution: 'adopt-hotspot' + java-version: '11' - - name: Download GraalVM - run: | - cd ~ - if ! [ -d graalvm-ce-java11-22.0.0.2 ]; then - curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - tar xzf graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - fi - - - name: Fetch deps - if: steps.cache-deps.outputs.cache-hit != 'true' - run: | - lein deps + - name: Install clojure tools + uses: DeLaGuardo/setup-clojure@5.0 + with: + cli: 1.10.3.1040 + lein: 2.9.8 - name: Run tests + env: + BABASHKA_FEATURE_JDBC: "true" + BABASHKA_FEATURE_POSTGRESQL: "true" run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" script/test - - - name: Test libraries - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" - sudo script/install-clojure script/run_lib_tests + - name: Run as lein command + run: echo '{:a 1}' | lein bb '(:a *in*)' + - name: Build uberjar run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" + mkdir -p /tmp/release script/uberjar + VERSION=$(cat resources/BABASHKA_VERSION) + jar=target/babashka-$VERSION-standalone.jar + cp $jar /tmp/release + java -jar $jar script/reflection.clj + reflection="babashka-$VERSION-reflection.json" + java -jar "$jar" --config .build/bb.edn --deps-root . release-artifact "$jar" + java -jar "$jar" --config .build/bb.edn --deps-root . release-artifact "$reflection" - name: Babashka version id: babashka-version @@ -87,50 +82,76 @@ jobs: - uses: actions/upload-artifact@v1 with: - name: jar + name: babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar path: target/babashka-${{ steps.babashka-version.outputs.version }}-standalone.jar - linux: + native: if: "!contains(github.event.head_commit.message, 'skip ci')" - needs: [jvm] - runs-on: ubuntu-18.04 + strategy: + matrix: + include: + - os: macos-12 + name: macos + static: false + #- os: ubuntu-latest + # name: linux + # static: false + #- os: ubuntu-latest + # name: linux + # static: true + runs-on: ${{ matrix.os }} + env: + LEIN_ROOT: "true" + GRAALVM_VERSION: "22.3.0" + BABASHKA_PLATFORM: ${{ matrix.name }} # used in release script + BABASHKA_TEST_ENV: native + BABASHKA_XMX: "-J-Xmx6500m" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Git checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 submodules: 'true' - - uses: actions/download-artifact@v1 - with: - name: jar - path: . - - name: Cache deps - uses: actions/cache@v1 + uses: actions/cache@v2 id: cache-deps with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} - restore-keys: | - ${{ runner.os }}-maven- + restore-keys: ${{ runner.os }}-maven- - - name: Cache GraalVM - uses: actions/cache@v1 - id: cache-graalvm + - name: Setup GraalVM + if: "matrix.static == false" + uses: graalvm/setup-graalvm@v1 with: - path: ~/graalvm-ce-java11-22.0.0.2 - key: ${{ runner.os }}-graalvm-22.0.0.2 - restore-keys: | - ${{ runner.os }}-graalvm-22.0.0.2 + version: '22.3.0' + java-version: '11' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download GraalVM + - name: Setup GraalVM+musl + if: "matrix.static == true" + uses: graalvm/setup-graalvm@v1 + with: + version: '22.3.0' + java-version: '11' + components: 'native-image' + native-image-musl: true + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install clojure tools + uses: DeLaGuardo/setup-clojure@5.0 + with: + cli: 1.10.3.1040 + lein: 2.9.8 + + - name: Install native dev tools + if: matrix.os == 'ubuntu-latest' run: | - cd ~ - if ! [ -d graalvm-ce-java11-22.0.0.2 ]; then - curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - tar xzf graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - fi + sudo apt-get update + sudo apt-get -y install build-essential zlib1g-dev - name: Babashka version id: babashka-version @@ -138,209 +159,50 @@ jobs: 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-java11-22.0.0.2" - script/compile + - name: Build uberjar + run: script/uberjar - - name: Test binary - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" - BABASHKA_TEST_ENV=native script/test + - name: Build native image + if: "matrix.static == false" + run: script/compile - - name: Install clojure - run: | - sudo script/install-clojure /usr/local - - - name: Test libraries - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" - BABASHKA_TEST_ENV=native script/run_lib_tests - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - path: bb - name: babashka-${{ steps.babashka-version.outputs.version }}-linux-amd64.zip - - linux-static: - if: "!contains(github.event.head_commit.message, 'skip ci')" - 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: . - - - 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-java11-22.0.0.2 - key: ${{ runner.os }}-graalvm-22.0.0.2 - restore-keys: | - ${{ runner.os }}-graalvm-22.0.0.2 - - - name: Download GraalVM - run: | - cd ~ - if ! [ -d graalvm-ce-java11-22.0.0.2 ]; then - curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - tar xzf graalvm-ce-java11-linux-amd64-22.0.0.2.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-java11-22.0.0.2" - export BABASHKA_STATIC=true - script/compile - - - name: Test binary - run: | - ./bb '(+ 1 2 3)' - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" - BABASHKA_TEST_ENV=native script/test - - - name: Install clojure - run: | - sudo script/install-clojure - - - name: Test libraries - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2" - BABASHKA_TEST_ENV=native script/run_lib_tests - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - path: bb - name: babashka-${{ steps.babashka-version.outputs.version }}-linux-static-amd64.zip - - mac: - if: "!contains(github.event.head_commit.message, 'skip ci')" - 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: . - - - name: Cache GraalVM - uses: actions/cache@v1 - id: cache-graalvm - with: - path: ~/graalvm-ce-java11-22.0.0.2 - key: ${{ runner.os }}-graalvm-22.0.0.2 - restore-keys: | - ${{ runner.os }}-graalvm-22.0.0.2 - - - name: Download GraalVM - run: | - cd ~ - if ! [ -d graalvm-ce-java11-22.0.0.2 ]; then - curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-darwin-amd64-22.0.0.2.tar.gz - tar xzf graalvm-ce-java11-darwin-amd64-22.0.0.2.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-java11-22.0.0.2/Contents/Home" - script/compile - - - name: Test binary - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2/Contents/Home" - sudo script/install-leiningen - BABASHKA_TEST_ENV=native script/test - - - name: Test libraries - run: | - export GRAALVM_HOME="$HOME/graalvm-ce-java11-22.0.0.2/Contents/Home" - 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: - if: "!contains(github.event.head_commit.message, 'skip ci') && github.event_name == 'push' && github.ref == 'refs/heads/master'" - needs: [jvm, linux, linux-static, mac] - 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 + - name: Build static native image + if: "matrix.static == true" env: - CLOJARS_USER: "${{ secrets.CLOJARS_USER }}" - CLOJARS_PASS: "${{ secrets.CLOJARS_PASS }}" + BABASHKA_STATIC: "true" + BABASHKA_MUSL: "true" + run: script/compile + + - name: Test binary and libs run: | - .github/script/deploy + script/test + script/run_lib_tests + + - name: Release + run: .circleci/script/release + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + path: bb + name: babashka-${{ steps.babashka-version.outputs.version }}-${{ matrix.name }}-amd64 + + - name: Upload static artifact + if: "matrix.static == true" + uses: actions/upload-artifact@v2 + with: + path: bb + name: babashka-${{ steps.babashka-version.outputs.version }}-${{ matrix.name }}-static-amd64 docker: - if: "!contains(github.event.head_commit.message, 'skip ci') && github.event_name == 'push' && github.ref == 'refs/heads/master'" + if: ${{ false }} # Disabled + # if: "!contains(github.event.head_commit.message, 'skip ci') && github.event_name == 'push' && github.ref == 'refs/heads/master'" needs: - - linux - - linux-static - runs-on: ubuntu-18.04 + - native + runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 submodules: 'true' @@ -366,9 +228,16 @@ jobs: name: babashka-${{ steps.babashka-version.outputs.version }}-linux-static-amd64.zip path: /tmp/release-static + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Build uberjar + run: script/uberjar + - name: Docker build env: DOCKERHUB_USER: "${{ secrets.DOCKERHUB_USER }}" DOCKERHUB_PASS: "${{ secrets.DOCKERHUB_PASS }}" - run: | - .github/script/docker + PLATFORMS: linux/amd64,linux/arm64 + run: java -jar ./target/babashka-$(cat resources/BABASHKA_VERSION)-standalone.jar .circleci/script/docker.clj diff --git a/.gitignore b/.gitignore index fb29f608..284c61e2 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ org_babashka*.h .envrc .lsp bb.build_artifacts.txt +target +.nrepl-port +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c9a92b..473540ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,241 @@ For a list of breaking changes, check [here](#breaking-changes). A preview of the next release can be installed from [babashka-dev-builds](https://github.com/babashka/babashka-dev-builds). +[Babashka](https://github.com/babashka/babashka): Native, fast starting Clojure interpreter for scripting + +## Unreleased + +- Implement `ns`, `lazy-seq` as macro +- Support `--dev-build` flag in installation script +- [#1451](https://github.com/babashka/babashka/issues/1451): Allow passing explicit file and line number to clojure.test ([@matthewdowney](https://github.com/matthewdowney)) +- [#1280](https://github.com/babashka/babashka/issues/1280): babashka REPL doesn't quit right after Ctrl-d ([@formerly-a-trickster](https://github.com/formerly-a-trickster) and Alice Margatroid) +- [#1446](https://github.com/babashka/babashka/issues/1446): add `pprint/code-dispatch` +- Update zlib to version `1.2.13` ([@thiagokokada](https://github.com/thiagokokada)) +- [#1454](https://github.com/babashka/babashka/issues/1454): Add `babashka.process` to `print-deps` output + +## 1.0.168 (2022-12-07) + +- `loop*`, `fn*` are now special forms and `loop`, `fn`, `defn`, `defmacro`, `and` and `or` are implemented as macros. This restores compatibility with [rcf](https://github.com/borkdude/hyperfiddle-rcf) +- fs: don't touch dirs in `split-ext` +- Update to babashka process to v0.4.13: support `(process opts? & args)` syntax everywhere +- [#1438](https://github.com/babashka/babashka/issues/1438): expose `query-string` and `url-encode` functions from org.httpkit.client ([@bobisageek](https://github.com/bobisageek)) +- Add `java.util.InputMismatchException` + +## 1.0.167 (2022-11-30) + +- [#1433](https://github.com/babashka/babashka/issues/1433): spec source as built-in fallback. When not including the + [clojure.spec.alpha](https://github.com/babashka/spec.alpha) fork as a + library, babashka loads a bundled version, when `clojure.spec.alpha` is required. +- [#1430](https://github.com/babashka/babashka/issues/1430): Fix issue with `bb tasks` throwing on empty display tasks list. +- Add note about BSOD when using WSL1, see [README.md/quickstart](https://github.com/LouDnl/babashka#quickstart) +- SCI: performance improvements +- Better error locations for interop ([@bobisageek](https://github.com/bobisageek)) +- Fix [babashka/babashka.nrepl#59](https://github.com/babashka/babashka.nrepl/issues/59): do not output extra new line with cider pprint +- Use `namespace-munge` instead of `munge` for doing ns -> file lookup + +## 1.0.166 (2022-11-24) + +See the [Testing babashka scripts](https://blog.michielborkent.nl/babashka-test-runner.html) blog post for how to run tests with this release. + +- Compatibility with Cognitest [test-runner](https://github.com/cognitect-labs/test-runner) and [tools.namespace](https://github.com/clojure/tools.namespace) +- Add `run-test` and `run-test-var` to `clojure.test` +- Compile distributed uberjar using GraalVM, fixes `babashka.process/exec` for Nix +- [#1414](https://github.com/babashka/babashka/issues/1414): preserve metadata on exec task function argument map +- [#1269](https://github.com/babashka/babashka/issues/1269): add lib tests for sluj ([@bobisageek](https://github.com/bobisageek)) +- Update nix app example in docs +- Add `java.lang.Error` and `java.net.URLClassLoader` (only for compatibility with the `clojure.java.classpath` lib) +- Deps.clj `deps.clj: 1.11.1.1200` +- Upgrade timbre to `6.0.1` +- Performance improvements in SCI +- SCI: preserve stack information on `throw` expressions + +## 1.0.165 (2022-11-01) + +- Fix [#1401](https://github.com/babashka/babashka/issues/1401): mutation of `deftype` field should be visible in protocol method +- Fix [#1405](https://github.com/babashka/babashka/issues/1405): drop name metadata from conditionally defined var +- [#602](https://github.com/babashka/babashka/issues/602): add lib tests for clj-commons/fs ([@bobisageek](https://github.com/bobisageek)) +- Add `java.net.URLConnection` class +- Add `java.time.zone.ZoneRules` class +- Copy more docstrings for core macros and vars +- Bump `core.async` to `1.6.673` +- Implement `in-ns` as function, rather than special form ([@SignSpice](https://github.com/SignSpice)) +- Bump `deps.clj` to `1.11.1.1182` +- Bump GraalVM to `22.3.0` +- SCI: don't rely on metadata for record implementation + +## 1.0.164 (2022-10-17) + +- SCI: optimizations for `let` +- Add single argument read method support to PipedInputStream proxy ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- feat: Honor `*print-namespace-maps*` in pprint ([@ghoseb](https://github.com/ghoseb)) +- [#1369](https://github.com/babashka/babashka/issues/1369): provide `.sha256` files for every released asset +- [#1397](https://github.com/babashka/babashka/issues/1397): Add `clojure.lang.Namespace` as alias for `sci.lang.Namespace`, such that `(instance? clojure.lang.Namespace *ns*)` returns `true` in bb +- [#1384](https://github.com/babashka/babashka/issues/1384): allow `.indexOf` on `LazySeq` +- [#1330](https://github.com/babashka/babashka/issues/1330): allow `(set! *warn-on-reflection*)` in programmatic nREPL +- Better error message when failing to load `bb.edn` ([@lispyclouds](https://github.com/lispyclouds)) +- Pods: print and flush to `*out*` and `*err*` instead of using `println` ([@justone](https://github.com/justone)) +- deps.clj: support for `CLJ_JVM_OPTS` and `JAVA_OPTS` ([@ikappaki](https://github.com/ikappaki)) +- Fix `*print-namespace-maps*` when using `clojure.pprint` ([@ghoseb](https://github.com/ghoseb)) +- Process: only slurp `*err*` when inputstream ([@ikappaki](https://github.com/ikappaki)) +- Process: add `alive?` API function ([@grzm](https://github.com/grzm)) +- Bump libraries: tools.cli, babashka.cli +- [#1391](https://github.com/babashka/babashka/issues/1391): include raw in `hiccup2.core` ns +- [#1391](https://github.com/babashka/babashka/issues/1391): support loading `hiccup.page` when adding hiccup to deps + +## 0.10.163 (2022-09-24) + +- [#808](https://github.com/babashka/babashka/issues/808): support `-Sdeps` option to support passing extra deps map which will be merged last +- [#1336](https://github.com/babashka/babashka/issues/1336): tasks subcommand doesn't work with global `-Sforce` option ([@bobisageek](https://github.com/bobisageek)) +- [#1340](https://github.com/babashka/babashka/issues/1340): `defprotocol` methods are missing `:doc` metadata ([@bobisageek](https://github.com/bobisageek)) +- [#1368](https://github.com/babashka/babashka/issues/1368): `-x`: do not pick up on aliases in `user` ns +- [#1367](https://github.com/babashka/babashka/issues/1367): Fix line number in clojure.test output ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- [#1370](https://github.com/babashka/babashka/issues/1370): Add `core.async` `to-chan!`, `to-chan!!`, `onto-chan!` ([@cap10morgan](https://github.com/cap10morgan)) +- [#1358](https://github.com/babashka/babashka/issues/1358): Expose a subset of `java.lang.ref` to enable hooking into the destruction/GC of objects ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- [#1364](https://github.com/babashka/babashka/issues/1364): Be tolerant of unknown tags in `bb.edn` +- Add and expose `babashka.classes/all-classes` to get a list of all available classes (as `java.lang.Class` objects) ([@eerohele](https://github.com/eerohele)) +- Add more reflection class methods ([@eerohele](https://github.com/eerohele)) +- Bump `clj-yaml` +- Add `-x` help +- Set `TCP_NODELAY` in pods for performance +- Expose `clojure.main/with-bindings` +- Add `ThreadPoolExecutor` rejection policy classes ([@eerohele](https://github.com/eerohele)) +- Download but don't run pods when `os.name` / `os.arch` don't match ([@cap10morgan](https://github.com/cap10morgan)) +- Add `clojure.core.server/stop-server` ([@eerohele](https://github.com/eerohele)) +- Add `ns-unalias` +- Add `AtomicInteger` and `AtomicLong` to full interop +- Add `PrintWriter-on` +- Improve `reify` error messages ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- Expose `core.async` `ManyToManyChannel` +- fs: add `write-lines` +- fs: add `write-bytes` +- [#1350](https://github.com/babashka/babashka/issues/1350): map `clojure.lang.Var` to `sci.lang.Var` +- Use temporary fork of `clj-yaml` with new `:load-all`, `:unknown-tag-fn` + options and preserves strings with numbers that start with zeros as strings + (this solves a problem when YAML 1.1 output is read as YAML 1.2.). Once + upstream SnakeYAML 1.33 lands, this will be used again. + +## 0.9.162 (2022-09-04) + +Check out our new project: [bbin](https://github.com/babashka/bbin): install any Babashka script or project with one command. Thanks [@rads](https://github.com/rads)! + +- Throw exception on attempt to reify multiple interfaces ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- Allow java.lang.Object reify with empty methods ([@retrogradeorbit](https://github.com/retrogradeorbit)) +- [#1343](https://github.com/babashka/babashka/issues/1343): Fix postgres feature +- [#1345](https://github.com/babashka/babashka/issues/1345): add `javax.net.ssl.SSLException` and `java.net.SocketTimeoutException` classes ([@lread](https://github.com/lread)) +- Fix `satisfies?` with marker protocol (no methods) +- Update `rewrite-clj` +- Update `deps.clj` +- Update `babashka.cli` +- Update `org.clj-commons/clj-yaml` +- `babashka.fs`: fix `expand-home` on Windows +- `babashka.fs`: expose `:win-exts` +- nREPL: preserve stacktrace on exception +- Fix [#1322](https://github.com/babashka/babashka/issues/1322): improve error location +- Fix [#1338](https://github.com/babashka/babashka/issues/1338): `add-watch` segfault +- Fix [#1339](https://github.com/babashka/babashka/issues/1339): resolve record name ending with dot. + +## 0.9.161 (2022-07-31) + +- Fix `exec` + +## 0.9.160 (2022-07-29) + +- Breaking: change `exec`, introduced in `0.9.159` to a function instead. + You now write `(exec 'mynamespace.function)` instead. + +## 0.9.159 (2022-07-29) + +Read the introductory blog post about the new babashka CLI integration [here](https://blog.michielborkent.nl/babashka-tasks-meets-babashka-cli.html). + +- [#1324](https://github.com/babashka/babashka/issues/1324): `-x` to invoke a function with babashka CLI +- [#1324](https://github.com/babashka/babashka/issues/1324): `babashka.tasks/exec` to invoke a function with babashka CLI in tasks +- SCI: don't eval metatada on defn body +- SCI issue 774: make interpreter stacktrace available to user +- `babashka.process`: improve `tokenize` +- Upgrade to GraalVM 22.2.0 (#1325) + +## 0.8.157 (2022-07-01) + +- Add compatibility with [`metosin/malli`](https://github.com/metosin/malli#babashka) `{:mvn/version "0.8.9"}`. +- Expose `babashka.nrepl.server/start-server!`- and `stop-server!`-functions to support programmatically starting + an nrepl-server. `start-server!` is redefined to not require a sci-context as its first argument. +- Fix misspelling in script/uberjar: `BABASHKA_FEATURE_TRANSIT` + +## 0.8.156 (2022-06-08) + +- macOS aarch64 support. Upgrading via brew or the installer script should + install the aarch64 version on an M1 system. +- Support for running [prismatic/schema](https://github.com/plumatic/schema) + from source. There is an open PR for babashka. Until it is merged you can use + [this](https://github.com/borkdude/schema/tree/bb-test-suite) fork. +- SCI: many small improvements, especially in `defrecord` (discovered while + trying to make `schema.core` work) +- Switch version schema to major.minor.release_count. +- babashka.nrepl: fix completions for static Java interop +- `fs/read-all-lines`, support charset +- fs: `strip` and `split-ext` are less reliant on file system and are now just + string operations +- Bump cheshire +- Add `babashka.process/exec` for exec call (replacing the current process) +- Improve `babashka.process/tokenize` +- [#1264](https://github.com/babashka/babashka/issues/1264): add support for calling `ScheduledExecutorService` +- Add support for `sun.misc.SignalHandler` +- Add `java.net.BindException`, `clojure.lang.AFunction`, `AbstractMethodError` +- Upgrade httpkit to `2.6.0-RC1` +- Add `process/shell`, similar to `babashka.tasks/shell` but in process ns +- pods: fix benign socket closed exception error output + +## 0.8.2 (2022-05-06) + +- Convey `*print-length*` to pprint and allow `set!` +- `babashka.nrepl`: support pprint length +- SCI: support invoking field, without dash +- Add compatibility with clojure numeric tower +- Update deps.clj to tools jar `1.11.1.1113` +- Compatibility with fipp and puget +- Fix [#1233](https://github.com/babashka/babashka/issues/1233): don't print error to stdout in prepl +- Update process with `:pre-start-fn` option +- Update fs with `cwd` and Windows glob improvements +- Expose edamame, fixes [#549](https://github.com/babashka/babashka/issues/549) and [#1258](https://github.com/babashka/babashka/issues/1258) (#1259) +- Support `BABASBHKA_PODS_DIR` environment variable + +## 0.8.1 (2022-04-15) + +- Add `prepare` subcommand to download deps & pods and cache pod metadata +- [#1041](https://github.com/babashka/babashka/issues/1041): Improve error message when regex literal in EDN config +- [#1223](https://github.com/babashka/babashka/issues/1223): Ensure that var metadata (specifically `:name`) aligns with the var's symbol (which in turn ensures that `doc` will print the var's name) +- [#556](https://github.com/babashka/babashka/issues/556) Add server-status to org.httpkit.server +- [#1231](https://github.com/babashka/babashka/issues/1231): missing methods in `reify` should fall back to default interface methods +- Fix destructuring in defrecord protocol impls +- Support `*print-dup*` + +## 0.8.0 (2022-04-04) + +### New + +- [#863](https://github.com/babashka/babashka/issues/863): allow pods to be declared in `bb.edn` and load them when required. See [pod library docs](https://github.com/babashka/pods#in-a-babashka-project) and the entry in the [babashka book](https://book.babashka.org/#_pods_in_bb_edn) for details. + +### Enhanced + +- [#1216](https://github.com/babashka/babashka/issues/1216): support `core.async/alts!` using polyfill +- [#1220](https://github.com/babashka/babashka/issues/1220): add reflection on java.util.concurrent.Future +- [#1211](https://github.com/babashka/babashka/issues/1211): return exit code 130 on sigint +- [#1224](https://github.com/babashka/babashka/issues/1224): add `proxy` support for `java.io.PipedInputStream` and `java.io.PipedOutputStream`. Add reflection for `java.utils.Scanner`. +- [babashka.curl#43](https://github.com/babashka/babashka.curl/issues/43) fix: last set-cookie headers on a page overwrites the ones before +- [#1216](https://github.com/babashka/babashka/issues/1216): fix `core.async` alts example with polyfill +- SCI: support `let*` special form +- Add compatibility with GraalVM 22.1 +- Bump timbre +- Bump Clojure to 1.11.0 +- Pods: support Rosetta2 fallback +- Process: fix for pprint +- Fs: improvement for which: do not match on local paths +- Proxy support for PipedInputStream and PipedOutputStream +- Expose `java.util.Scanner` for interop +- Bump Selmer +- Add `->Eduction` +- Add `*unchecked-math` for compatibility + ## 0.7.8 (2022-03-13) This release improves compatibility with several libraries: [loom](https://github.com/aysylu/loom), [hugsql.core](https://www.hugsql.org) and [specter](https://github.com/redplanetlabs/specter)! @@ -12,12 +247,9 @@ This release improves compatibility with several libraries: [loom](https://githu To use specter in babashka, use the following coordinates: ``` clojure -{:deps {com.rpl/specter {:git/url "https://github.com/borkdude/specter" - :git/sha "8ba809a2cd35d3b6f8c5287e6bd3b4e06e42f6dc"}}} +{:deps {com.rpl/specter {:mvn/version "1.1.4"}}} ``` -Hopefully the compatibility commit can be upstreamed back into specter at some point. - - Add `clojure.data.priority-map` as built-in library - this makes babashka compatible with [aysylu/loom](https://github.com/aysylu/loom) - Add part of `clojure.tools.reader.reader-types` to support [hugsql.core](https://www.hugsql.org) - [#1204](https://github.com/babashka/babashka/issues/1204) add property `babashka.config` to reflect `bb.edn` location ([@mknoszlig](https://github.com/mknoszlig)) diff --git a/Dockerfile b/Dockerfile index 8ffb784f..f9ad88b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,10 +5,11 @@ RUN apt update RUN apt install --no-install-recommends -yy build-essential zlib1g-dev WORKDIR "/opt" -ENV GRAALVM_VERSION="22.0.0.2" +ENV GRAALVM_VERSION="22.3.0" ARG TARGETARCH -ENV BABASHKA_ARCH=${TARGETARCH} -ENV GRAALVM_ARCH=${TARGETARCH} +# Do not set those directly, use TARGETARCH instead +ENV BABASHKA_ARCH= +ENV GRAALVM_ARCH= RUN if [ "${TARGETARCH}" = "" ] || [ "${TARGETARCH}" = "amd64" ]; then \ export GRAALVM_ARCH=amd64; export BABASHKA_ARCH=x86_64; \ elif [ "${TARGETARCH}" = "arm64" ]; then \ @@ -34,7 +35,7 @@ ARG BABASHKA_FEATURE_CSV= ARG BABASHKA_FEATURE_JAVA_NET_HTTP= ARG BABASHKA_FEATURE_JAVA_NIO= ARG BABASHKA_FEATURE_JAVA_TIME= -ARG BABAHSKA_FEATURE_TRANSIT= +ARG BABASHKA_FEATURE_TRANSIT= ARG BABASHKA_FEATURE_XML= ARG BABASHKA_FEATURE_YAML= ARG BABASHKA_FEATURE_HTTPKIT_CLIENT= @@ -51,7 +52,7 @@ ENV BABASHKA_FEATURE_CSV=$BABASHKA_FEATURE_CSV ENV BABASHKA_FEATURE_JAVA_NET_HTTP=$BABASHKA_FEATURE_JAVA_NET_HTTP ENV BABASHKA_FEATURE_JAVA_NIO=$BABASHKA_FEATURE_JAVA_NIO ENV BABASHKA_FEATURE_JAVA_TIME=$BABASHKA_FEATURE_JAVA_TIME -ENV BABAHSKA_FEATURE_TRANSIT=$BABAHSKA_FEATURE_TRANSIT +ENV BABASHKA_FEATURE_TRANSIT=$BABASHKA_FEATURE_TRANSIT ENV BABASHKA_FEATURE_XML=$BABASHKA_FEATURE_XML ENV BABASHKA_FEATURE_YAML=$BABASHKA_FEATURE_YAML ENV BABASHKA_FEATURE_HTTPKIT_CLIENT=$BABASHKA_FEATURE_HTTPKIT_CLIENT diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 091f6654..f1b10a07 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -4,9 +4,11 @@ COPY bb /bin/bb RUN chmod +x /bin/bb +# TODO: See https://github.com/sgerrand/alpine-pkg-glibc/issues/185 and remove the `--force-overwrite`s when resolved. + RUN apk --no-cache add curl ca-certificates tar && \ curl -Ls https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk > /tmp/glibc-2.28-r0.apk && \ - apk add --allow-untrusted /tmp/glibc-2.28-r0.apk + apk add --allow-untrusted --force-overwrite /tmp/glibc-2.28-r0.apk RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf # TODO: Run actual native tests when they are ported @@ -21,7 +23,7 @@ FROM alpine:3 RUN apk --no-cache add curl ca-certificates tar && \ curl -Ls https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk > /tmp/glibc-2.28-r0.apk && \ - apk add --allow-untrusted /tmp/glibc-2.28-r0.apk + apk add --allow-untrusted --force-overwrite /tmp/glibc-2.28-r0.apk RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf COPY metabom.jar /opt/babashka-metabom.jar diff --git a/README.md b/README.md index ec2444a2..75a23601 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +[Babashka](https://babashka.org) [![CircleCI](https://circleci.com/gh/babashka/babashka/tree/master.svg?style=shield)](https://circleci.com/gh/babashka/babashka/tree/master) [![project chat](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://app.slack.com/client/T03RZGPFR/CLX41ASCS) @@ -12,12 +12,6 @@ @laheadle on Clojurians Slack -
- -Please leave some feedback about babashka in the [Q1 Survey](https://forms.gle/ko3NjDg2SwXeEoNQ9)! - -
- ## Introduction Babashka is a native Clojure interpreter for scripting with fast startup. Its @@ -59,7 +53,7 @@ anywhere on the path. Then you're ready to go: ``` shellsession -$ ls | bb -i '(filter #(-> % io/file .isDirectory) *input*)' +$ ls | bb -i '(filter fs/directory? *input*)' ("doc" "resources" "sci" "script" "src" "target" "test") bb took 4ms. ``` @@ -115,7 +109,8 @@ $ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]' [1 2] ``` -Read more about input and output flags [here](https://book.babashka.org/#_input_and_output_flags). +Read more about `*input*` and in- and output flags +[here](https://book.babashka.org/#_input_and_output_flags). Execute a script. E.g. print the current time in California using the `java.time` API: @@ -153,15 +148,39 @@ Install: brew install borkdude/brew/babashka -On macOS with an M1 processor: + - softwareupdate --install-rosetta - arch -x86_64 brew install borkdude/brew/babashka + + Upgrade: brew upgrade babashka +### Nix + +Linux and macOS (including ARM Macs) binaries are provided via nix (see the installation instructions for nix [here](https://nixos.org/download.html)). + +Install: + + # Adding `nixpkgs-unstable` channel for more up-to-date binaries, skip this if you already have `nixpkgs-unstable` in your channel list + nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable + nix-channel --update + nix-env -iA nixpkgs-unstable.babashka + +Upgrade: + + nix-channel --update + nix-env -iA nixpkgs-unstable.babashka + +You can find more documentation on how to use babashka with nix [here](./doc/nix.md). + +### Alpine + +On Alpine it's recommended to download the binary manually from [Github +Releases](https://github.com/babashka/babashka/releases) and use the static +linux binary. + ### Arch (Linux) `babashka` is [available](https://aur.archlinux.org/packages/babashka-bin/) in the [Arch User Repository](https://aur.archlinux.org). It can be installed using your favorite [AUR](https://aur.archlinux.org) helper such as @@ -180,6 +199,8 @@ Babashka can be installed using a plugin as follows: ### Windows +#### Scoop + On Windows you can install using [scoop](https://scoop.sh/) and the [scoop-clojure](https://github.com/littleli/scoop-clojure) bucket. @@ -194,9 +215,23 @@ scoop bucket add extras scoop install babashka ``` +#### Manual + +If scoop does not work for you, then you can also just download the `bb.exe` +binary from [Github releases](https://github.com/babashka/babashka/releases) and +place it on your path manually. + +#### WSL1 +> Note: WSL1 users might experience a BSOD, please use the --static install option when installing +``` shell +$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install +$ chmod +x install +$ ./install --static +``` + ### Installer script -Install via the installer script: +Install via the installer script for linux and macOS: ``` shell $ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install @@ -260,17 +295,76 @@ Check out the [news](doc/news.md) page to keep track of babashka-related news it Go [here](https://book.babashka.org/#built-in-namespaces) to see the full list of built-in namespaces. -## [Projects](doc/projects.md) +## [Compatible Projects](doc/projects.md) A list of projects (scripts, libraries, pods and tools) known to work with babashka. +## Badges + + +[![bb compatible](/logo/badge.svg)](https://babashka.org) + +The babashka compatible badge indicates that a [library can be used as babashka dependency](doc/projects.md). + +If this is the case for your library, we encourage you to proudly display this badge. + +
Markdown + +```markdown +[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://babashka.org) +``` +
+ +
AsciiDoc + +```asciidoc +https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg[bb compatible]] +``` +
+ +
HTML + +```html +bb compatible +``` +
+
+ +[![bb built-in](/logo/built-in-badge.svg)](https://babashka.org) + +The babashka built-in badge means that a [library has been built directly into babashka](https://book.babashka.org/#built-in-namespaces) and requires no extra dependencies to use it. + +If this rare honor belongs to your library, you should display this badge. + +
Markdown + +```markdown +[![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://babashka.org) +``` +
+ +
AsciiDoc + +```asciidoc +https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg[bb built-in]] +``` +
+ +
HTML + +```html +bb built-in +``` +
+
+ ## [Pods](https://github.com/babashka/babashka.pods) Pods are programs that can be used as a Clojure library by babashka. Documentation is available in the [pod library -repo](https://github.com/babashka/babashka.pods). +repo](https://github.com/babashka/pods). -A list of available pods can be found [here](doc/projects.md#pods). +A list of available pods can be found in the [pod registry](https://github.com/babashka/pod-registry). ## Differences with Clojure @@ -312,6 +406,23 @@ handling of SIGINT and SIGPIPE. This can be done by setting ## Articles, podcasts and videos +- [Babashka: How GraalVM Helped Create a Fast-Starting Scripting Environment for Clojure](https://medium.com/graalvm/babashka-how-graalvm-helped-create-a-fast-starting-scripting-environment-for-clojure-b0fcc38b0746) by Michiel Borkent +- [Adding Prompts To Your Babashka Scripts With Dialog](https://www.pixelated-noise.com/blog/2022/12/09/dialog-and-babashka/index.html) by A.C. Danvers +- [Using Babashka to Get Electricity Prices](https://www.karimarttila.fi/clojure/2022/12/04/using-babashka-to-get-electricity-prices.html) by Kari Marttila +- [Manage git hooks with babashka tasks](https://blaster.ai/blog/posts/manage-git-hooks-w-babashka.html) by Mykhaylo Bilyanskyy +- [Messing around with babashka](https://ian-muge.medium.com/messing-around-with-babashka-f181a9003faa) by Ian Muge +- [Deleting AWS Glacier vaults with babashka](https://javahippie.net/clojure/2022/07/23/deleting-aws-glacier-vaults-with-babashka.html) by Tim Zöller +- [Recursive document transformations with Pandoc and Clojure](https://play.teod.eu/document-transform-pandoc-clojure/) by Teodor Heggelund +- [Blambda!](https://jmglov.net/blog/2022-07-03-blambda.html) by Josh Glover +- [Babashka CLI](https://blog.michielborkent.nl/babashka-cli.html): turn Clojure functions into CLIs! +- [Breakneck Babashka on K8s](https://www.linkedin.com/pulse/breakneck-babashka-k8s-heow-goodman/) by Heow Goodman +- [Recursive document transformations with Pandoc and Clojure](https://play.teod.eu/document-transform-pandoc-clojure/) +- [Detecting inconsistent aliases in a clojure codebase](https://www.youtube.com/watch?v=bf8KLKkCH2g) by Oxalorg +- [I, too, Wrote Myself a Static Site Generator](https://dawranliou.com/blog/i-too-wrote-myself-a-static-site-generator/) by Daw-Ran Liou +- [Babashka and Clojure](https://youtu.be/ZvOs5Ele6VE) by Rahul Dé at North Virginia Linux Users Group +- [Create a password manager with Clojure using Babashka, sqlite, honeysql and stash](https://youtu.be/jm0RXmyjRJ8) by Daniel Amber +- [Writing Clojure-living-cookbooks](https://www.loop-code-recur.io/live-clojure-cookbooks) by Cyprien Pannier +- [Using babashka with PHP](https://blog.michielborkent.nl/using-babashka-with-php.html) by Michiel Borkent - [Moldable Emacs: a Clojure Playground with Babashka](https://ag91.github.io/blog/2021/11/05/moldable-emacs-a-clojure-playground-with-babashka/) by Andrea - [Finding my inner Wes Anderson with #Babashka](https://javahippie.net/clojure/2021/10/18/finding-my-inner-wes-anderson.html) by Tim Zöller - [Awesome Babashka: Parse & produce HTML and SQLite](https://blog.jakubholy.net/2021/awesome-babashka-dash/) by Jakub Holý @@ -360,7 +471,7 @@ binary, these evaluation criteria are considered: - The library cannot be interpreted by with babashka using `--classpath`. - The functionality can't be met by shelling out to another CLI or can't be written as a small layer over an existing CLI (like `babashka.curl`) instead. -- The library cannot be implemented a +- The library cannot be implemented as a [pod](https://github.com/babashka/babashka.pods). If not all of the criteria are met, but adding a feature is still useful to a diff --git a/appveyor.yml b/appveyor.yml index b9e771c4..7e60e61c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,10 +7,15 @@ image: Visual Studio 2017 clone_folder: C:\projects\babashka environment: - GRAALVM_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.0.0.2 - JAVA_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.0.0.2 + GRAALVM_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.3.0 + JAVA_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.3.0 BABASHKA_XMX: "-J-Xmx5g" +skip_commits: + files: + - "logo/*" + - "**/*.md" + cache: - C:\ProgramData\chocolatey\lib -> project.clj, appveyor.yml - '%USERPROFILE%\.m2 -> project.clj' @@ -33,7 +38,7 @@ clone_script: build_script: - cmd: >- - powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-windows-amd64-22.0.0.2.zip', 'graalvm.zip') }" + powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-windows-amd64-22.3.0.zip', 'graalvm.zip') }" powershell -Command "if (Test-Path('graalvm')) { return } else { Expand-Archive graalvm.zip graalvm }" diff --git a/babashka.curl b/babashka.curl index 3d3d117e..99e6d3ba 160000 --- a/babashka.curl +++ b/babashka.curl @@ -1 +1 @@ -Subproject commit 3d3d117ea0f8a143a06e3cace92e4e8b6a5782df +Subproject commit 99e6d3ba7a7252284b43f9de7d91d3433ecfa8f0 diff --git a/babashka.nrepl b/babashka.nrepl index d3fce671..ad763a78 160000 --- a/babashka.nrepl +++ b/babashka.nrepl @@ -1 +1 @@ -Subproject commit d3fce67198b042707f01d841c032f841cc57ab7f +Subproject commit ad763a78f1bc327a493ff0b650aa5408ecbf4819 diff --git a/deps.clj b/deps.clj index cee6e47d..2d185718 160000 --- a/deps.clj +++ b/deps.clj @@ -1 +1 @@ -Subproject commit cee6e47de8b59c6386704be0c82c0a1d74c9722c +Subproject commit 2d185718ba2871c96e6cb4a4181d1dcf9d8fde86 diff --git a/deps.edn b/deps.edn index b190e616..01302a39 100644 --- a/deps.edn +++ b/deps.edn @@ -10,23 +10,28 @@ "feature-selmer" "feature-logging" "feature-priority-map" + "feature-rrb-vector" + "feature-jdbc" "pods/src" "babashka.nrepl/src" "depstar/src" "process/src" "deps.clj/src" "deps.clj/resources" - "resources" "sci/resources"], - :deps {org.clojure/clojure {:mvn/version "1.11.0-beta1"}, - borkdude/sci {:local/root "sci"} + "resources" "sci/resources" + "reify/src"], + :deps {org.clojure/clojure {:mvn/version "1.11.1"}, + org.babashka/sci {:local/root "sci"} + org.babashka/babashka.impl.reify {:mvn/version "0.1.3"} + org.babashka/sci.impl.types {:mvn/version "0.0.2"} babashka/babashka.curl {:local/root "babashka.curl"} babashka/fs {:local/root "fs"} babashka/babashka.core {:local/root "babashka.core"} borkdude/graal.locking {:mvn/version "0.0.2"}, - org.clojure/core.async {:mvn/version "1.5.648"}, - org.clojure/tools.cli {:mvn/version "1.0.206"}, + org.clojure/core.async {:mvn/version "1.6.673"}, + org.clojure/tools.cli {:mvn/version "1.0.214"}, org.clojure/data.csv {:mvn/version "1.0.0"}, - cheshire/cheshire {:mvn/version "5.10.2"} - org.clojure/data.xml {:mvn/version "0.2.0-alpha6"} - clj-commons/clj-yaml {:mvn/version "0.7.108"} + cheshire/cheshire {:mvn/version "5.11.0"} + org.clojure/data.xml {:mvn/version "0.2.0-alpha8"} + clj-commons/clj-yaml {:mvn/version "0.7.169"} com.cognitect/transit-clj {:mvn/version "1.0.329"} org.clojure/test.check {:mvn/version "1.1.1"} nrepl/bencode {:mvn/version "1.1.0"} @@ -34,16 +39,19 @@ org.postgresql/postgresql {:mvn/version "42.2.18"} org.hsqldb/hsqldb {:mvn/version "2.5.1"} datascript/datascript {:mvn/version "1.0.1"} - http-kit/http-kit {:mvn/version "2.5.3"} + http-kit/http-kit {:mvn/version "2.6.0-RC1"} babashka/clojure-lanterna {:mvn/version "0.9.8-SNAPSHOT"} org.clojure/core.match {:mvn/version "1.0.0"} hiccup/hiccup {:mvn/version "2.0.0-alpha2"} - rewrite-clj/rewrite-clj {:mvn/version "1.0.699-alpha"} - selmer/selmer {:mvn/version "1.12.44"} - com.taoensso/timbre {:mvn/version "5.1.2"} + rewrite-clj/rewrite-clj {:mvn/version "1.1.45"} + selmer/selmer {:mvn/version "1.12.50"} + com.taoensso/timbre {:mvn/version "6.0.1"} org.clojure/tools.logging {:mvn/version "1.1.0"} org.clojure/data.priority-map {:mvn/version "1.1.0"} - instaparse/instaparse {:mvn/version "1.4.10"}} + instaparse/instaparse {:mvn/version "1.4.10"} + insn/insn {:mvn/version "0.5.2"} + org.clojure/core.rrb-vector {:mvn/version "0.1.2"} + org.babashka/cli {:mvn/version "0.6.41"}} :aliases {:babashka/dev {:main-opts ["-m" "babashka.main"]} :profile @@ -56,7 +64,7 @@ :lib-tests {:extra-paths ["process/src" "process/test" "test-resources/lib_tests"] :extra-deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"} - org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha" + #_#_org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha" :sha "0dec1f88cbde74a0470b454396f09a03adb4ae39"} lambdaisland/regal {:mvn/version "0.0.143"} cprop/cprop {:mvn/version "0.1.16"} @@ -106,13 +114,15 @@ environ/environ {:mvn/version "1.2.0"} table/table {:git/url "https://github.com/cldwalker/table", :sha "f6293c5f3dac1dd6f525a80fc80930f8ccdf16b7"} markdown-clj/markdown-clj {:mvn/version "1.10.8"} - org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace", :sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b"} + org.clojure/tools.namespace {:git/sha "daf82a10e70182aea4c0716a48f3922163441b32", + :git/url "https://github.com/clojure/tools.namespace"} medley/medley {:mvn/version "1.3.0"} - io.github.cognitect-labs/test-runner {:git/tag "v0.5.0", :git/sha "b3fd0d2"} + io.github.cognitect-labs/test-runner {:git/url "https://github.com/cognitect-labs/test-runner", + :git/sha "7284cda41fb9edc0f3bc6b6185cfb7138fc8a023"} borkdude/missing.test.assertions {:git/url "https://github.com/borkdude/missing.test.assertions", :sha "603cb01bee72fb17addacc53c34c85612684ad70"} dev.nubank/docopt {:mvn/version "0.6.1-fix7"} testdoc/testdoc {:mvn/version "1.4.1"} - org.clojars.lispyclouds/contajners {:mvn/version "0.0.4"} + org.clojars.lispyclouds/contajners {:mvn/version "0.0.6"} borkdude/rewrite-edn {:mvn/version "0.1.0"} clojure-term-colors/clojure-term-colors {:mvn/version "0.1.0"} io.aviso/pretty {:mvn/version "1.1.1"} @@ -129,17 +139,48 @@ clojure-msgpack/clojure-msgpack {:mvn/version "1.2.1"} cli-matic/cli-matic {:git/url "https://github.com/l3nz/cli-matic.git", :git/sha "9cd53ba7336363e3d06650dbad413b6f8b06e471"} aysylu/loom {:mvn/version "1.0.2"} - com.layerware/hugsql-core {:mvn/version "0.5.1"} - com.github.seancorfield/expectations {:mvn/version "2.0.157"}} + com.layerware/hugsql-core {:mvn/version "0.5.3"} + com.github.seancorfield/expectations {:mvn/version "2.0.157"} + com.rpl/specter {:mvn/version "1.1.4"} + com.github.askonomm/clarktown {:mvn/version "1.1.2"} + org.clojure/math.numeric-tower {:git/tag "math.numeric-tower-0.0.5", :git/sha "12eb9c5", :git/url "https://github.com/clojure/math.numeric-tower"} + prismatic/schema {:git/url "https://github.com/plumatic/schema" + :git/sha "6846dc7c3a9df5bfd718f68f183c683ce0f621ff" + :git/tag "schema-1.3.0"} + metosin/malli {:git/url "https://github.com/metosin/malli" + :git/sha "588147ef49b2e41c7d12a8aa994b39c1c6fedd99" + :git/tag "0.8.9"} + meander/epsilon {:git/url "https://github.com/noprompt/meander" + :git/sha "55f5ce70e6ef717e95c58260f6bc725d70c0cb6d"} + cc.qbits/auspex {:git/url "https://github.com/mpenet/auspex" + :git/sha "1a9d7427e60e1a434a764aa820d1c53f7e22504a" + :deps/manifest :deps} + exoscale/interceptor {:git/url "https://github.com/exoscale/interceptor" + :git/sha "ca115fe00a0abf3a2f78452ab309c3aa4c00fc4e" + :deps/manifest :deps} + lambdaisland/uri {:git/url "https://github.com/lambdaisland/uri" + :git/sha "ac4f1f9c8e4f45a088db1c6383ce2191c973987c" + :deps/manifest :deps} + clj-commons/fs {:mvn/version "1.6.310"} + postmortem/postmortem {:git/url "https://github.com/athos/Postmortem" + :git/sha "1a29775a3d286f9f6fe3f979c78b6e2bf298d5ba"} + com.github.rawleyfowler/sluj {:git/url "https://github.com/rawleyfowler/sluj" + :git/sha "4a92e772b4e07bf127423448d4140748b5782198" + :deps/manifest :deps}} :classpath-overrides {org.clojure/clojure nil org.clojure/spec.alpha nil}} :clj-nvd {:extra-deps {clj-nvd/clj-nvd {:git/url "https://github.com/miikka/clj-nvd.git" :sha "f2ec98699e057a379baf170cb49cf7ad76874a70"}} :main-opts ["-m" "clj-nvd.core"]} - :test ;; added by neil - {:extra-paths ["test"] - :extra-deps {io.github.cognitect-labs/test-runner - {:git/tag "v0.5.0" :git/sha "b3fd0d2"}} - :main-opts ["-m" "cognitect.test-runner"] - :exec-fn cognitect.test-runner.api/test}}} + :test + {:extra-paths ["test"] + :extra-deps {io.github.cognitect-labs/test-runner + {:git/tag "v0.5.0" :git/sha "b3fd0d2"} + nubank/matcher-combinators {:mvn/version "3.6.0"}} + :main-opts ["-m" "cognitect.test-runner"] + :exec-fn cognitect.test-runner.api/test} + :test-pod + {:extra-paths ["test-resources"] + :main-opts ["-m" "babashka.main" "test-resources/pod.clj"]}}} +;; release diff --git a/doc/build.md b/doc/build.md index 5571fa02..4de497e0 100644 --- a/doc/build.md +++ b/doc/build.md @@ -3,24 +3,24 @@ ## Prerequisites - Install [lein](https://leiningen.org/) for producing uberjars -- Download [GraalVM](https://www.graalvm.org/downloads/). Currently we use *java11-22.0.0.2*. +- Download [GraalVM](https://www.graalvm.org/downloads/). Currently we use *java11-22.3.0*. - For Windows, installing Visual Studio 2019 with the "Desktop development with C++" workload is recommended. - Set `$GRAALVM_HOME` to the GraalVM distribution directory. On macOS this can look like: ``` shell - export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.0.0.2/Contents/Home + export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.3.0/Contents/Home ``` On linux: ``` shell - export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.0.0.2 + export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.3.0 ``` On Windows, from the [Visual Studio 2019 x64 Native Tools Command Prompt](https://github.com/oracle/graal/issues/2116#issuecomment-590470806) or `cmd.exe` (not Powershell): ``` - set GRAALVM_HOME=%USERPROFILE%\Downloads\graalvm-ce-java11-22.0.0.2 + set GRAALVM_HOME=%USERPROFILE%\Downloads\graalvm-ce-java11-22.3.0 ``` If you are not running from the x64 Native Tools Command Prompt, you will need to set additional environment variables using: ``` diff --git a/doc/companies.md b/doc/companies.md index ea76a3eb..b068313a 100644 --- a/doc/companies.md +++ b/doc/companies.md @@ -61,6 +61,7 @@ Sponsoring via [Cognitect](https://www.cognitect.com/). - [Datil](https://datil.com/) - [Deon Digital](https://www.deondigital.com/) - [Fluent](https://fluent.to/) +- [Fluree](https://flur.ee/) - [Hi](https://www.hi.group/) - [Juxt](https://www.juxt.pro/) - [Latacora](https://www.latacora.com/) diff --git a/doc/dev.md b/doc/dev.md index 7d4155f7..872cfa8e 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -37,7 +37,7 @@ reasons: ## Requirements -You need [lein](https://leiningen.org/) for running JVM tests and/or producing uberjars. For building binaries you need GraalVM. Currently we use java11-22.0.0.2. +You need [lein](https://leiningen.org/) for running JVM tests and/or producing uberjars. For building binaries you need GraalVM. Currently we use java11-22.3.0. ## Clone repository @@ -81,8 +81,14 @@ Test the native version: ## Tests for Libraries Babashka runs tests of libraries that are compatible with it through -`script/run_lib_tests`. The script `add-libtest.clj` makes adding new libraries -fairly easy. Some examples: +`script/run_lib_tests`. + +You can check out [this +commit](https://github.com/babashka/babashka/commit/8d9ac4c4d18a5588a4a258a61a9db3835b4f4e5c) +for how to add tests for a library that needs no changes to its tests. +The library is cloned as a git dependency, which also includes the tests, that are then added to the test's classpath and ran. + +If a library's tests needs changes, we copy the tests using the `add-libtest.clj` script. Examples: ```sh # To add tests for a new library on clojars diff --git a/doc/fly_io/Dockerfile b/doc/fly_io/Dockerfile new file mode 100644 index 00000000..8c9149ca --- /dev/null +++ b/doc/fly_io/Dockerfile @@ -0,0 +1,5 @@ +FROM babashka/babashka:0.8.2 + +COPY example.clj / + +ENTRYPOINT bb /example.clj diff --git a/doc/fly_io/README.md b/doc/fly_io/README.md new file mode 100644 index 00000000..f21090d5 --- /dev/null +++ b/doc/fly_io/README.md @@ -0,0 +1,19 @@ +# Deploying a babashka app to fly.io + +[Fly.io](https://fly.io/) is a service that can run full stack apps with minimal +configuration. If you like the ease of Heroku, you might like fly.io and perhaps +even better! This document shows how to get a minimal babashka application up +and running on `fly.io`. + +In `example.clj` we start an http-kit web server which spits out some HTML. You +can run this locally by invoking `bb example.clj` from the command line. + +To get this site running on `fly.io`, you need to +[install](https://fly.io/docs/getting-started/installing-flyctl/) and [log +in](https://fly.io/docs/getting-started/log-in-to-fly/). + +Then run `flyctl launch` to create a new application. After making changes, you +can re-deploy the site with `flyctl deploy`. + +That's it! See this +[tweet](https://twitter.com/borkdude/status/1526175120825401344) for a demo. diff --git a/doc/fly_io/example.clj b/doc/fly_io/example.clj new file mode 100644 index 00000000..2250fb95 --- /dev/null +++ b/doc/fly_io/example.clj @@ -0,0 +1,21 @@ +(ns example + (:require [hiccup2.core :refer [html]] + [org.httpkit.server :refer [run-server]])) + +(def port (or (some-> (System/getenv "PORT") + parse-long) + 8092)) + +(run-server + (fn [_] + {:body + (str (html + [:html + [:body + [:h1 "Hello world!"] + [:p (str "This site is running with babashka v" + (System/getProperty "babashka.version"))]]]))}) + {:port port}) + +(println "Site running on port" port) +@(promise) diff --git a/doc/fly_io/fly.toml b/doc/fly_io/fly.toml new file mode 100644 index 00000000..6ebbdeeb --- /dev/null +++ b/doc/fly_io/fly.toml @@ -0,0 +1,41 @@ +# fly.toml file generated for shy-sound-2847 on 2022-05-16T14:12:38+02:00 + +app = "shy-sound-2847" + +kill_signal = "SIGINT" +kill_timeout = 5 +processes = [] + +[env] + PORT = "8092" + +[experimental] + allowed_public_ports = [] + auto_rollback = true + +[[services]] + http_checks = [] + internal_port = 8092 + processes = ["app"] + protocol = "tcp" + script_checks = [] + + [services.concurrency] + hard_limit = 25 + soft_limit = 20 + type = "connections" + + [[services.ports]] + force_https = true + handlers = ["http"] + port = 80 + + [[services.ports]] + handlers = ["tls", "http"] + port = 443 + + [[services.tcp_checks]] + grace_period = "1s" + interval = "15s" + restart_limit = 0 + timeout = "2s" diff --git a/doc/libraries.csv b/doc/libraries.csv index 021b0d81..7d8c22c1 100644 --- a/doc/libraries.csv +++ b/doc/libraries.csv @@ -8,19 +8,24 @@ borkdude/deps,https://github.com/borkdude/deps.clj borkdude/missing.test.assertions,https://github.com/borkdude/missing.test.assertions borkdude/rewrite-edn,https://github.com/borkdude/rewrite-edn camel-snake-kebab/camel-snake-kebab,https://github.com/clj-commons/camel-snake-kebab +cc.qbits/auspex,https://github.com/mpenet/auspex circleci/bond,https://github.com/circleci/bond cli-matic/cli-matic,https://github.com/l3nz/cli-matic.git clj-commons/clj-yaml,https://github.com/clj-commons/clj-yaml +clj-commons/fs,https://github.com/clj-commons/fs clj-commons/multigrep,https://github.com/clj-commons/multigrep clj-stacktrace/clj-stacktrace,https://github.com/mmcgrana/clj-stacktrace clojure-csv/clojure-csv,https://github.com/davidsantiago/clojure-csv clojure-msgpack/clojure-msgpack,https://github.com/edma2/clojure-msgpack clojure-term-colors/clojure-term-colors,https://github.com/trhura/clojure-term-colors com.exoscale/lingo,https://github.com/exoscale/lingo +com.github.askonomm/clarktown,https://github.com/askonomm/clarktown +com.github.rawleyfowler/sluj,https://github.com/rawleyfowler/sluj com.github.seancorfield/expectations,https://github.com/clojure-expectations/clojure-test com.github.seancorfield/honeysql,https://github.com/seancorfield/honeysql com.grammarly/omniconf,https://github.com/grammarly/omniconf com.layerware/hugsql-core, +com.rpl/specter,https://github.com/redplanetlabs/specter com.stuartsierra/component,https://github.com/stuartsierra/component com.stuartsierra/dependency,https://github.com/stuartsierra/dependency com.wsscode/cljc-misc,https://github.com/wilkerlucio/cljc-misc @@ -34,6 +39,7 @@ douglass/clj-psql,https://github.com/DarinDouglass/clj-psql edn-query-language/eql,https://github.com/edn-query-language/eql environ/environ,https://github.com/weavejester/environ exoscale/coax,https://github.com/exoscale/coax +exoscale/interceptor,https://github.com/exoscale/interceptor expound/expound,https://github.com/bhb/expound failjure/failjure,https://github.com/adambard/failjure ffclj/ffclj,https://github.com/luissantos/ffclj @@ -55,8 +61,10 @@ java-http-clj/java-http-clj,http://www.github.com/schmee/java-http-clj lambdaisland/regal,https://github.com/lambdaisland/regal listora/again,https://github.com/liwp/again markdown-clj/markdown-clj,https://github.com/yogthos/markdown-clj +meander/epsilon,https://github.com/noprompt/meander medley/medley,https://github.com/weavejester/medley meta-merge/meta-merge,https://github.com/weavejester/meta-merge +metosin/malli,https://github.com/metosin/malli minimallist/minimallist,https://github.com/green-coder/minimallist mvxcvi/arrangement,https://github.com/greglook/clj-arrangement orchestra/orchestra,https://github.com/jeaye/orchestra @@ -72,9 +80,12 @@ org.clojure/data.generators,https://github.com/clojure/data.generators org.clojure/data.json,https://github.com/clojure/data.json org.clojure/data.zip,https://github.com/clojure/data.zip org.clojure/math.combinatorics,https://github.com/clojure/math.combinatorics +org.clojure/math.numeric-tower,https://github.com/clojure/math.numeric-tower org.clojure/test.check,https://github.com/clojure/test.check org.clojure/tools.gitlibs,https://github.com/clojure/tools.gitlibs org.clojure/tools.namespace,https://github.com/babashka/tools.namespace +postmortem/postmortem,https://github.com/athos/Postmortem +prismatic/schema,https://github.com/plumatic/schema progrock/progrock,https://github.com/weavejester/progrock reifyhealth/specmonstah,https://github.com/reifyhealth/specmonstah rewrite-clj/rewrite-clj,https://github.com/clj-commons/rewrite-clj diff --git a/doc/news.md b/doc/news.md index bfd3b531..f5eb263c 100644 --- a/doc/news.md +++ b/doc/news.md @@ -5,6 +5,89 @@ you have anything to add. Also see [#babashka](https://twitter.com/hashtag/babashka?src=hashtag_click&f=live) on Twitter. +## [2022-07](https://twitter.com/search?q=(%23babashka)%20until%3A2022-08-01%20since%3A2022-07-01&src=typed_query&f=live) + +- [Recursive document transformations with Pandoc and Clojure](https://play.teod.eu/document-transform-pandoc-clojure/) by Teodor Heggelund +- [Babashka toolbox](https://babashka.org/toolbox/): A categorised directory of libraries and tools for Babashka +- [Quickblog](https://github.com/borkdude/quickblog): Light-weight static blog engine for Clojure and babashka +- Win a babashka t-shirt by participating in [this](https://twitter.com/borkdude/status/1547847843381030912) contest! +- [AWS Lambda, now with first class parentheses](https://www.juxt.pro/blog/nbb-lambda) by Ray McDermott (about nbb) +- [bb-github-app](https://github.com/brandonstubbs/bb-github-app): An example Babashka Script authenticating as a Github App and interacting with the Checks API +- [Ruuter](https://github.com/askonomm/ruuter#setting-up-with-babashka) is a routing library which works very well with bb +- [Blambda!](https://jmglov.net/blog/2022-07-03-blambda.html) by Josh Glover +- Files with the `.bb` extension are now correctly highlighted as Clojure code on Github! See [this](https://twitter.com/borkdude/status/1543937735429431298) tweet. +- Encode and decode files as kroki url diagrams, a [gist](https://gist.github.com/henryw374/070845dbd8cfb4672a3c0d06cf8b00e4) by Henry Widd +- Customized bb builds with clj-nix: [tweet](https://twitter.com/jlesquembre/status/1543686641461694470) +- Expose Clojure functions in the CLI with babashka and nix: [tweet](https://twitter.com/jlesquembre/status/1546777332471455745) +- [Meander](https://github.com/noprompt/meander) is now compatible with bb: [tweet](https://twitter.com/borkdude/status/1542881167338250242) +- [Deleting AWS Glacier vaults with babashka](https://javahippie.net/clojure/2022/07/23/deleting-aws-glacier-vaults-with-babashka.html) by Tim Zöller + + +## [2022-06](https://twitter.com/search?q=(%23babashka)%20until%3A2022-07-01%20since%3A2022-06-01&src=typed_query&f=live) + +- Releases: [0.8.156](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [AWS wiki page](https://github.com/babashka/babashka/wiki/AWS) +- [blambda](https://github.com/jmglov/blambda): Blambda! is a custom runtime for AWS Lambda that lets you write functions using Babashka +- [Babashka CLI](https://blog.michielborkent.nl/babashka-cli.html): turn Clojure functions into CLIs! +- [Http-server](https://github.com/babashka/http-server#babashka): Serve static assets +- [Deps-bundler](https://github.com/MrGung/deps-bundler): Bundle dependencies on + a computer that has access to maven and clojars (PC-A) and bring these over to + a computer with limited access (PC-L). +- [Prismatic/schema](https://github.com/plumatic/schema/blob/master/CHANGELOG.md#130-2022-06-10) and babashka are now compatible +- [Logseq bb tasks](https://github.com/logseq/bb-tasks): Reusable babashka tasks used by logseq team +- [Breakneck Babashka on K8s](Breakneck Babashka on K8s) by Heow Goodman + +## [2022-05](https://twitter.com/search?q=(%23babashka)%20until%3A2022-06-01%20since%3A2022-05-01&src=typed_query&f=live) + +- Releases: [0.8.2](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [Etaoin](https://github.com/clj-commons/etaoin) moved to clj-commons and now works with babashka as well. +- [Nix docs for babashka](https://github.com/babashka/babashka/blob/master/doc/nix.md) +- [Fly.io docs for babashka](https://github.com/babashka/babashka/tree/master/doc/fly_io) +- [Babashka survey results](https://blog.michielborkent.nl/babashka-survey-q1-2022.html) +- [Quickdoc](https://github.com/borkdude/quickdoc): (Quick and minimal API doc generation for Clojure +- [Awyeah-api](https://github.com/grzm/awyeah-api) - Cognitect's aws-api for babashka + +## [2022-04](https://twitter.com/search?q=(%23babashka)%20until%3A2022-05-01%20since%3A2022-04-01&src=typed_query&f=live) + +- Releases: [0.8.0 - 0.8.1](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [Babashka and Clojure](https://youtu.be/ZvOs5Ele6VE) by Rahul Dé at North Virginia Linux Users Group +- [Setup-Clojure](https://github.com/DeLaGuardo/setup-clojure/releases/tag/5.0) Github action is now able to install babashka! +- Control Chrome via devtools using [clj-chrome-devtools](https://github.com/tatut/clj-chrome-devtools/blob/master/bb.clj) which runs with bb! +- Use pods directly in `bb.edn`: [tweet](https://twitter.com/borkdude/status/1510995356229767172) + +## [2022-03](https://twitter.com/search?q=(%23babashka)%20until%3A2022-04-01%20since%3A2022-03-01&src=typed_query&f=live) + +- Releases: [0.7.7 - 0.7.8](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [Create a password manager with Clojure using Babashka, sqlite, honeysql and stash](https://youtu.be/jm0RXmyjRJ8) by Daniel Amber +- [Detecting inconsistent aliases in a clojure codebase](https://www.youtube.com/watch?v=bf8KLKkCH2g) by Oxalorg +- [Clj-konmari](https://github.com/oxalorg/clj-konmari/) by Oxalorg +- [Logseq-query](https://github.com/cldwalker/logseq-query) by Gabriel Horner [(announcement tweet with video)](https://twitter.com/cldwalker/status/1506991213030871041) +- The [loom](https://github.com/aysylu/loom) library is now compatible [(tweet)](https://twitter.com/borkdude/status/1502237220811550723) +- The [at-at](https://github.com/overtone/at-at) library is now compatible + +## [2022-02](https://twitter.com/search?q=(%23babashka)%20until%3A2022-03-01%20since%3A2022-02-01&src=typed_query&f=live) + +- Releases: [0.7.5 - 0.7.6](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [Spire is available as a babashka pod](https://twitter.com/epic_castle/status/1496784352256008194) +- Babashka Clojure template on [Repl.it](https://replit.com/@eccentric-j/Babashka-Clojure-Template?v=1#replit.nix) by Eccentric J +- Create a self-contained executable with [caxa](https://github.com/babashka/babashka/wiki/Self-contained-executable) +- Cli-matic is now compatible due to this [PR](https://github.com/l3nz/cli-matic/pull/145) +- [I, too, Wrote Myself a Static Site Generator](https://dawranliou.com/blog/i-too-wrote-myself-a-static-site-generator/) by Daw-Ran Liou +- [Staplegun](https://github.com/escherize/staplegun): Single file clipboard-manager +- [Bbb](https://github.com/nikvdp/bbb): make executable CLI tools from bb scripts +- [Apptemplate](https://github.com/redstarssystems/apptemplate): Application project template for Clojure featuring bb tasks + + +## [2022-01](https://twitter.com/search?f=live&q=(%23babashka)%20until%3A2022-02-01%20since%3A2022-01-01&src=typed_query) + +- Releases: [0.7.4](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). +- [Babashka dev builds](https://github.com/babashka/babashka-dev-builds) +- [Writing Clojure-living-cookbooks](https://www.loop-code-recur.io/live-clojure-cookbooks/) by Cyprien Pannier +- [HTMX Todo App](https://github.com/prestancedesign/babashka-htmx-todoapp) +- [Better linting for `bb.edn`](https://twitter.com/borkdude/status/1484100071134220291) +- [Unwordle](https://github.com/mknoszlig/unwordle): solver for wordle puzzles +- [Using babashka with PHP](https://blog.michielborkent.nl/using-babashka-with-php.html) by Michiel Borkent + ## 2021-12 - Releases: [0.6.8 - 0.7.3](https://github.com/babashka/babashka/blob/master/CHANGELOG.md). @@ -66,7 +149,7 @@ Twitter. - [Cursive](https://twitter.com/CursiveIDE/status/1439022267187433472) adds support for babashka in 1.11.0 - How to filter tail output with babashka on [StackOverflow](https://stackoverflow.com/questions/69241046/how-to-filter-output-of-tail-with-babashka/69241911?stw=2#69241911) - [Awesome Babashka: Parse & produce HTML and SQLite](https://blog.jakubholy.net/2021/awesome-babashka-dash/) by Jakub Holy -- [neil](https://github.com/babashka/neil): an installable babashka script to add common aliases and features to deps.edn-based projects. +- [Neil](https://github.com/babashka/neil): an installable babashka script to add common aliases and features to deps.edn-based projects. ## 2021-08 diff --git a/doc/nix.md b/doc/nix.md new file mode 100644 index 00000000..25dcb3ec --- /dev/null +++ b/doc/nix.md @@ -0,0 +1,87 @@ +# Using Babashka with Nix + +Babashka is [packaged](https://search.nixos.org/packages?type=packages&query=babashka) in nixpkgs and can be easily used from the Nix package manager. + +The following assumes a recent installation of nix and uses the unstable [nix cli](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix.html) and [Flakes](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake.html). + +To enable the unstable cli and flakes add the following to `/etc/nix/nix.conf`: + +``` +extra-experimental-features flakes nix-command +``` + +## Imperative install on Nix + +To imperatively install nix for the current user, run `nix profile install babashka`. + +## Declarative global install on NixOS + +To install babashka for all users on a NixOS system, place it in `environment.systemPackages` in your `configuration.nix`: + +```nix +{ pkgs, ... }: +{ + environment.systemPackages = with pkgs; [ + babashka + ]; +} +``` + +Then run `nixos-rebuild switch`, to activate the new configuration. + +## Declarative per-user install with home-manager + +You can install babashka for a specific user using [home-manager](https://github.com/nix-community/home-manager). Add the following to your `~/.config/nixpkgs/home.nix`: + +```nix +{ pkgs, ... }: +{ + home.packages = with pkgs; [ + babashka + ]; +} +``` + +Then run `home-manager switch`, to activate the new configuration. + +## Per project install with direnv + +To make babashka available on a per-project basis, you can use [direnv](https://direnv.net/). + +Create a file `.envrc` in the project directory with the following contents: + +``` +use flake +``` + +Create a file `flake.nix` in the project directory with the following contents: + +```nix +{ + outputs = {nixpkgs, ...}: let + supportedSystems = ["x86_64-linux" "x86_64-darwin"]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = system: import nixpkgs {inherit system;}; + in { + devShell = forAllSystems (system: let + pkgs = nixpkgsFor system; + in + pkgs.mkShell { + packages = with pkgs; [ + babashka + ]; + }); + }; +} +``` + +After running `direnv allow`, babashka should be available on the `$PATH`, when you are inside the project directory. + +## Write Babashka Application + +You can write babashka scripts with native dependencies using [WriteBabashkaApplication](https://github.com/sohalt/write-babashka-application). + +The WriteBabashkaApplication repository has an [example](https://github.com/Sohalt/write-babashka-application/tree/main/example) `flake.nix` using `cowsay` as an external dependency. + + +You can download that example, and then build the application using `nix build` or run it using `nix run`. diff --git a/doc/projects.md b/doc/projects.md index d136ed88..f27bbe9e 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -7,7 +7,6 @@ The following libraries and projects are known to work with babashka. - [tools.namespace](#toolsnamespace) - [test-runner](#test-runner) - [spec.alpha](#specalpha) - - [tools.bbuild](#toolsbbuild) - [clj-http-lite](#clj-http-lite) - [spartan.spec](#spartanspec) - [missing.test.assertions](#missingtestassertions) @@ -62,6 +61,11 @@ The following libraries and projects are known to work with babashka. - [datalog-parser](#datalog-parser) - [at-at](#at-at) - [aysylu/loom](#aysyluloom) + - [Clarktown](#clarktown) + - [Malli](#malli) + - [Meander](#meander) + - [Schema](#schema) + - [Sluj](#sluj) - [Pods](#pods) - [Projects](#projects-1) - [babashka-test-action](#babashka-test-action) @@ -88,6 +92,7 @@ The following libraries and projects are known to work with babashka. - [Babashka + scittle guestbook](#babashka--scittle-guestbook) - [bb htmx todo app](#bb-htmx-todo-app) - [bb aws lambda runtime](#bb-aws-lambda-runtime) + - [bb-github-app](#bb-github-app) Also keep an eye on the [news](news.md) page for new projects, gists and other developments around babashka. @@ -114,9 +119,9 @@ A fork of `clojure.spec.alpha` that includes support for generation and instrumentation! Its readme also contains instructions on how to use `clojure.core.specs.alpha`. -### [tools.bbuild](https://github.com/babashka/tools.bbuild) + -A fork of `tools.build`. + ### [clj-http-lite](https://github.com/clj-commons/clj-http-lite) @@ -763,7 +768,7 @@ Example: (require '[babashka.deps :as deps]) -(deps/add-deps '{:deps {org.clojars.lispyclouds/contajners {:mvn/version "0.0.2"}}}) +(deps/add-deps '{:deps {org.clojars.lispyclouds/contajners {:mvn/version "0.0.6"}}}) (require '[contajners.core :as c]) @@ -808,6 +813,26 @@ Ahead-of-time function scheduler. Compatible with babashka 0.7.7+. Graph library for Clojure. Compatible with babashka 0.7.8+. +### [Clarktown](https://github.com/askonomm/clarktown) + +An extensible and modular zero-dependency, pure-Clojure Markdown parser. + +### [Malli](https://github.com/metosin/malli#babashka) + +Data-Driven Schemas for Clojure/Script + +### [Meander](https://github.com/noprompt/meander) + +Tools for transparent data transformation + +### [Schema](https://github.com/plumatic/schema) + +Clojure(Script) library for declarative data description and validation + +### [Sluj](https://github.com/rawleyfowler/sluj) + +Sluj is a very small library for converting strings of UTF-16 text to slugs. A slug is a piece of text that is URL safe. + ## Pods [Babashka pods](https://github.com/babashka/babashka.pods) are programs that can @@ -875,7 +900,7 @@ A babashka script to obtain covid-19 related information. ### [bb-spotify](https://github.com/kolharsam/bb-spotify) -Contol your spotify player using babashka. +Control your spotify player using babashka. ### [lambdaisland/open-source](https://github.com/lambdaisland/open-source) @@ -935,3 +960,8 @@ Quick example of a todo list SPA using Babashka and htmx. ### [bb aws lambda runtime](https://github.com/tatut/bb-lambda) AWS Lambda custom runtime for Babashka scripts. + +### [bb-github-app](https://github.com/brandonstubbs/bb-github-app) + +An example Babashka script that can authenticate as a Github Application, +this example focuses on the checks api. diff --git a/examples/README.md b/examples/README.md index 0f851994..53064d50 100644 --- a/examples/README.md +++ b/examples/README.md @@ -38,6 +38,8 @@ - [Simple logger](#simple-logger) - [Using GZip streams (memo utility)](#using-gzip-streams-to-make-a-note-utility) - [Pretty-printing mySQL results](#pretty-printing-mysql-results) + - [Single page application with Babashka + htmx](#single-page-application-with-babashka--htmx) + - [Wikipedia translation](#wikipedia-translation) Here's a gallery of useful examples. Do you have a useful example? PR welcome! @@ -390,7 +392,7 @@ Example usage: $ examples/http-server.clj ``` -See [file-server.clj](file-server.clj). +See [http-server.clj](http-server.clj). ## Torrent viewer @@ -555,3 +557,14 @@ Example of a todo list SPA using Babashka and htmx See [htmx_todoapp.clj](htmx_todoapp.clj) Contributed by [@prestancedesign](https://github.com/prestancedesign). + +## Wikipedia translation + +[wiki-translate.clj](wiki-translate.clj) uses Wikipedia to translate words from English to Dutch (other languages are available). + +``` shell +$ bb wiki-translate.clj window +"Venster (muur) – Dutch" +``` + +Shared by Janne Himanka on Clojurians Slack diff --git a/examples/normalize-keywords.clj b/examples/normalize-keywords.clj index b4b455c7..af3ff329 100644 --- a/examples/normalize-keywords.clj +++ b/examples/normalize-keywords.clj @@ -1,18 +1,18 @@ (ns normalize-keywords (:require [babashka.pods :as pods] + [clojure.java.io :as io] [rewrite-clj.node :as node] [rewrite-clj.zip :as z])) -(pods/load-pod 'borkdude/clj-kondo "2021.06.18") +(pods/load-pod 'clj-kondo/clj-kondo "2022.11.02") (require '[pod.borkdude.clj-kondo :as clj-kondo]) (def code (first *command-line-args*)) -(def findings - (->> (with-in-str code - (clj-kondo/run! {:lint [code] - :config {:output {:analysis {:keywords true}}}})) +(defn findings [file-path] + (->> (clj-kondo/run! {:lint [file-path] + :config {:output {:analysis {:keywords true}}}}) :analysis :keywords (filter (some-fn :alias :auto-resolved)))) @@ -33,6 +33,11 @@ zloc (z/replace zloc (node/coerce k))] (recur zloc (next findings))) (recur (z/next zloc) findings))) - (println (str (z/root zloc)))))) + (str (z/root zloc))))) -(remove-locs (z/of-file code) findings) +(doseq [f (file-seq (io/file code)) + :when (re-find #"\.clj[cdsx]?$" (str f)) + :let [file-path (str f)]] + (when-let [findings' (findings file-path)] + (prn (format "Rewriting %s" file-path)) + (spit f (remove-locs (z/of-file file-path) findings')))) diff --git a/examples/wiki-translate.clj b/examples/wiki-translate.clj new file mode 100644 index 00000000..1558705b --- /dev/null +++ b/examples/wiki-translate.clj @@ -0,0 +1,11 @@ +#!/usr/bin/env bb +;; by Janne Himanka shared on Clojurians Slack +(require '[babashka.curl :as curl]) + +(let [url (str "https://en.wikipedia.org/wiki/" (first *command-line-args*)) + page (:body (curl/get url))] + (cond + (re-find #"Disambiguation" page) + (doseq [item (map last (re-seq #"
  • SciNamespace 'datascript.core nil)) -(def datascript-db-ns (vars/->SciNamespace 'datascript.db nil)) +(def datascript-ns (sci/create-ns 'datascript.core nil)) +(def datascript-db-ns (sci/create-ns 'datascript.db nil)) (def datascript-namespace {'create-conn (copy-var d/create-conn datascript-ns) diff --git a/feature-hiccup/babashka/impl/hiccup.clj b/feature-hiccup/babashka/impl/hiccup.clj index 7263fb88..f29581c4 100644 --- a/feature-hiccup/babashka/impl/hiccup.clj +++ b/feature-hiccup/babashka/impl/hiccup.clj @@ -52,10 +52,11 @@ util/raw-string) (def hiccup-namespace - {'html (copy-var html-1 hns)}) + {'html (copy-var html-1 hns {:name 'html})}) (def hiccup2-namespace - {'html (copy-var html-2 hns2)}) + {'html (copy-var html-2 hns2 {:name 'html}) + 'raw (copy-var util/raw-string hns2 {:name 'raw})}) (def html-mode (copy-var util/*html-mode* uns)) (def escape-strings? (copy-var util/*escape-strings?* uns)) @@ -63,7 +64,8 @@ (def hiccup-util-namespace {'*html-mode* html-mode '*escape-strings?* escape-strings? - 'raw-string (copy-var util/raw-string uns)}) + 'raw-string (copy-var util/raw-string uns) + 'to-uri (copy-var util/to-uri uns)}) (defn render-html [& contents] (binding [util/*html-mode* @html-mode diff --git a/feature-httpkit-client/babashka/impl/httpkit_client.clj b/feature-httpkit-client/babashka/impl/httpkit_client.clj index bb3e10ea..645aaa97 100644 --- a/feature-httpkit-client/babashka/impl/httpkit_client.clj +++ b/feature-httpkit-client/babashka/impl/httpkit_client.clj @@ -72,8 +72,10 @@ 'acl (copy-var acl cns) 'unlock (copy-var unlock cns) 'default-client (copy-var client/default-client cns) - '*default-client* default-client}) + '*default-client* default-client + 'query-string (copy-var client/query-string cns) + 'url-encode (copy-var client/url-encode cns)}) (def sni-client-namespace {'ssl-configurer (copy-var sni-client/ssl-configurer sns) - 'default-client (sci/new-var 'sni-client sni-client {:ns sns})}) + 'default-client (sci/new-var 'default-client sni-client {:ns sns})}) diff --git a/feature-httpkit-server/babashka/impl/httpkit_server.clj b/feature-httpkit-server/babashka/impl/httpkit_server.clj index f7e1935c..42608208 100644 --- a/feature-httpkit-server/babashka/impl/httpkit_server.clj +++ b/feature-httpkit-server/babashka/impl/httpkit_server.clj @@ -8,6 +8,7 @@ {:obj sns 'server-stop! (copy-var server/server-stop! sns) 'server-port (copy-var server/server-port sns) + 'server-status (copy-var server/server-status sns) 'run-server (copy-var server/run-server sns) 'sec-websocket-accept (copy-var server/sec-websocket-accept sns) 'websocket-handshake-check (copy-var server/websocket-handshake-check sns) diff --git a/feature-jdbc/babashka/impl/jdbc.clj b/feature-jdbc/babashka/impl/jdbc.clj index a9a4a3bc..bfcffdb2 100644 --- a/feature-jdbc/babashka/impl/jdbc.clj +++ b/feature-jdbc/babashka/impl/jdbc.clj @@ -1,12 +1,12 @@ (ns babashka.impl.jdbc {:no-doc true} - (:require [next.jdbc :as njdbc] - [next.jdbc.result-set :as rs] - [next.jdbc.sql :as sql] - [sci.impl.namespaces :refer [copy-var macrofy]] - [sci.impl.vars :as vars])) + (:require + [next.jdbc :as njdbc] + [next.jdbc.result-set :as rs] + [next.jdbc.sql :as sql] + [sci.core :as sci])) -(def next-ns (vars/->SciNamespace 'next.jdbc nil)) +(def next-ns (sci/create-ns 'next.jdbc nil)) (defn with-transaction "Given a transactable object, gets a connection and binds it to `sym`, @@ -19,30 +19,30 @@ * `:rollback-only` -- `true` / `false`." [_ _ [sym transactable opts] & body] (let [con (vary-meta sym assoc :tag 'java.sql.Connection)] - `(next.jdbc/transact ~transactable (^{:once true} fn* [~con] ~@body) ~(or opts {})))) + `(njdbc/transact ~transactable (^{:once true} fn* [~con] ~@body) ~(or opts {})))) (def njdbc-namespace - {'get-datasource (copy-var njdbc/get-datasource next-ns) - 'execute! (copy-var njdbc/execute! next-ns) - 'execute-one! (copy-var njdbc/execute-one! next-ns) - 'get-connection (copy-var njdbc/get-connection next-ns) - 'plan (copy-var njdbc/plan next-ns) - 'prepare (copy-var njdbc/prepare next-ns) - 'transact (copy-var njdbc/transact next-ns) - 'with-transaction (macrofy 'with-transaction with-transaction next-ns)}) + {'get-datasource (sci/copy-var njdbc/get-datasource next-ns) + 'execute! (sci/copy-var njdbc/execute! next-ns) + 'execute-one! (sci/copy-var njdbc/execute-one! next-ns) + 'get-connection (sci/copy-var njdbc/get-connection next-ns) + 'plan (sci/copy-var njdbc/plan next-ns) + 'prepare (sci/copy-var njdbc/prepare next-ns) + 'transact (sci/copy-var njdbc/transact next-ns) + 'with-transaction (sci/copy-var with-transaction next-ns)}) -(def sns (vars/->SciNamespace 'next.jdbc.sql nil)) +(def sns (sci/create-ns 'next.jdbc.sql nil)) (def next-sql-namespace - {'insert-multi! (copy-var sql/insert-multi! sns)}) + {'insert-multi! (sci/copy-var sql/insert-multi! sns)}) -(def rsns (vars/->SciNamespace 'next.jdbc.result-set nil)) +(def rsns (sci/create-ns 'next.jdbc.result-set nil)) (def result-set-namespace - {'as-maps (copy-var rs/as-maps rsns) - 'as-unqualified-maps (copy-var rs/as-unqualified-maps rsns) - 'as-modified-maps (copy-var rs/as-modified-maps rsns) - 'as-unqualified-modified-maps (copy-var rs/as-unqualified-modified-maps rsns) - 'as-lower-maps (copy-var rs/as-lower-maps rsns) - 'as-unqualified-lower-maps (copy-var rs/as-unqualified-lower-maps rsns) - 'as-maps-adapter (copy-var rs/as-maps-adapter rsns)}) + {'as-maps (sci/copy-var rs/as-maps rsns) + 'as-unqualified-maps (sci/copy-var rs/as-unqualified-maps rsns) + 'as-modified-maps (sci/copy-var rs/as-modified-maps rsns) + 'as-unqualified-modified-maps (sci/copy-var rs/as-unqualified-modified-maps rsns) + 'as-lower-maps (sci/copy-var rs/as-lower-maps rsns) + 'as-unqualified-lower-maps (sci/copy-var rs/as-unqualified-lower-maps rsns) + 'as-maps-adapter (sci/copy-var rs/as-maps-adapter rsns)}) diff --git a/feature-rrb-vector/babashka/impl/rrb_vector.clj b/feature-rrb-vector/babashka/impl/rrb_vector.clj new file mode 100644 index 00000000..955e5d7b --- /dev/null +++ b/feature-rrb-vector/babashka/impl/rrb_vector.clj @@ -0,0 +1,7 @@ +(ns babashka.impl.rrb-vector + (:require [clojure.core.rrb-vector :as rrb] + [sci.core :as sci])) + +(def rrbns (sci/create-ns 'clojure.core.rrb-vector)) + +(def rrb-vector-namespace {'catvec (sci/copy-var rrb/catvec rrbns)}) diff --git a/feature-selmer/babashka/impl/selmer.clj b/feature-selmer/babashka/impl/selmer.clj index 14ce03c8..32aeba3e 100644 --- a/feature-selmer/babashka/impl/selmer.clj +++ b/feature-selmer/babashka/impl/selmer.clj @@ -60,7 +60,7 @@ (selmer.parser/render-template template context-map))) (defn sci-ns-resolve [ns fqs] - (sci/eval-form @ctx (list 'clojure.core/ns-resolve ns (list 'quote fqs)))) + (sci/eval-form (ctx) (list 'clojure.core/ns-resolve ns (list 'quote fqs)))) (defn force! [x] (if (instance? clojure.lang.IDeref x) @x x)) diff --git a/feature-spec-alpha/babashka/impl/spec.clj b/feature-spec-alpha/babashka/impl/spec.clj index 7e01d189..7d69f125 100644 --- a/feature-spec-alpha/babashka/impl/spec.clj +++ b/feature-spec-alpha/babashka/impl/spec.clj @@ -4,12 +4,11 @@ [babashka.impl.clojure.spec.gen.alpha :as gen] [babashka.impl.clojure.spec.test.alpha :as test] [clojure.core :as c] - [sci.core :as sci :refer [copy-var]] - [sci.impl.vars :as vars])) + [sci.core :as sci :refer [copy-var]])) -(def sns (vars/->SciNamespace 'clojure.spec.alpha nil)) -(def tns (vars/->SciNamespace 'clojure.spec.test.alpha nil)) -(def gns (vars/->SciNamespace 'clojure.spec.gen.alpha nil)) +(def sns (sci/create-ns 'clojure.spec.alpha nil)) +(def tns (sci/create-ns 'clojure.spec.test.alpha nil)) +(def gns (sci/create-ns 'clojure.spec.gen.alpha nil)) (defn- ns-qualify "Qualify symbol s by resolving it or using the current *ns*." diff --git a/feature-transit/babashka/impl/transit.clj b/feature-transit/babashka/impl/transit.clj index ab249c6a..15b0f19a 100644 --- a/feature-transit/babashka/impl/transit.clj +++ b/feature-transit/babashka/impl/transit.clj @@ -1,10 +1,9 @@ (ns babashka.impl.transit - (:require [cognitect.transit :as transit] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + (:require + [cognitect.transit :as transit] + [sci.core :as sci :refer [copy-var]])) - -(def tns (vars/->SciNamespace 'cognitect.transit nil)) +(def tns (sci/create-ns 'cognitect.transit nil)) (def transit-namespace {'write (copy-var transit/write tns) diff --git a/feature-xml/babashka/impl/xml.clj b/feature-xml/babashka/impl/xml.clj index d889aa79..0018cb70 100644 --- a/feature-xml/babashka/impl/xml.clj +++ b/feature-xml/babashka/impl/xml.clj @@ -12,7 +12,7 @@ (def xtns (sci/create-ns 'clojure.data.xml.tree nil)) (defn- clj-ns-name [ns] - (cond (instance? sci.impl.vars.SciNamespace ns) (str ns) + (cond (instance? sci.lang.Namespace ns) (str ns) (keyword? ns) (name ns) :else (str ns))) @@ -40,8 +40,8 @@ #_(assert (<= (count ans)) (pr-str ans)) (let [xn (xml/uri-symbol n) al (symbol (clj-ns-name a))] - (sci/eval-form @ctx `(create-ns (quote ~xn))) - (sci/eval-form @ctx `(alias (quote ~al) (quote ~xn))) + (sci/eval-form (ctx) `(create-ns (quote ~xn))) + (sci/eval-form (ctx) `(alias (quote ~al) (quote ~xn))) (recur rst))))) (def xml-namespace diff --git a/fs b/fs index 2bf527f7..d3226ccc 160000 --- a/fs +++ b/fs @@ -1 +1 @@ -Subproject commit 2bf527f797d69b3f14247940958e0d7b509f3ce2 +Subproject commit d3226cccd9898eba5adb50dfcc30a7223ff5c8cc diff --git a/install b/install index 50a07309..349405b0 100755 --- a/install +++ b/install @@ -8,6 +8,7 @@ static_binary="false" default_install_dir="/usr/local/bin" install_dir="$default_install_dir" download_dir="" +dev_build="" print_help() { echo "Installs latest (or specific) version of babashka. Installation directory defaults to /usr/local/bin." @@ -56,6 +57,10 @@ do static_binary="true" shift ;; + --dev-build) + dev_build="true" + shift + ;; *) # unknown option print_help shift @@ -74,7 +79,11 @@ if [[ "$checksum" != "" ]] && [[ "$version" == "" ]]; then fi if [[ "$version" == "" ]]; then - version="$(curl -sL https://raw.githubusercontent.com/babashka/babashka/master/resources/BABASHKA_RELEASED_VERSION)" + if [[ "$dev_build" == "true" ]]; then + version="$(curl -sL https://raw.githubusercontent.com/babashka/babashka/master/resources/BABASHKA_VERSION)" + else + version="$(curl -sL https://raw.githubusercontent.com/babashka/babashka/master/resources/BABASHKA_RELEASED_VERSION)" + fi fi case "$(uname -s)" in @@ -82,21 +91,32 @@ case "$(uname -s)" in Darwin*) platform=macos;; esac -case "$(uname -m)" in - aarch64) arch=aarch64;; - *) arch=amd64;; -esac - # Ugly ugly conversion of version to a comparable number IFS='.' read -ra VER <<< "${version//-SNAPSHOT/}" vernum=$(printf "%03d%03d%03d" "${VER[0]}" "${VER[1]}" "${VER[2]}") +case "$(uname -m)" in + aarch64) arch=aarch64;; + arm64) if [[ 10#$vernum -le 10#000008002 ]]; then + arch="amd64" + else + arch="aarch64" + fi + ;; + *) arch=amd64 + # always use static image on linux + if [[ "$platform" == "linux" ]]; then + static_binary="true" + fi + ;; +esac + if [[ 10#$vernum -le 10#000002013 ]]; then - ext="zip" - util="$(which unzip) -qqo" + ext="zip" + util="$(which unzip) -qqo" else - ext="tar.gz" - util="$(which tar) -zxf" + ext="tar.gz" + util="$(which tar) -zxf" fi case "$platform-$static_binary" in @@ -119,15 +139,15 @@ fi download_url="https://github.com/babashka/$repo/releases/download/v$version/$filename" # macOS only have shasum available by default -# Some Linux distros (RHEL-like) only have sha256sum avaiable by default (others have both) +# Some Linux distros (RHEL-like) only have sha256sum available by default (others have both) if command -v sha256sum >/dev/null; then - sha256sum_cmd="sha256sum" + sha256sum_cmd="sha256sum" elif command -v shasum >/dev/null; then - sha256sum_cmd="shasum -a 256" + sha256sum_cmd="shasum -a 256" else - >&2 echo "Either 'sha256sum' or 'shasum' needs to be on PATH for '--checksum' flag!" - >&2 echo "Exiting..." - exit 1 + >&2 echo "Either 'sha256sum' or 'shasum' needs to be on PATH for '--checksum' flag!" + >&2 echo "Exiting..." + exit 1 fi # Running this part in a subshell so when it finishes we go back to the previous directory diff --git a/logo/babashka-blue-yellow.png b/logo/babashka-blue-yellow.png new file mode 100644 index 00000000..ff85cd2b Binary files /dev/null and b/logo/babashka-blue-yellow.png differ diff --git a/logo/babashka-blue-yellow.svg b/logo/babashka-blue-yellow.svg new file mode 100644 index 00000000..7e298b09 --- /dev/null +++ b/logo/babashka-blue-yellow.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/logo/babashka_red.png b/logo/babashka_red.png new file mode 100644 index 00000000..f178d08f Binary files /dev/null and b/logo/babashka_red.png differ diff --git a/logo/babashka_red.svg b/logo/babashka_red.svg new file mode 100644 index 00000000..ad915719 --- /dev/null +++ b/logo/babashka_red.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/logo/badge.svg b/logo/badge.svg new file mode 100644 index 00000000..cc95918c --- /dev/null +++ b/logo/badge.svg @@ -0,0 +1 @@ +babashka: compatiblebabashkacompatible \ No newline at end of file diff --git a/logo/built-in-badge.svg b/logo/built-in-badge.svg new file mode 100644 index 00000000..afeae2eb --- /dev/null +++ b/logo/built-in-badge.svg @@ -0,0 +1 @@ +babashka: built-inbabashkabuilt-in \ No newline at end of file diff --git a/pods b/pods index 538fc6f4..decf7910 160000 --- a/pods +++ b/pods @@ -1 +1 @@ -Subproject commit 538fc6f4147badf3a457b3eb8f9f95dd3bc947b1 +Subproject commit decf791000081ca9e6d2fbea9f20a0aa3fae902e diff --git a/process b/process index 5db9560e..90e4cf0b 160000 --- a/process +++ b/process @@ -1 +1 @@ -Subproject commit 5db9560eab698f9773537acb0167f38d6fd0d322 +Subproject commit 90e4cf0b0cc7856f8c39591c3350cdf156d11042 diff --git a/project.clj b/project.clj index db6cbeb6..cc6a62f2 100644 --- a/project.clj +++ b/project.clj @@ -10,31 +10,38 @@ :source-paths ["src" "sci/src" "babashka.curl/src" "fs/src" "pods/src" "babashka.core/src" "babashka.nrepl/src" "depstar/src" "process/src" - "deps.clj/src" "deps.clj/resources"] + "deps.clj/src" "deps.clj/resources" + "reify/src"] ;; for debugging Reflector.java code: ;; :java-source-paths ["sci/reflector/src-java"] :java-source-paths ["src-java"] :resource-paths ["resources" "sci/resources"] :test-selectors {:default (complement :windows-only) :windows (complement :skip-windows)} - :dependencies [[org.clojure/clojure "1.11.0-beta1"] - [borkdude/edamame "0.0.19"] + :dependencies [[org.clojure/clojure "1.11.1"] + [borkdude/edamame "1.0.16"] [borkdude/graal.locking "0.0.2"] - [org.clojure/tools.cli "1.0.206"] - [cheshire "5.10.2"] + [org.clojure/tools.cli "1.0.214"] + [cheshire "5.11.0"] [nrepl/bencode "1.1.0"] [borkdude/sci.impl.reflector "0.0.1"] - [org.clojure/core.async "1.5.648"] + [org.babashka/sci.impl.types "0.0.2"] + [org.babashka/babashka.impl.reify "0.1.3"] + [org.clojure/core.async "1.6.673"] [org.clojure/test.check "1.1.1"] [com.github.clj-easy/graal-build-time "0.1.0"] [rewrite-clj/rewrite-clj "1.0.699-alpha"] - [instaparse/instaparse "1.4.10"]] + [instaparse/instaparse "1.4.10"] + [rewrite-clj/rewrite-clj "1.1.45"] + [insn/insn "0.5.2"] + [org.babashka/cli "0.6.41"]] :plugins [[org.kipz/lein-meta-bom "0.1.1"]] :metabom {:jar-name "metabom.jar"} :profiles {:feature/xml {:source-paths ["feature-xml"] - :dependencies [[org.clojure/data.xml "0.2.0-alpha6"]]} + :dependencies [[org.clojure/data.xml "0.2.0-alpha8"]]} :feature/yaml {:source-paths ["feature-yaml"] - :dependencies [[clj-commons/clj-yaml "0.7.108"]]} + :dependencies [[clj-commons/clj-yaml "0.7.169" + #_#_clj-commons/clj-yaml "0.7.110"]]} :feature/jdbc {:source-paths ["feature-jdbc"] :dependencies [[seancorfield/next.jdbc "1.1.610"]]} :feature/sqlite [:feature/jdbc {:dependencies [[org.xerial/sqlite-jdbc "3.36.0.3"]]}] @@ -49,9 +56,9 @@ :feature/datascript {:source-paths ["feature-datascript"] :dependencies [[datascript "1.3.10"]]} :feature/httpkit-client {:source-paths ["feature-httpkit-client"] - :dependencies [[http-kit "2.5.3"]]} + :dependencies [[http-kit "2.6.0-RC1"]]} :feature/httpkit-server {:source-paths ["feature-httpkit-server"] - :dependencies [[http-kit "2.5.3"]]} + :dependencies [[http-kit "2.6.0-RC1"]]} :feature/lanterna {:source-paths ["feature-lanterna"] :dependencies [[babashka/clojure-lanterna "0.9.8-SNAPSHOT"]]} :feature/core-match {:source-paths ["feature-core-match"] @@ -61,12 +68,14 @@ :feature/test-check {:source-paths ["feature-test-check"]} :feature/spec-alpha {:source-paths ["feature-spec-alpha"]} :feature/selmer {:source-paths ["feature-selmer"] - :dependencies [[selmer/selmer "1.12.44"]]} + :dependencies [[selmer/selmer "1.12.50"]]} :feature/logging {:source-paths ["feature-logging"] - :dependencies [[com.taoensso/timbre "5.1.2"] + :dependencies [[com.taoensso/timbre "6.0.1"] [org.clojure/tools.logging "1.1.0"]]} :feature/priority-map {:source-paths ["feature-priority-map"] :dependencies [[org.clojure/data.priority-map "1.1.0"]]} + :feature/rrb-vector {:source-paths ["feature-rrb-vector"] + :dependencies [[org.clojure/core.rrb-vector "0.1.2"]]} :test [:feature/xml :feature/lanterna :feature/yaml @@ -84,8 +93,10 @@ :feature/selmer :feature/logging :feature/priority-map + :feature/rrb-vector {:dependencies [[com.clojure-goes-fast/clj-async-profiler "0.5.0"] - [com.opentable.components/otj-pg-embedded "0.13.3"]]}] + [com.opentable.components/otj-pg-embedded "0.13.3"] + [nubank/matcher-combinators "3.6.0"]]}] :uberjar {:global-vars {*assert* false} :jvm-opts ["-Dclojure.compiler.direct-linking=true" "-Dclojure.spec.skip-macros=true" diff --git a/reify/.dir-locals.el b/reify/.dir-locals.el new file mode 100644 index 00000000..85a2d84d --- /dev/null +++ b/reify/.dir-locals.el @@ -0,0 +1,2 @@ +((nil + (cider-clojure-cli-global-options . "-A:test:build"))) diff --git a/reify/bb.edn b/reify/bb.edn new file mode 100644 index 00000000..adce8f9c --- /dev/null +++ b/reify/bb.edn @@ -0,0 +1,2 @@ +{:tasks {install (clojure "-T:build install") + deploy (clojure "-T:build deploy")}} diff --git a/reify/build.clj b/reify/build.clj new file mode 100644 index 00000000..c0493917 --- /dev/null +++ b/reify/build.clj @@ -0,0 +1,50 @@ +(ns build + (:require [build.reify2 :as reify2] + [clojure.tools.build.api :as b])) + +(def lib 'org.babashka/babashka.impl.reify) +(def version "0.1.3") +(def class-dir "target/classes") +(def basis (b/create-basis {:project "deps.edn"})) +(def jar-file (format "target/%s-%s.jar" (name lib) version)) + +(defn clean [_] + (b/delete {:path "target"})) + +(defn gen-classes [_] + (reify2/gen-classes nil)) + +(defn jar [_] + (gen-classes nil) + (b/write-pom {:class-dir class-dir + :lib lib + :version version + :basis basis + :src-dirs ["src"]}) + (b/copy-dir {:src-dirs ["src"] + :target-dir class-dir}) + (b/jar {:class-dir class-dir + :jar-file jar-file})) + +(defn install [_] + (jar nil) + (b/install {:basis basis + :lib lib + :version version + :jar-file jar-file + :class-dir class-dir})) + +(defn deploy [opts] + (jar opts) + ((requiring-resolve 'deps-deploy.deps-deploy/deploy) + (merge {:installer :remote + :artifact jar-file + :pom-file (b/pom-path {:lib lib :class-dir class-dir})} + opts)) + opts) + +;;;; Scratch + +(comment + (gen-classes nil) + ) diff --git a/reify/build/reify2.clj b/reify/build/reify2.clj new file mode 100644 index 00000000..4dcc9f35 --- /dev/null +++ b/reify/build/reify2.clj @@ -0,0 +1,188 @@ +(ns build.reify2 + {:no-doc true} + (:require [babashka.impl.reify2.interfaces :refer [interfaces]] + [insn.core :as insn])) + +(set! *warn-on-reflection* false) + +(defn set-symbol! [s] + [[:aconst-null] + [:ldc s] + [:invokestatic clojure.lang.Symbol "intern" [String String clojure.lang.Symbol]] + [:putstatic :this (str "_sym_" s) clojure.lang.Symbol]]) + +(defn return [desc] + (case (last desc) + :void [:return] + (:boolean :int) [:ireturn] + [:areturn])) + +(defn loads [desc cast?] + (let [desc (butlast desc)] + (vec + (mapcat (fn [i e] + (case e + :boolean [[:iload i] + (when cast? [:invokestatic Boolean "valueOf" [:boolean Boolean]])] + :int [[:iload i] + (when cast? [:invokestatic Integer "valueOf" [:int Integer]])] + [[:aload i]])) + (range 1 (inc (count desc))) + desc)))) + +(defn emit-method [class meth desc default] + (let [args (dec (count desc))] + [[[:aload 0] + [:getfield :this "_methods" java.util.Map] + [:getstatic :this (str "_sym_" meth) clojure.lang.Symbol] + [:invokeinterface java.util.Map "get" [Object Object]] + [:checkcast clojure.lang.IFn] + [:astore (inc args)] + [:aload (inc args)] + [:ifnull :fallback] + [:aload (inc args)] + ;; load this, always the first argument of IFn + [:aload 0]] + ;; load remaining args + (loads desc true) + [[:invokeinterface clojure.lang.IFn "invoke" (vec (repeat (inc (count desc)) Object))] + (let [ret-type* (last desc) + ret-type (if (class? ret-type*) + (.getName ^Class ret-type*) + ret-type*)] + (case ret-type + :void [:pop] + :boolean [[:checkcast Boolean] + [:invokevirtual Boolean "booleanValue"]] + :int [[:checkcast Integer] + [:invokevirtual Integer "intValue"]] + "java.lang.Object" nil + (when (class? ret-type*) + [[:checkcast ret-type*]]))) + (return desc) + [:mark :fallback]] + (if default + [[[:aload 0]] + (loads desc false) + [[:invokespecial class meth desc true] + (return desc)]] + [[:new java.lang.UnsupportedOperationException] + [:dup] + [:ldc (format "No implementation of method found: %s %s" meth desc)] + [:invokespecial java.lang.UnsupportedOperationException :init [String :void]] + [:athrow]])])) + +(defn interface-data [^Class interface methods] + (let [class-sym (symbol (.getName interface)) + method-names (distinct (map :name methods))] + {:name (symbol (str "babashka.impl." (.getName interface))) + :version 1.8 + :interfaces [class-sym + 'sci.impl.types.IReified + 'clojure.lang.IMeta + 'clojure.lang.IObj] + :flags [:super :public] + :fields (into [{:flags #{:private}, + :name "_methods" :type java.util.Map} + {:flags #{:private}, + :name "_interfaces" :type Object} + {:flags #{:private}, + :name "_protocols" :type Object} + {:flags #{:private}, + :name "_meta" :type clojure.lang.IPersistentMap}] + (for [name method-names] + {:flags #{:private :static}, + :name (str "_sym_" name) :type clojure.lang.Symbol})) + :methods (into [{:name :clinit + :emit (reduce into + [] + (conj + (mapv set-symbol! method-names) + [[:return]]))} + {:name :init + :desc [:void] + :emit [[:aload 0] + [:invokespecial :super :init [:void]] + [:return]]} + {:name :init + :desc [java.util.Map Object Object :void] + :emit [[:aload 0] + [:invokespecial :super :init [:void]] + [:aload 0] + [:aload 1] + [:putfield :this "_methods" java.util.Map] + [:aload 0] + [:aload 2] + [:putfield :this "_interfaces" Object] + [:aload 0] + [:aload 3] + [:putfield :this "_protocols" Object] + [:return]]} + {:name :meta + :desc [clojure.lang.IPersistentMap] + :emit [[:aload 0] + [:getfield :this "_meta" clojure.lang.IPersistentMap] + [:areturn]]} + {:name :withMeta + :desc [clojure.lang.IPersistentMap clojure.lang.IObj] + :emit [[:aload 0] + [:aload 1] + [:putfield :this "_meta" clojure.lang.IPersistentMap] + [:aload 0] + [:areturn]]} + {:name :getInterfaces + :desc [Object] + :emit [[:aload 0] + [:getfield :this "_interfaces" Object] + [:areturn]]} + {:name :getMethods + :desc [Object] + :emit [[:aload 0] + [:getfield :this "_methods" java.util.Map] + [:areturn]]} + {:name :getProtocols + :desc [Object] + :emit [[:aload 0] + [:getfield :this "_protocols" Object] + [:areturn]]}] + (for [{:keys [name desc default]} methods] + {:flags #{:public}, :name name + :desc desc + :emit (emit-method interface name desc default)} + ))})) + +(set! *warn-on-reflection* true) + +(defn type->kw [type] + (condp = type + Void/TYPE :void + Boolean/TYPE :boolean + Integer/TYPE :int + type)) + +(defn class->methods [^Class clazz] + (let [meths (.getMethods clazz) + meths (mapv bean meths) + ;; TODO: fix problems with clojure.lang.IFn, special cased for now + ;; The problem is that the 20-arity (highest one) could not be reified + ;; meths (filter #(<= (:parameterCount %) 19) meths) + meths (mapv (fn [{:keys [name + parameterTypes + returnType + default]}] + (let [ret-type (type->kw returnType)] + {:name name + :desc (conj (mapv type->kw parameterTypes) ret-type) + :default default})) + meths)] + (distinct meths))) + +(let [i clojure.lang.IFn] + (insn/define (insn/visit (interface-data i (class->methods i))))) + +(def reified (babashka.impl.clojure.lang.IFn. {'invoke (fn [& _args] :yep)} {} {})) + +(defn gen-classes [_] + (doseq [i interfaces] + (insn/write (doto (insn/visit (interface-data i (class->methods i))) + insn/define) "target/classes"))) diff --git a/reify/deps.edn b/reify/deps.edn new file mode 100644 index 00000000..35a3c915 --- /dev/null +++ b/reify/deps.edn @@ -0,0 +1,13 @@ +{:deps {org.babashka/sci.impl.types {:mvn/version "0.0.2"}} + :aliases + {:build ;; added by neil + {:paths ["." "build" "src"] + :deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"} + slipset/deps-deploy {:mvn/version "0.2.0"} + org.babashka/sci.impl.types {:mvn/version "0.0.2"} + ;; insn/insn {:mvn/version "0.5.3"} + insn/insn { + :git/sha "f85da286d429b507480f8527b12ce3e1e0e17296" + :git/url "https://github.com/phronmophobic/insn" + }} + :ns-default build}}} diff --git a/reify/src/babashka/impl/reify2.clj b/reify/src/babashka/impl/reify2.clj new file mode 100644 index 00000000..dff57479 --- /dev/null +++ b/reify/src/babashka/impl/reify2.clj @@ -0,0 +1,90 @@ +(ns babashka.impl.reify2 + (:require [babashka.impl.reify2.interfaces :refer [interfaces]])) + +(set! *warn-on-reflection* false) + +(defn method-or-bust [methods k] + (or (get methods k) + (throw (UnsupportedOperationException. "Method not implemented: " k)))) + +(defn reify-ifn [m] + (let [methods (:methods m) + invoke-fn (or (get methods 'invoke) + (fn [& _args] + (throw (UnsupportedOperationException. "Method not implemented: invoke")))) + apply-fn (or (get methods 'applyTo) + (fn [& _args] + (throw (UnsupportedOperationException. "Method not implemented: applyTo"))))] + (reify + sci.impl.types.IReified + (getMethods [_] (:methods m)) + (getInterfaces [_] (:interfaces m)) + (getProtocols [_] (:protocols m)) + clojure.lang.IFn + (invoke [this] (invoke-fn this)) + (invoke [this a0] (invoke-fn this a0)) + (invoke [this a0 a1] (invoke-fn this a0 a1)) + (invoke [this a0 a1 a2] (invoke-fn this a0 a1 a2)) + (invoke [this a0 a1 a2 a3] (invoke-fn this a0 a1 a2 a3)) + (invoke [this a0 a1 a2 a3 a4] (invoke-fn this a0 a1 a2 a3 a4)) + (invoke [this a0 a1 a2 a3 a4 a5] (invoke-fn this a0 a1 a2 a3 a4 a5)) + (invoke [this a0 a1 a2 a3 a4 a5 a6] (invoke-fn this a0 a1 a2 a3 a4 a5 a6)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19)) + (invoke [this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20] (invoke-fn this a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20)) + (applyTo [this arglist] (apply-fn this arglist))))) + +(defn reify-object [m] + (let [methods (:methods m) + toString-fn (or (get methods 'toString) + (fn [this] + (str + (.getName (.getClass this)) + "@" + (Integer/toHexString (.hashCode this)))))] + (reify + sci.impl.types.IReified + (getMethods [_] (:methods m)) + (getInterfaces [_] (:interfaces m)) + (getProtocols [_] (:protocols m)) + java.lang.Object + (toString [this] (toString-fn this))))) + +(defmacro gen-reify-fn [] + `(fn [~'m] + (when (> (count (:interfaces ~'m)) 1) + (throw (UnsupportedOperationException. "babashka reify only supports implementing a single interface"))) + (if (empty? (:interfaces ~'m)) + (reify + sci.impl.types.IReified + (getMethods [_] (:methods ~'m)) + (getInterfaces [_] (:interfaces ~'m)) + (getProtocols [_] (:protocols ~'m))) + (case (.getName ~(with-meta `(first (:interfaces ~'m)) + {:tag 'Class})) + ~@(mapcat identity + (cons + ["clojure.lang.IFn" + `(reify-ifn ~'m) + "java.lang.Object" + `(reify-object ~'m)] + (for [i interfaces] + (let [in (.getName ^Class i)] + [in + `(new ~(symbol (str "babashka.impl." in)) + (:methods ~'m) + (:interfaces ~'m) + (:protocols ~'m))])))))))) + +(def reify-fn (gen-reify-fn)) diff --git a/reify/src/babashka/impl/reify2/interfaces.clj b/reify/src/babashka/impl/reify2/interfaces.clj new file mode 100644 index 00000000..a94feec3 --- /dev/null +++ b/reify/src/babashka/impl/reify2/interfaces.clj @@ -0,0 +1,34 @@ +(ns babashka.impl.reify2.interfaces) + +(def interfaces [java.nio.file.FileVisitor + java.io.FileFilter + java.io.FilenameFilter + clojure.lang.Associative + clojure.lang.ILookup + java.util.Map$Entry + ;; TODO: fix problems with clojure.lang.IFn, special cased for now + ;; The problem is that the 20-arity (highest one) could not be reified + ;; clojure.lang.IFn + clojure.lang.IPersistentCollection + clojure.lang.IReduce + clojure.lang.IReduceInit + clojure.lang.IKVReduce + clojure.lang.Indexed + clojure.lang.IPersistentMap + clojure.lang.IPersistentStack + clojure.lang.Reversible + clojure.lang.Seqable + java.lang.Iterable + java.lang.Runnable + java.net.http.WebSocket$Listener + java.util.Iterator + java.util.function.Consumer + java.util.function.BiConsumer + java.util.function.Function + java.util.function.BiFunction + java.util.function.Predicate + java.util.function.Supplier + java.lang.Comparable + javax.net.ssl.X509TrustManager + clojure.lang.LispReader$Resolver + sun.misc.SignalHandler]) diff --git a/resources/BABASHKA_RELEASED_VERSION b/resources/BABASHKA_RELEASED_VERSION index f83dbb32..6aafafcc 100644 --- a/resources/BABASHKA_RELEASED_VERSION +++ b/resources/BABASHKA_RELEASED_VERSION @@ -1 +1 @@ -0.7.8 \ No newline at end of file +1.0.168 \ No newline at end of file diff --git a/resources/BABASHKA_VERSION b/resources/BABASHKA_VERSION index 6287e3f0..a3361b37 100644 --- a/resources/BABASHKA_VERSION +++ b/resources/BABASHKA_VERSION @@ -1 +1 @@ -0.7.9-SNAPSHOT \ No newline at end of file +1.0.169-SNAPSHOT \ No newline at end of file diff --git a/resources/META-INF/babashka/deps.edn b/resources/META-INF/babashka/deps.edn deleted file mode 100644 index b190e616..00000000 --- a/resources/META-INF/babashka/deps.edn +++ /dev/null @@ -1,145 +0,0 @@ -{:paths ["src" "feature-xml" - "feature-yaml" "feature-csv" "feature-transit" - "feature-java-time" "feature-java-nio" - "feature-httpkit-client" "feature-httpkit-server" - "feature-lanterna" - "feature-core-match" - "feature-hiccup" - "feature-test-check" - "feature-spec-alpha" - "feature-selmer" - "feature-logging" - "feature-priority-map" - "pods/src" - "babashka.nrepl/src" - "depstar/src" "process/src" - "deps.clj/src" "deps.clj/resources" - "resources" "sci/resources"], - :deps {org.clojure/clojure {:mvn/version "1.11.0-beta1"}, - borkdude/sci {:local/root "sci"} - babashka/babashka.curl {:local/root "babashka.curl"} - babashka/fs {:local/root "fs"} - babashka/babashka.core {:local/root "babashka.core"} - borkdude/graal.locking {:mvn/version "0.0.2"}, - org.clojure/core.async {:mvn/version "1.5.648"}, - org.clojure/tools.cli {:mvn/version "1.0.206"}, - org.clojure/data.csv {:mvn/version "1.0.0"}, - cheshire/cheshire {:mvn/version "5.10.2"} - org.clojure/data.xml {:mvn/version "0.2.0-alpha6"} - clj-commons/clj-yaml {:mvn/version "0.7.108"} - com.cognitect/transit-clj {:mvn/version "1.0.329"} - org.clojure/test.check {:mvn/version "1.1.1"} - nrepl/bencode {:mvn/version "1.1.0"} - seancorfield/next.jdbc {:mvn/version "1.1.610"} - org.postgresql/postgresql {:mvn/version "42.2.18"} - org.hsqldb/hsqldb {:mvn/version "2.5.1"} - datascript/datascript {:mvn/version "1.0.1"} - http-kit/http-kit {:mvn/version "2.5.3"} - babashka/clojure-lanterna {:mvn/version "0.9.8-SNAPSHOT"} - org.clojure/core.match {:mvn/version "1.0.0"} - hiccup/hiccup {:mvn/version "2.0.0-alpha2"} - rewrite-clj/rewrite-clj {:mvn/version "1.0.699-alpha"} - selmer/selmer {:mvn/version "1.12.44"} - com.taoensso/timbre {:mvn/version "5.1.2"} - org.clojure/tools.logging {:mvn/version "1.1.0"} - org.clojure/data.priority-map {:mvn/version "1.1.0"} - instaparse/instaparse {:mvn/version "1.4.10"}} - :aliases {:babashka/dev - {:main-opts ["-m" "babashka.main"]} - :profile - {:extra-deps - {com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.5.0"}} - :extra-paths ["test"] - :jvm-opts ["-Djdk.attach.allowAttachSelf" - "-Dclojure.compiler.direct-linking=true"] - :main-opts ["-m" "babashka.profile"]} - :lib-tests - {:extra-paths ["process/src" "process/test" "test-resources/lib_tests"] - :extra-deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"} - org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha" - :sha "0dec1f88cbde74a0470b454396f09a03adb4ae39"} - lambdaisland/regal {:mvn/version "0.0.143"} - cprop/cprop {:mvn/version "0.1.16"} - comb/comb {:mvn/version "0.1.1"} - mvxcvi/arrangement {:mvn/version "2.0.0"} - org.clojure/data.zip {:mvn/version "1.0.0"} - clojure-csv/clojure-csv {:mvn/version "2.0.2"} - org.clojure/math.combinatorics {:mvn/version "0.1.6"} - doric/doric {:mvn/version "0.9.0"} - henryw374/cljc.java-time - {:git/url "https://github.com/henryw374/cljc.java-time.git" - :sha "e3d184b78e933322b3fcaa6ca66cbb8f42a6b35c"} - camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"} - aero/aero {:mvn/version "1.1.6"} - org.clojure/data.generators {:mvn/version "1.0.0"} - honeysql/honeysql {:mvn/version "1.0.461"} - com.github.seancorfield/honeysql {:mvn/version "2.2.840"} - minimallist/minimallist {:mvn/version "0.0.10"} - circleci/bond {:mvn/version "0.6.0"} - version-clj/version-clj {:mvn/version "2.0.2"} - gaka/gaka {:mvn/version "0.3.0"} - failjure/failjure {:mvn/version "2.2.0"} - io.helins/binf {:mvn/version "1.1.0-beta0"} - rm-hull/jasentaa {:mvn/version "0.2.5"} - slingshot/slingshot {:mvn/version "0.12.2"} - io.replikativ/hasch {:mvn/version "0.3.7"} - com.grammarly/omniconf {:mvn/version "0.4.3"} - crispin/crispin {:mvn/version "0.3.8"} - org.clojure/data.json {:mvn/version "2.4.0"} - clj-commons/multigrep {:mvn/version "0.5.0"} - amperity/vault-clj {:mvn/version "1.0.4"} - java-http-clj/java-http-clj {:mvn/version "0.4.3"} - com.stuartsierra/component {:mvn/version "1.0.0"} - org.clojars.askonomm/ruuter {:mvn/version "1.2.2"} - org.clj-commons/digest {:mvn/version "1.4.100"} - hato/hato {:mvn/version "0.8.2"} - better-cond/better-cond {:mvn/version "2.1.1"} - org.clojure/core.specs.alpha {:mvn/version "0.2.62"} - reifyhealth/specmonstah {:git/url "https://github.com/reifyhealth/specmonstah", :sha "a2b357009a3aa99a0c2d2361f3bbcd0b0e36505e"} - exoscale/coax {:mvn/version "1.0.0-alpha14"} - orchestra/orchestra {:mvn/version "2021.01.01-1"} - expound/expound {:mvn/version "0.8.10"} - integrant/integrant {:mvn/version "0.8.0"} - com.stuartsierra/dependency {:mvn/version "1.0.0"} - listora/again {:mvn/version "1.0.0"} - org.clojure/tools.gitlibs {:mvn/version "2.4.172"} - environ/environ {:mvn/version "1.2.0"} - table/table {:git/url "https://github.com/cldwalker/table", :sha "f6293c5f3dac1dd6f525a80fc80930f8ccdf16b7"} - markdown-clj/markdown-clj {:mvn/version "1.10.8"} - org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace", :sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b"} - medley/medley {:mvn/version "1.3.0"} - io.github.cognitect-labs/test-runner {:git/tag "v0.5.0", :git/sha "b3fd0d2"} - borkdude/missing.test.assertions {:git/url "https://github.com/borkdude/missing.test.assertions", :sha "603cb01bee72fb17addacc53c34c85612684ad70"} - dev.nubank/docopt {:mvn/version "0.6.1-fix7"} - testdoc/testdoc {:mvn/version "1.4.1"} - org.clojars.lispyclouds/contajners {:mvn/version "0.0.4"} - borkdude/rewrite-edn {:mvn/version "0.1.0"} - clojure-term-colors/clojure-term-colors {:mvn/version "0.1.0"} - io.aviso/pretty {:mvn/version "1.1.1"} - progrock/progrock {:mvn/version "0.1.2"} - djblue/portal {:mvn/version "0.19.0"} - com.wsscode/cljc-misc {:mvn/version "2021.10.16"} - edn-query-language/eql {:mvn/version "2021.07.18"} - meta-merge/meta-merge {:mvn/version "1.0.0"} - com.exoscale/lingo {:mvn/version "1.0.0-alpha14"} - io.github.swirrl/dogstatsd {:mvn/version "0.1.39"} - org.clojure/algo.monads {:mvn/version "0.1.6"} - io.lambdaforge/datalog-parser {:mvn/version "0.1.9"} - clj-stacktrace/clj-stacktrace {:mvn/version "0.2.8"} - clojure-msgpack/clojure-msgpack {:mvn/version "1.2.1"} - cli-matic/cli-matic {:git/url "https://github.com/l3nz/cli-matic.git", :git/sha "9cd53ba7336363e3d06650dbad413b6f8b06e471"} - aysylu/loom {:mvn/version "1.0.2"} - com.layerware/hugsql-core {:mvn/version "0.5.1"} - com.github.seancorfield/expectations {:mvn/version "2.0.157"}} - :classpath-overrides {org.clojure/clojure nil - org.clojure/spec.alpha nil}} - :clj-nvd - {:extra-deps {clj-nvd/clj-nvd {:git/url "https://github.com/miikka/clj-nvd.git" - :sha "f2ec98699e057a379baf170cb49cf7ad76874a70"}} - :main-opts ["-m" "clj-nvd.core"]} - :test ;; added by neil - {:extra-paths ["test"] - :extra-deps {io.github.cognitect-labs/test-runner - {:git/tag "v0.5.0" :git/sha "b3fd0d2"}} - :main-opts ["-m" "cognitect.test-runner"] - :exec-fn cognitect.test-runner.api/test}}} diff --git a/resources/META-INF/native-image/babashka/babashka/native-image.properties b/resources/META-INF/native-image/babashka/babashka/native-image.properties index 2577a2dc..2e6e0571 100644 --- a/resources/META-INF/native-image/babashka/babashka/native-image.properties +++ b/resources/META-INF/native-image/babashka/babashka/native-image.properties @@ -4,8 +4,9 @@ Args=-H:+ReportExceptionStackTraces \ -J-Dborkdude.dynaload.aot=true \ -H:IncludeResources=BABASHKA_VERSION \ -H:IncludeResources=META-INF/babashka/.* \ + -H:IncludeResources=src/babashka/.* \ -H:IncludeResources=SCI_VERSION \ - -H:Log=registerResource: \ + -H:Log=registerResource:3 \ -H:EnableURLProtocols=http,https,jar \ --enable-all-security-services \ -H:+JNI \ @@ -13,6 +14,7 @@ Args=-H:+ReportExceptionStackTraces \ --report-unsupported-elements-at-runtime \ --initialize-at-build-time=com.fasterxml.jackson \ --initialize-at-build-time=java.sql.SQLException \ + --initialize-at-build-time=org.yaml.snakeyaml \ --initialize-at-run-time=org.postgresql.sspi.SSPIClient \ --initialize-at-run-time=org.httpkit.client.ClientSslEngineFactory$SSLHolder \ -H:ServiceLoaderFeatureExcludeServices=javax.sound.sampled.spi.AudioFileReader \ diff --git a/resources/src/babashka/clojure/spec/alpha.clj b/resources/src/babashka/clojure/spec/alpha.clj new file mode 100644 index 00000000..9b9772d3 --- /dev/null +++ b/resources/src/babashka/clojure/spec/alpha.clj @@ -0,0 +1,2020 @@ +; 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 + ^{:doc "The spec library specifies the structure of data or functions and provides + operations to validate, conform, explain, describe, and generate data based on + the specs. + + Rationale: https://clojure.org/about/spec + Guide: https://clojure.org/guides/spec"} + clojure.spec.alpha + (:refer-clojure :exclude [+ * and assert or cat def keys merge]) + (:require [clojure.walk :as walk] + [clojure.spec.gen.alpha :as gen] + [clojure.string :as str])) + +(alias 'c 'clojure.core) + +;; added because I didn't want to expose clojure.lang.Compiler in bb +;; see https://ask.clojure.org/index.php/11371/consider-adding-demunge-into-clojure-core +(defmacro if-bb [then & [else]] + (if (System/getProperty "babashka.version") + then + else)) + +(if-bb + (require 'clojure.main)) + +(defn demunge [s] + #_:clj-kondo/ignore + (if-bb (clojure.main/demunge s) + (clojure.lang.Compiler/demunge s))) + +(set! *warn-on-reflection* true) + +(def ^:dynamic *recursion-limit* + "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) + can be recursed through during generation. After this a + non-recursive branch will be chosen." + 4) + +(def ^:dynamic *fspec-iterations* + "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" + 21) + +(def ^:dynamic *coll-check-limit* + "The number of elements validated in a collection spec'ed with 'every'" + 101) + +(def ^:dynamic *coll-error-limit* + "The number of errors reported by explain in a collection spec'ed with 'every'" + 20) + +(defprotocol Spec + (conform* [spec x]) + (unform* [spec y]) + (explain* [spec path via in x]) + (gen* [spec overrides path rmap]) + (with-gen* [spec gfn]) + (describe* [spec])) + +(defonce ^:private registry-ref (atom {})) + +(defn- deep-resolve [reg k] + (loop [spec k] + (if (ident? spec) + (recur (get reg spec)) + spec))) + +(defn- reg-resolve + "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not ident" + [k] + (if (ident? k) + (let [reg @registry-ref + spec (get reg k)] + (if-not (ident? spec) + spec + (deep-resolve reg spec))) + k)) + +(defn- reg-resolve! + "returns the spec/regex at end of alias chain starting with k, throws if not found, k if k not ident" + [k] + (if (ident? k) + (c/or (reg-resolve k) + (throw (Exception. (str "Unable to resolve spec: " k)))) + k)) + +(defn spec? + "returns x if x is a spec object, else logical false" + [x] + (when (instance? clojure.spec.alpha.Spec x) + x)) + +(defn regex? + "returns x if x is a (clojure.spec) regex op, else logical false" + [x] + (c/and (::op x) x)) + +(defn- with-name [spec name] + (cond + (ident? spec) spec + (regex? spec) (assoc spec ::name name) + + (instance? clojure.lang.IObj spec) + (with-meta spec (assoc (meta spec) ::name name)))) + +(defn- spec-name [spec] + (cond + (ident? spec) spec + + (regex? spec) (::name spec) + + (instance? clojure.lang.IObj spec) + (-> (meta spec) ::name))) + +(declare spec-impl) +(declare regex-spec-impl) + +(defn- maybe-spec + "spec-or-k must be a spec, regex or resolvable kw/sym, else returns nil." + [spec-or-k] + (let [s (c/or (c/and (ident? spec-or-k) (reg-resolve spec-or-k)) + (spec? spec-or-k) + (regex? spec-or-k) + nil)] + (if (regex? s) + (with-name (regex-spec-impl s nil) (spec-name s)) + s))) + +(defn- the-spec + "spec-or-k must be a spec, regex or kw/sym, else returns nil. Throws if unresolvable kw/sym" + [spec-or-k] + (c/or (maybe-spec spec-or-k) + (when (ident? spec-or-k) + (throw (Exception. (str "Unable to resolve spec: " spec-or-k)))))) + +(defprotocol Specize + (specize* [_] [_ form])) + +(defn- fn-sym [^Object f] + (let [[_ f-ns f-n] (re-matches #"(.*)\$(.*?)(__[0-9]+)?" (.. f getClass getName))] + ;; check for anonymous function + (when (c/and (not= "fn" f-n) + (not= "sci.impl.fns$fun" f-ns)) + (symbol (demunge f-ns) (demunge f-n))))) + +(extend-protocol Specize + clojure.lang.Keyword + (specize* ([k] (specize* (reg-resolve! k))) + ([k _] (specize* (reg-resolve! k)))) + + clojure.lang.Symbol + (specize* ([s] (specize* (reg-resolve! s))) + ([s _] (specize* (reg-resolve! s)))) + + clojure.lang.IPersistentSet + (specize* ([s] (spec-impl s s nil nil)) + ([s form] (spec-impl form s nil nil))) + + Object + (specize* ([o] (if (c/and (not (map? o)) (ifn? o)) + (if-let [s (fn-sym o)] + (spec-impl s o nil nil) + (spec-impl ::unknown o nil nil)) + (spec-impl ::unknown o nil nil))) + ([o form] (spec-impl form o nil nil)))) + +(defn- specize + ([s] (c/or (spec? s) (specize* s))) + ([s form] (c/or (spec? s) (specize* s form)))) + +(defn invalid? + "tests the validity of a conform return value" + [ret] + (identical? ::invalid ret)) + +(defn conform + "Given a spec and a value, returns :clojure.spec.alpha/invalid + if value does not match spec, else the (possibly destructured) value." + [spec x] + (conform* (specize spec) x)) + +(defn unform + "Given a spec and a value created by or compliant with a call to + 'conform' with the same spec, returns a value with all conform + destructuring undone." + [spec x] + (unform* (specize spec) x)) + +(defn form + "returns the spec as data" + [spec] + ;;TODO - incorporate gens + (describe* (specize spec))) + +(defn abbrev [form] + (cond + (seq? form) + (walk/postwalk (fn [form] + (cond + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + (c/and (seq? form) (= 'fn (first form)) (= '[%] (second form))) + (last form) + + :else form)) + form) + + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + :else form)) + +(defn describe + "returns an abbreviated description of the spec as data" + [spec] + (abbrev (form spec))) + +(defn with-gen + "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" + [spec gen-fn] + (let [spec (reg-resolve spec)] + (if (regex? spec) + (assoc spec ::gfn gen-fn) + (with-gen* (specize spec) gen-fn)))) + +(defn explain-data* [spec path via in x] + (let [probs (explain* (specize spec) path via in x)] + (when-not (empty? probs) + {::problems probs + ::spec spec + ::value x}))) + +(defn explain-data + "Given a spec and a value x which ought to conform, returns nil if x + conforms, else a map with at least the key ::problems whose value is + a collection of problem-maps, where problem-map has at least :path :pred and :val + keys describing the predicate and the value that failed at that + path." + [spec x] + (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) + +(defn explain-printer + "Default printer for explain-data. nil indicates a successful validation." + [ed] + (if ed + (let [problems (->> (::problems ed) + (sort-by #(- (count (:in %)))) + (sort-by #(- (count (:path %)))))] + ;;(prn {:ed ed}) + (doseq [{:keys [path pred val reason via in] :as prob} problems] + (pr val) + (print " - failed: ") + (if reason (print reason) (pr (abbrev pred))) + (when-not (empty? in) + (print (str " in: " (pr-str in)))) + (when-not (empty? path) + (print (str " at: " (pr-str path)))) + (when-not (empty? via) + (print (str " spec: " (pr-str (last via))))) + (doseq [[k v] prob] + (when-not (#{:path :pred :val :reason :via :in} k) + (print "\n\t" (pr-str k) " ") + (pr v))) + (newline))) + (println "Success!"))) + +(def ^:dynamic *explain-out* explain-printer) + +(defn explain-out + "Prints explanation data (per 'explain-data') to *out* using the printer in *explain-out*, + by default explain-printer." + [ed] + (*explain-out* ed)) + +(defn explain + "Given a spec and a value that fails to conform, prints an explanation to *out*." + [spec x] + (explain-out (explain-data spec x))) + +(defn explain-str + "Given a spec and a value that fails to conform, returns an explanation as a string." + ^String [spec x] + (with-out-str (explain spec x))) + +(declare valid?) + +(defn- gensub + [spec overrides path rmap form] + ;;(prn {:spec spec :over overrides :path path :form form}) + (let [spec (specize spec)] + (if-let [g (c/or (when-let [gfn (c/or (get overrides (c/or (spec-name spec) spec)) + (get overrides path))] + (gfn)) + (gen* spec overrides path rmap))] + (gen/such-that #(valid? spec %) g 100) + (let [abbr (abbrev form)] + (throw (ex-info (str "Unable to construct gen at: " path " for: " abbr) + {::path path ::form form ::failure :no-gen})))))) + +(defn gen + "Given a spec, returns the generator for it, or throws if none can + be constructed. Optionally an overrides map can be provided which + should map spec names or paths (vectors of keywords) to no-arg + generator-creating fns. These will be used instead of the generators at those + names/paths. Note that parent generator (in the spec or overrides + map) will supersede those of any subtrees. A generator for a regex + op must always return a sequential collection (i.e. a generator for + s/? should return either an empty sequence/vector or a + sequence/vector with one item in it)" + ([spec] (gen spec nil)) + ([spec overrides] (gensub spec overrides [] {::recursion-limit *recursion-limit*} spec))) + +(defn- ->sym + "Returns a symbol from a symbol or var" + [x] + (if (var? x) + (let [m (meta x) + n (:name m) + ns (:ns m)] + (symbol (str ns) (str n))) + x)) + +(defn- unfn [expr] + (if (c/and (seq? expr) + (symbol? (first expr)) + (= "fn*" (name (first expr)))) + (let [[[s] & form] (rest expr)] + (conj (walk/postwalk-replace {s '%} form) '[%] 'fn)) + expr)) + +(defn- res [form] + (cond + (keyword? form) form + (symbol? form) (cond + (= 'fn form) 'clojure.core/fn ;; make tests pass, fn is not a macro in SCI + (= 'not form) 'clojure.core/not ;; make tests pass, not is not a macro in SCI + :else (c/or (-> form resolve ->sym) form)) + (sequential? form) (walk/postwalk #(if (symbol? %) (res %) %) (unfn form)) + :else form)) + +(defn ^:skip-wiki def-impl + "Do not call this directly, use 'def'" + [k form spec] + (c/assert (c/and (ident? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") + (if (nil? spec) + (swap! registry-ref dissoc k) + (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) + spec + (spec-impl form spec nil nil))] + (swap! registry-ref assoc k (with-name spec k)))) + k) + +(defn- ns-qualify + "Qualify symbol s by resolving it or using the current *ns*." + [s] + (if-let [ns-sym (some-> s namespace symbol)] + (c/or (some-> (get (ns-aliases *ns*) ns-sym) str (symbol (name s))) + s) + (symbol (str *ns*) (str s)))) + +(defmacro def + "Given a namespace-qualified keyword or resolvable symbol k, and a + spec, spec-name, predicate or regex-op makes an entry in the + registry mapping k to the spec. Use nil to remove an entry in + the registry for k." + [k spec-form] + (let [k (if (symbol? k) (ns-qualify k) k)] + `(def-impl '~k '~(res spec-form) ~spec-form))) + +(defn registry + "returns the registry map, prefer 'get-spec' to lookup a spec by name" + [] + @registry-ref) + +(defn get-spec + "Returns spec registered for keyword/symbol/var k, or nil." + [k] + (get (registry) (if (keyword? k) k (->sym k)))) + +(defmacro spec + "Takes a single predicate form, e.g. can be the name of a predicate, + like even?, or a fn literal like #(< % 42). Note that it is not + generally necessary to wrap predicates in spec when using the rest + of the spec macros, only to attach a unique generator + + Can also be passed the result of one of the regex ops - + cat, alt, *, +, ?, in which case it will return a regex-conforming + spec, useful when nesting an independent regex. + --- + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator. + + Returns a spec." + [form & {:keys [gen]}] + (when form + `(spec-impl '~(res form) ~form ~gen nil))) + +(defmacro multi-spec + "Takes the name of a spec/predicate-returning multimethod and a + tag-restoring keyword or fn (retag). Returns a spec that when + conforming or explaining data will pass it to the multimethod to get + an appropriate spec. You can e.g. use multi-spec to dynamically and + extensibly associate specs with 'tagged' data (i.e. data where one + of the fields indicates the shape of the rest of the structure). + + (defmulti mspec :tag) + + The methods should ignore their argument and return a predicate/spec: + (defmethod mspec :int [_] (s/keys :req-un [::tag ::i])) + + retag is used during generation to retag generated values with + matching tags. retag can either be a keyword, at which key the + dispatch-tag will be assoc'ed, or a fn of generated value and + dispatch-tag that should return an appropriately retagged value. + + Note that because the tags themselves comprise an open set, + the tag key spec cannot enumerate the values, but can e.g. + test for keyword?. + + Note also that the dispatch values of the multimethod will be + included in the path, i.e. in reporting and gen overrides, even + though those values are not evident in the spec. +" + [mm retag] + `(multi-spec-impl '~(res mm) (var ~mm) ~retag)) + +(defmacro keys + "Creates and returns a map validating spec. :req and :opt are both + vectors of namespaced-qualified keywords. The validator will ensure + the :req keys are present. The :opt keys serve as documentation and + may be used by the generator. + + The :req key vector supports 'and' and 'or' for key groups: + + (s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z]) + + There are also -un versions of :req and :opt. These allow + you to connect unqualified keys to specs. In each case, fully + qualified keywords are passed, which name the specs, but unqualified + keys (with the same name component) are expected and checked at + conform-time, and generated during gen: + + (s/keys :req-un [:my.ns/x :my.ns/y]) + + The above says keys :x and :y are required, and will be validated + and generated by specs (if they exist) named :my.ns/x :my.ns/y + respectively. + + In addition, the values of *all* namespace-qualified keys will be validated + (and possibly destructured) by any registered specs. Note: there is + no support for inline value specification, by design. + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator." + [& {:keys [req req-un opt opt-un gen]}] + (let [unk #(-> % name keyword) + req-keys (filterv keyword? (flatten req)) + req-un-specs (filterv keyword? (flatten req-un)) + _ (c/assert (every? #(c/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) + "all keys must be namespace-qualified keywords") + req-specs (into req-keys req-un-specs) + req-keys (into req-keys (map unk req-un-specs)) + opt-keys (into (vec opt) (map unk opt-un)) + opt-specs (into (vec opt) opt-un) + gx (gensym) + parse-req (fn [rk f] + (map (fn [x] + (if (keyword? x) + `(contains? ~gx ~(f x)) + (walk/postwalk + (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) + x))) + rk)) + pred-exprs [`(map? ~gx)] + pred-exprs (into pred-exprs (parse-req req identity)) + pred-exprs (into pred-exprs (parse-req req-un unk)) + keys-pred `(fn* [~gx] (c/and ~@pred-exprs)) + pred-exprs (mapv (fn [e] `(fn* [~gx] ~e)) pred-exprs) + pred-forms (walk/postwalk res pred-exprs)] + ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) + `(map-spec-impl {:req '~req :opt '~opt :req-un '~req-un :opt-un '~opt-un + :req-keys '~req-keys :req-specs '~req-specs + :opt-keys '~opt-keys :opt-specs '~opt-specs + :pred-forms '~pred-forms + :pred-exprs ~pred-exprs + :keys-pred ~keys-pred + :gfn ~gen}))) + +(defmacro or + "Takes key+pred pairs, e.g. + + (s/or :even even? :small #(< % 42)) + + Returns a destructuring spec that returns a map entry containing the + key of the first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") + `(or-spec-impl ~keys '~pf ~pred-forms nil))) + +(defmacro and + "Takes predicate/spec-forms, e.g. + + (s/and even? #(< % 42)) + + Returns a spec that returns the conformed value. Successive + conformed values propagate through rest of predicates." + [& pred-forms] + `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) + +(defmacro merge + "Takes map-validating specs (e.g. 'keys' specs) and + returns a spec that returns a conformed map satisfying all of the + specs. Unlike 'and', merge can generate maps satisfying the + union of the predicates." + [& pred-forms] + `(merge-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) + +(defn- res-kind + [opts] + (let [{kind :kind :as mopts} opts] + (->> + (if kind + (assoc mopts :kind `~(res kind)) + mopts) + (mapcat identity)))) + +(defmacro every + "takes a pred and validates collection elements against that pred. + + Note that 'every' does not do exhaustive checking, rather it samples + *coll-check-limit* elements. Nor (as a result) does it do any + conforming of elements. 'explain' will report at most *coll-error-limit* + problems. Thus 'every' should be suitable for potentially large + collections. + + Takes several kwargs options that further constrain the collection: + + :kind - a pred that the collection type must satisfy, e.g. vector? + (default nil) Note that if :kind is specified and :into is + not, this pred must generate in order for every to generate. + :count - specifies coll has exactly this count (default nil) + :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) + :distinct - all the elements are distinct (default nil) + + And additional args that control gen + + :gen-max - the maximum coll size to generate (default 20) + :into - one of [], (), {}, #{} - the default collection to generate into + (default: empty coll as generated by :kind pred if supplied, else []) + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator + + See also - coll-of, every-kv +" + [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] + (let [desc (::describe opts) + nopts (-> opts + (dissoc :gen ::describe) + (assoc ::kind-form `'~(res (:kind opts)) + ::describe (c/or desc `'(every ~(res pred) ~@(res-kind opts))))) + gx (gensym) + cpreds (cond-> [(list (c/or kind `coll?) gx)] + count (conj `(= ~count (bounded-count ~count ~gx))) + + (c/or min-count max-count) + (conj `(<= (c/or ~min-count 0) + (bounded-count (if ~max-count (inc ~max-count) ~min-count) ~gx) + (c/or ~max-count Integer/MAX_VALUE))) + + distinct + (conj `(c/or (empty? ~gx) (apply distinct? ~gx))))] + `(every-impl '~pred ~pred ~(assoc nopts ::cpred `(fn* [~gx] (c/and ~@cpreds))) ~gen))) + +(defmacro every-kv + "like 'every' but takes separate key and val preds and works on associative collections. + + Same options as 'every', :into defaults to {} + + See also - map-of" + + [kpred vpred & opts] + (let [desc `(every-kv ~(res kpred) ~(res vpred) ~@(res-kind opts))] + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ::describe '~desc ~@opts))) + +(defmacro coll-of + "Returns a spec for a collection of items satisfying pred. Unlike + 'every', coll-of will exhaustively conform every value. + + Same options as 'every'. conform will produce a collection + corresponding to :into if supplied, else will match the input collection, + avoiding rebuilding when possible. + + See also - every, map-of" + [pred & opts] + (let [desc `(coll-of ~(res pred) ~@(res-kind opts))] + `(every ~pred ::conform-all true ::describe '~desc ~@opts))) + +(defmacro map-of + "Returns a spec for a map whose keys satisfy kpred and vals satisfy + vpred. Unlike 'every-kv', map-of will exhaustively conform every + value. + + Same options as 'every', :kind defaults to map?, with the addition of: + + :conform-keys - conform keys as well as values (default false) + + See also - every-kv" + [kpred vpred & opts] + (let [desc `(map-of ~(res kpred) ~(res vpred) ~@(res-kind opts))] + `(every-kv ~kpred ~vpred ::conform-all true :kind map? ::describe '~desc ~@opts))) + + +(defmacro * + "Returns a regex op that matches zero or more values matching + pred. Produces a vector of matches iff there is at least one match" + [pred-form] + `(rep-impl '~(res pred-form) ~pred-form)) + +(defmacro + + "Returns a regex op that matches one or more values matching + pred. Produces a vector of matches" + [pred-form] + `(rep+impl '~(res pred-form) ~pred-form)) + +(defmacro ? + "Returns a regex op that matches zero or one value matching + pred. Produces a single value (not a collection) if matched." + [pred-form] + `(maybe-impl ~pred-form '~(res pred-form))) + +(defmacro alt + "Takes key+pred pairs, e.g. + + (s/alt :even even? :small #(< % 42)) + + Returns a regex op that returns a map entry containing the key of the + first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return" + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") + `(alt-impl ~keys ~pred-forms '~pf))) + +(defmacro cat + "Takes key+pred pairs, e.g. + + (s/cat :e even? :o odd?) + + Returns a regex op that matches (all) values in sequence, returning a map + containing the keys of each pred and the corresponding value." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + ;;(prn key-pred-forms) + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") + `(cat-impl ~keys ~pred-forms '~pf))) + +(defmacro & + "takes a regex op re, and predicates. Returns a regex-op that consumes + input as per re but subjects the resulting value to the + conjunction of the predicates, and any conforming they might perform." + [re & preds] + (let [pv (vec preds)] + `(amp-impl ~re '~(res re) ~pv '~(mapv res pv)))) + +(defmacro conformer + "takes a predicate function with the semantics of conform i.e. it should return either a + (possibly converted) value or :clojure.spec.alpha/invalid, and returns a + spec that uses it as a predicate/conformer. Optionally takes a + second fn that does unform of result of first" + ([f] `(spec-impl '(conformer ~(res f)) ~f nil true)) + ([f unf] `(spec-impl '(conformer ~(res f) ~(res unf)) ~f nil true ~unf))) + +(defmacro fspec + "takes :args :ret and (optional) :fn kwargs whose values are preds + and returns a spec whose conform/explain take a fn and validates it + using generative testing. The conformed value is always the fn itself. + + See 'fdef' for a single operation that creates an fspec and + registers it, as well as a full description of :args, :ret and :fn + + fspecs can generate functions that validate the arguments and + fabricate a return value compliant with the :ret spec, ignoring + the :fn spec if present. + + Optionally takes :gen generator-fn, which must be a fn of no args + that returns a test.check generator." + + [& {:keys [args ret fn gen] :or {ret `any?}}] + `(fspec-impl (spec ~args) '~(res args) + (spec ~ret) '~(res ret) + (spec ~fn) '~(res fn) ~gen)) + +(defmacro tuple + "takes one or more preds and returns a spec for a tuple, a vector + where each element conforms to the corresponding pred. Each element + will be referred to in paths using its ordinal." + [& preds] + (c/assert (not (empty? preds))) + `(tuple-impl '~(mapv res preds) ~(vec preds))) + +(defn- macroexpand-check + [v args] + (let [fn-spec (get-spec v)] + (when-let [arg-spec (:args fn-spec)] + (when (invalid? (conform arg-spec args)) + (let [ed (assoc (explain-data* arg-spec [] + (if-let [name (spec-name arg-spec)] [name] []) [] args) + ::args args)] + (throw (ex-info + (str "Call to " (->sym v) " did not conform to spec.") + ed))))))) + +(defmacro fdef + "Takes a symbol naming a function, and one or more of the following: + + :args A regex spec for the function arguments as they were a list to be + passed to apply - in this way, a single spec can handle functions with + multiple arities + :ret A spec for the function's return value + :fn A spec of the relationship between args and ret - the + value passed is {:args conformed-args :ret conformed-ret} and is + expected to contain predicates that relate those values + + Qualifies fn-sym with resolve, or using *ns* if no resolution found. + Registers an fspec in the global registry, where it can be retrieved + by calling get-spec with the var or fully-qualified symbol. + + Once registered, function specs are included in doc, checked by + instrument, tested by the runner clojure.spec.test.alpha/check, and (if + a macro) used to explain errors during macroexpansion. + + Note that :fn specs require the presence of :args and :ret specs to + conform values, and so :fn specs will be ignored if :args or :ret + are missing. + + Returns the qualified fn-sym. + + For example, to register function specs for the symbol function: + + (s/fdef clojure.core/symbol + :args (s/alt :separate (s/cat :ns string? :n string?) + :str string? + :sym symbol?) + :ret symbol?)" + [fn-sym & specs] + `(clojure.spec.alpha/def ~fn-sym (clojure.spec.alpha/fspec ~@specs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- recur-limit? [rmap id path k] + (c/and (> (get rmap id) (::recursion-limit rmap)) + (contains? (set path) k))) + +(defn- inck [m k] + (assoc m k (inc (c/or (get m k) 0)))) + +(defn- dt + ([pred x form] (dt pred x form nil)) + ([pred x form cpred?] + (if pred + (if-let [spec (the-spec pred)] + (conform spec x) + (if (ifn? pred) + (if cpred? + (pred x) + (if (pred x) x ::invalid)) + (throw (Exception. (str (pr-str form) " is not a fn, expected predicate fn"))))) + x))) + +(defn valid? + "Helper function that returns true when x is valid for spec." + ([spec x] + (let [spec (specize spec)] + (not (invalid? (conform* spec x))))) + ([spec x form] + (let [spec (specize spec form)] + (not (invalid? (conform* spec x)))))) + +(defn- pvalid? + "internal helper function that returns true when x is valid for spec." + ([pred x] + (not (invalid? (dt pred x ::unknown)))) + ([pred x form] + (not (invalid? (dt pred x form))))) + +(defn- explain-1 [form pred path via in v] + ;;(prn {:form form :pred pred :path path :in in :v v}) + (let [pred (maybe-spec pred)] + (if (spec? pred) + (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) in v) + [{:path path :pred form :val v :via via :in in}]))) + +(declare or-k-gen and-k-gen) + +(defn- k-gen + "returns a generator for form f, which can be a keyword or a list + starting with 'or or 'and." + [f] + (cond + (keyword? f) (gen/return f) + (= 'or (first f)) (or-k-gen 1 (rest f)) + (= 'and (first f)) (and-k-gen (rest f)))) + +(defn- or-k-gen + "returns a tuple generator made up of generators for a random subset + of min-count (default 0) to all elements in s." + ([s] (or-k-gen 0 s)) + ([min-count s] + (gen/bind (gen/tuple + (gen/choose min-count (count s)) + (gen/shuffle (map k-gen s))) + (fn [[n gens]] + (apply gen/tuple (take n gens)))))) + +(defn- and-k-gen + "returns a tuple generator made up of generators for every element + in s." + [s] + (apply gen/tuple (map k-gen s))) + + +(defn ^:skip-wiki map-spec-impl + "Do not call this directly, use 'spec' with a map argument" + [{:keys [req-un opt-un keys-pred pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] + :as argm}] + (let [k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) + keys->specnames #(c/or (k->s %) %) + id (java.util.UUID/randomUUID)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ m] + (if (keys-pred m) + (let [reg (registry)] + (loop [ret m, [[k v] & ks :as keys] m] + (if keys + (let [sname (keys->specnames k)] + (if-let [s (get reg sname)] + (let [cv (conform s v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret k cv)) + ks))) + (recur ret ks))) + ret))) + ::invalid)) + (unform* [_ m] + (let [reg (registry)] + (loop [ret m, [k & ks :as keys] (c/keys m)] + (if keys + (if (contains? reg (keys->specnames k)) + (let [cv (get m k) + v (unform (keys->specnames k) cv)] + (recur (if (identical? cv v) ret (assoc ret k v)) + ks)) + (recur ret ks)) + ret)))) + (explain* [_ path via in x] + (if-not (map? x) + [{:path path :pred `map? :val x :via via :in in}] + (let [reg (registry)] + (apply concat + (when-let [probs (->> (map (fn [pred form] (when-not (pred x) form)) + pred-exprs pred-forms) + (keep identity) + seq)] + (map + #(identity {:path path :pred % :val x :via via :in in}) + probs)) + (map (fn [[k v]] + (when-not (c/or (not (contains? reg (keys->specnames k))) + (pvalid? (keys->specnames k) v k)) + (explain-1 (keys->specnames k) (keys->specnames k) (conj path k) via (conj in k) v))) + (seq x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [rmap (inck rmap id) + rgen (fn [k s] [k (gensub s overrides (conj path k) rmap k)]) + ogen (fn [k s] + (when-not (recur-limit? rmap id path k) + [k (gen/delay (gensub s overrides (conj path k) rmap k))])) + reqs (map rgen req-keys req-specs) + opts (remove nil? (map ogen opt-keys opt-specs))] + (when (every? identity (concat (map second reqs) (map second opts))) + (gen/bind + (gen/tuple + (and-k-gen req) + (or-k-gen opt) + (and-k-gen req-un) + (or-k-gen opt-un)) + (fn [[req-ks opt-ks req-un-ks opt-un-ks]] + (let [qks (flatten (concat req-ks opt-ks)) + unqks (map (comp keyword name) (flatten (concat req-un-ks opt-un-ks)))] + (->> (into reqs opts) + (filter #((set (concat qks unqks)) (first %))) + (apply concat) + (apply gen/hash-map))))))))) + (with-gen* [_ gfn] (map-spec-impl (assoc argm :gfn gfn))) + (describe* [_] (cons `keys + (cond-> [] + req (conj :req req) + opt (conj :opt opt) + req-un (conj :req-un req-un) + opt-un (conj :opt-un opt-un))))))) + + + + +(defn ^:skip-wiki spec-impl + "Do not call this directly, use 'spec'" + ([form pred gfn cpred?] (spec-impl form pred gfn cpred? nil)) + ([form pred gfn cpred? unc] + (cond + (spec? pred) (cond-> pred gfn (with-gen gfn)) + (regex? pred) (regex-spec-impl pred gfn) + (ident? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) + :else + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ret (pred x)] + (if cpred? + ret + (if ret x ::invalid)))) + (unform* [_ x] (if cpred? + (if unc + (unc x) + (throw (IllegalStateException. "no unform fn for conformer"))) + x)) + (explain* [_ path via in x] + (when (invalid? (dt pred x form cpred?)) + [{:path path :pred form :val x :via via :in in}])) + (gen* [_ _ _ _] (if gfn + (gfn) + (gen/gen-for-pred pred))) + (with-gen* [_ gfn] (spec-impl form pred gfn cpred? unc)) + (describe* [_] form))))) + +(defn ^:skip-wiki multi-spec-impl + "Do not call this directly, use 'multi-spec'" + ([form mmvar retag] (multi-spec-impl form mmvar retag nil)) + ([form mmvar retag gfn] + (let [id (java.util.UUID/randomUUID) + predx #(let [^clojure.lang.MultiFn mm @mmvar] + (c/and (.getMethod mm ((.-dispatchFn mm) %)) + (mm %))) + dval #((.-dispatchFn ^clojure.lang.MultiFn @mmvar) %) + tag (if (keyword? retag) + #(assoc %1 retag %2) + retag)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (if-let [pred (predx x)] + (dt pred x form) + ::invalid)) + (unform* [_ x] (if-let [pred (predx x)] + (unform pred x) + (throw (IllegalStateException. (str "No method of: " form " for dispatch value: " (dval x)))))) + (explain* [_ path via in x] + (let [dv (dval x) + path (conj path dv)] + (if-let [pred (predx x)] + (explain-1 form pred path via in x) + [{:path path :pred form :val x :reason "no method" :via via :in in}]))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [[k f]] + (let [p (f nil)] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gen/delay + (gen/fmap + #(tag % k) + (gensub p overrides (conj path k) rmap (list 'method form k)))))))) + gs (->> (methods @mmvar) + (remove (fn [[k]] (invalid? k))) + (map gen) + (remove nil?))] + (when (every? identity gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) + (describe* [_] `(multi-spec ~form ~retag)))))) + +(defn ^:skip-wiki tuple-impl + "Do not call this directly, use 'tuple'" + ([forms preds] (tuple-impl forms preds nil)) + ([forms preds gfn] + (let [specs (delay (mapv specize preds forms)) + cnt (count preds)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (let [specs @specs] + (if-not (c/and (vector? x) + (= (count x) cnt)) + ::invalid + (loop [ret x, i 0] + (if (= i cnt) + ret + (let [v (x i) + cv (conform* (specs i) v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret i cv)) + (inc i))))))))) + (unform* [_ x] + (c/assert (c/and (vector? x) + (= (count x) (count preds)))) + (loop [ret x, i 0] + (if (= i (count x)) + ret + (let [cv (x i) + v (unform (preds i) cv)] + (recur (if (identical? cv v) ret (assoc ret i v)) + (inc i)))))) + (explain* [_ path via in x] + (cond + (not (vector? x)) + [{:path path :pred `vector? :val x :via via :in in}] + + (not= (count x) (count preds)) + [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] + + :else + (apply concat + (map (fn [i form pred] + (let [v (x i)] + (when-not (pvalid? pred v) + (explain-1 form pred (conj path i) via (conj in i) v)))) + (range (count preds)) forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [i p f] + (gensub p overrides (conj path i) rmap f)) + gs (map gen (range (count preds)) preds forms)] + (when (every? identity gs) + (apply gen/tuple gs))))) + (with-gen* [_ gfn] (tuple-impl forms preds gfn)) + (describe* [_] `(tuple ~@forms)))))) + +(defn- tagged-ret [tag ret] + (clojure.lang.MapEntry. tag ret)) + +(defn ^:skip-wiki or-spec-impl + "Do not call this directly, use 'or'" + [keys forms preds gfn] + (let [id (java.util.UUID/randomUUID) + kps (zipmap keys preds) + specs (delay (mapv specize preds forms)) + cform (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + (let [ret (conform* (specs 1) x)] + (if (invalid? ret) + ::invalid + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + (let [ret (conform* (specs 1) x)] + (if (invalid? ret) + (let [ret (conform* (specs 2) x)] + (if (invalid? ret) + ::invalid + (tagged-ret (keys 2) ret))) + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + (fn [x] + (let [specs @specs] + (loop [i 0] + (if (< i (count specs)) + (let [spec (specs i)] + (let [ret (conform* spec x)] + (if (invalid? ret) + (recur (inc i)) + (tagged-ret (keys i) ret)))) + ::invalid)))))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (cform x)) + (unform* [_ [k x]] (unform (kps k) x)) + (explain* [this path via in x] + (when-not (pvalid? this x) + (apply concat + (map (fn [k form pred] + (when-not (pvalid? pred x) + (explain-1 form pred (conj path k) via in x))) + keys forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [k p f] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gen/delay + (gensub p overrides (conj path k) rmap f))))) + gs (remove nil? (map gen keys preds forms))] + (when-not (empty? gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (or-spec-impl keys forms preds gfn)) + (describe* [_] `(or ~@(mapcat vector keys forms)))))) + +(defn- and-preds [x preds forms] + (loop [ret x + [pred & preds] preds + [form & forms] forms] + (if pred + (let [nret (dt pred ret form)] + (if (invalid? nret) + ::invalid + ;;propagate conformed values + (recur nret preds forms))) + ret))) + +(defn- explain-pred-list + [forms preds path via in x] + (loop [ret x + [form & forms] forms + [pred & preds] preds] + (when pred + (let [nret (dt pred ret form)] + (if (invalid? nret) + (explain-1 form pred path via in ret) + (recur nret forms preds)))))) + +(defn ^:skip-wiki and-spec-impl + "Do not call this directly, use 'and'" + [forms preds gfn] + (let [specs (delay (mapv specize preds forms)) + cform + (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + ::invalid + (conform* (specs 1) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + ::invalid + (let [ret (conform* (specs 1) ret)] + (if (invalid? ret) + ::invalid + (conform* (specs 2) ret)))))) + (fn [x] + (let [specs @specs] + (loop [ret x i 0] + (if (< i (count specs)) + (let [nret (conform* (specs i) ret)] + (if (invalid? nret) + ::invalid + ;;propagate conformed values + (recur nret (inc i)))) + ret)))))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (cform x)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) + (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) + (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) + (describe* [_] `(and ~@forms))))) + +(defn ^:skip-wiki merge-spec-impl + "Do not call this directly, use 'merge'" + [forms preds gfn] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] + (if (some invalid? ms) + ::invalid + (apply c/merge ms)))) + (unform* [_ x] (apply c/merge (map #(unform % x) (reverse preds)))) + (explain* [_ path via in x] + (apply concat + (map #(explain-1 %1 %2 path via in x) + forms preds))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/fmap + #(apply c/merge %) + (apply gen/tuple (map #(gensub %1 overrides path rmap %2) + preds forms))))) + (with-gen* [_ gfn] (merge-spec-impl forms preds gfn)) + (describe* [_] `(merge ~@forms)))) + +(defn- coll-prob [x kfn kform distinct count min-count max-count + path via in] + (let [pred (c/or kfn coll?) + kform (c/or kform `coll?)] + (cond + (not (pvalid? pred x)) + (explain-1 kform pred path via in x) + + (c/and count (not= count (bounded-count count x))) + [{:path path :pred `(= ~count (c/count ~'%)) :val x :via via :in in}] + + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count Integer/MAX_VALUE)))) + [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}] + + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + [{:path path :pred 'distinct? :val x :via via :in in}]))) + +(def ^:private empty-coll {`vector? [], `set? #{}, `list? (), `map? {}}) + +(defn ^:skip-wiki every-impl + "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" + ([form pred opts] (every-impl form pred opts nil)) + ([form pred {conform-into :into + describe-form ::describe + :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn ::cpred + conform-keys ::conform-all] + :or {gen-max 20} + :as opts} + gfn] + (let [gen-into (if conform-into (empty conform-into) (get empty-coll kind-form)) + spec (delay (specize pred)) + check? #(valid? @spec %) + kfn (c/or kfn (fn [i v] i)) + addcv (fn [ret i v cv] (conj ret cv)) + cfns (fn [x] + ;;returns a tuple of [init add complete] fns + (cond + (c/and (vector? x) (c/or (not conform-into) (vector? conform-into))) + [identity + (fn [ret i v cv] + (if (identical? v cv) + ret + (assoc ret i cv))) + identity] + + (c/and (map? x) (c/or (c/and kind (not conform-into)) (map? conform-into))) + [(if conform-keys empty identity) + (fn [ret i v cv] + (if (c/and (identical? v cv) (not conform-keys)) + ret + (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) + identity] + + (c/or (list? conform-into) (seq? conform-into) (c/and (not conform-into) (c/or (list? x) (seq? x)))) + [(constantly ()) addcv reverse] + + :else [#(empty (c/or conform-into %)) addcv identity]))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (let [spec @spec] + (cond + (not (cpred x)) ::invalid + + conform-all + (let [[init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if vseq + (let [cv (conform* spec v)] + (if (invalid? cv) + ::invalid + (recur (add ret i v cv) (inc i) vs))) + (complete ret)))) + + + :else + (if (indexed? x) + (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] + (loop [i 0] + (if (>= i (c/count x)) + x + (if (valid? spec (nth x i)) + (recur (c/+ i step)) + ::invalid)))) + (let [limit *coll-check-limit*] + (loop [i 0 [v & vs :as vseq] (seq x)] + (cond + (c/or (nil? vseq) (= i limit)) x + (valid? spec v) (recur (inc i) vs) + :else ::invalid))))))) + (unform* [_ x] + (if conform-all + (let [spec @spec + [init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if (>= i (c/count x)) + (complete ret) + (recur (add ret i v (unform* spec v)) (inc i) vs)))) + x)) + (explain* [_ path via in x] + (c/or (coll-prob x kind kind-form distinct count min-count max-count + path via in) + (apply concat + ((if conform-all identity (partial take *coll-error-limit*)) + (keep identity + (map (fn [i v] + (let [k (kfn i v)] + (when-not (check? v) + (let [prob (explain-1 form pred path via (conj in k) v)] + prob)))) + (range) x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [pgen (gensub pred overrides path rmap form)] + (gen/bind + (cond + gen-into (gen/return gen-into) + kind (gen/fmap #(if (empty? %) % (empty %)) + (gensub kind overrides path rmap form)) + :else (gen/return [])) + (fn [init] + (gen/fmap + #(if (vector? init) % (into init %)) + (cond + distinct + (if count + (gen/vector-distinct pgen {:num-elements count :max-tries 100}) + (gen/vector-distinct pgen {:min-elements (c/or min-count 0) + :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) + :max-tries 100})) + + count + (gen/vector pgen count) + + (c/or min-count max-count) + (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) + + :else + (gen/vector pgen 0 gen-max)))))))) + + (with-gen* [_ gfn] (every-impl form pred opts gfn)) + (describe* [_] (c/or describe-form `(every ~(res form) ~@(mapcat identity opts)))))))) + +;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; +;;See: +;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/ +;; http://www.ccs.neu.edu/home/turon/re-deriv.pdf + +;;ctors +(defn- accept [x] {::op ::accept :ret x}) + +(defn- accept? [{:keys [::op]}] + (= ::accept op)) + +(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret, rep+ :rep+}] + (when (every? identity ps) + (if (accept? p1) + (let [rp (:ret p1) + ret (conj ret (if ks {k1 rp} rp))] + (if pr + (pcat* {:ps pr :ks kr :forms fr :ret ret}) + (accept ret))) + {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms :rep+ rep+}))) + +(defn- pcat [& ps] (pcat* {:ps ps :ret []})) + +(defn ^:skip-wiki cat-impl + "Do not call this directly, use 'cat'" + [ks ps forms] + (pcat* {:ks ks, :ps ps, :forms forms, :ret {}})) + +(defn- rep* [p1 p2 ret splice form] + (when p1 + (let [r {::op ::rep, :p2 p2, :splice splice, :forms form :id (java.util.UUID/randomUUID)}] + (if (accept? p1) + (assoc r :p1 p2 :ret (conj ret (:ret p1))) + (assoc r :p1 p1, :ret ret))))) + +(defn ^:skip-wiki rep-impl + "Do not call this directly, use '*'" + [form p] (rep* p p [] false form)) + +(defn ^:skip-wiki rep+impl + "Do not call this directly, use '+'" + [form p] + (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret [] :rep+ form})) + +(defn ^:skip-wiki amp-impl + "Do not call this directly, use '&'" + [re re-form preds pred-forms] + {::op ::amp :p1 re :amp re-form :ps preds :forms pred-forms}) + +(defn- filter-alt [ps ks forms f] + (if (c/or ks forms) + (let [pks (->> (map vector ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + (filter #(-> % first f)))] + [(seq (map first pks)) (when ks (seq (map second pks))) (when forms (seq (map #(nth % 2) pks)))]) + [(seq (filter f ps)) ks forms])) + +(defn- alt* [ps ks forms] + (let [[[p1 & pr :as ps] [k1 :as ks] forms] (filter-alt ps ks forms identity)] + (when ps + (let [ret {::op ::alt, :ps ps, :ks ks :forms forms}] + (if (nil? pr) + (if k1 + (if (accept? p1) + (accept (tagged-ret k1 (:ret p1))) + ret) + p1) + ret))))) + +(defn- alts [& ps] (alt* ps nil nil)) +(defn- alt2 [p1 p2] (if (c/and p1 p2) (alts p1 p2) (c/or p1 p2))) + +(defn ^:skip-wiki alt-impl + "Do not call this directly, use 'alt'" + [ks ps forms] (assoc (alt* ps ks forms) :id (java.util.UUID/randomUUID))) + +(defn ^:skip-wiki maybe-impl + "Do not call this directly, use '?'" + [p form] (assoc (alt* [p (accept ::nil)] nil [form ::nil]) :maybe form)) + +(defn- noret? [p1 pret] + (c/or (= pret ::nil) + (c/and (#{::rep ::pcat} (::op (reg-resolve! p1))) ;;hrm, shouldn't know these + (empty? pret)) + nil)) + +(declare preturn) + +(defn- accept-nil? [p] + (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve! p)] + (case op + ::accept true + nil nil + ::amp (c/and (accept-nil? p1) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (not (invalid? ret)))) + ::rep (c/or (identical? p1 p2) (accept-nil? p1)) + ::pcat (every? accept-nil? ps) + ::alt (c/some accept-nil? ps)))) + +(declare add-ret) + +(defn- preturn [p] + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve! p)] + (case op + ::accept ret + nil nil + ::amp (let [pret (preturn p1)] + (if (noret? p1 pret) + ::nil + (and-preds pret ps forms))) + ::rep (add-ret p1 ret k) + ::pcat (add-ret p0 ret k) + ::alt (let [[[p0] [k0]] (filter-alt ps ks forms accept-nil?) + r (if (nil? p0) ::nil (preturn p0))] + (if k0 (tagged-ret k0 r) r))))) + +(defn- op-unform [p x] + ;;(prn {:p p :x x}) + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve! p) + kps (zipmap ks ps)] + (case op + ::accept [ret] + nil [(unform p x)] + ::amp (let [px (reduce #(unform %2 %1) x (reverse ps))] + (op-unform p1 px)) + ::rep (mapcat #(op-unform p1 %) x) + ::pcat (if rep+ + (mapcat #(op-unform p0 %) x) + (mapcat (fn [k] + (when (contains? x k) + (op-unform (kps k) (get x k)))) + ks)) + ::alt (if maybe + [(unform p0 x)] + (let [[k v] x] + (op-unform (kps k) v)))))) + +(defn- add-ret [p r k] + (let [{:keys [::op ps splice] :as p} (reg-resolve! p) + prop #(let [ret (preturn p)] + (if (empty? ret) r ((if splice into conj) r (if k {k ret} ret))))] + (case op + nil r + (::alt ::accept ::amp) + (let [ret (preturn p)] + ;;(prn {:ret ret}) + (if (= ret ::nil) r (conj r (if k {k ret} ret)))) + + (::rep ::pcat) (prop)))) + +(defn- deriv + [p x] + (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms amp] :as p} (reg-resolve! p)] + (when p + (case op + ::accept nil + nil (let [ret (dt p x p)] + (when-not (invalid? ret) (accept ret))) + ::amp (when-let [p1 (deriv p1 x)] + (if (= ::accept (::op p1)) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (when-not (invalid? ret) + (accept ret))) + (amp-impl p1 amp ps forms))) + ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) + (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) + ::alt (alt* (map #(deriv % x) ps) ks forms) + ::rep (alt2 (rep* (deriv p1 x) p2 ret splice forms) + (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) + +(defn- op-describe [p] + (let [{:keys [::op ps ks forms splice p1 rep+ maybe amp] :as p} (reg-resolve! p)] + ;;(prn {:op op :ks ks :forms forms :p p}) + (when p + (case op + ::accept nil + nil p + ::amp (list* 'clojure.spec.alpha/& amp forms) + ::pcat (if rep+ + (list `+ rep+) + (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) + ::alt (if maybe + (list `? maybe) + (cons `alt (mapcat vector ks forms))) + ::rep (list (if splice `+ `*) forms))))) + +(defn- op-explain [form p path via in input] + ;;(prn {:form form :p p :path path :input input}) + (let [[x :as input] input + {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve! p) + via (if-let [name (spec-name p)] (conj via name) via) + insufficient (fn [path form] + [{:path path + :reason "Insufficient input" + :pred form + :val () + :via via + :in in}])] + (when p + (case op + ::accept nil + nil (if (empty? input) + (insufficient path form) + (explain-1 form p path via in x)) + ::amp (if (empty? input) + (if (accept-nil? p1) + (explain-pred-list forms ps path via in (preturn p1)) + (insufficient path (:amp p))) + (if-let [p1 (deriv p1 x)] + (explain-pred-list forms ps path via in (preturn p1)) + (op-explain (:amp p) p1 path via in input))) + ::pcat (let [pkfs (map vector + ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + [pred k form] (if (= 1 (count pkfs)) + (first pkfs) + (first (remove (fn [[p]] (accept-nil? p)) pkfs))) + path (if k (conj path k) path) + form (c/or form (op-describe pred))] + (if (c/and (empty? input) (not pred)) + (insufficient path form) + (op-explain form pred path via in input))) + ::alt (if (empty? input) + (insufficient path (op-describe p)) + (apply concat + (map (fn [k form pred] + (op-explain (c/or form (op-describe pred)) + pred + (if k (conj path k) path) + via + in + input)) + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil)) + ps))) + ::rep (op-explain (if (identical? p1 p2) + forms + (op-describe p1)) + p1 path via in input))))) + +(defn- re-gen [p overrides path rmap f] + ;;(prn {:op op :ks ks :forms forms}) + (let [origp p + {:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) + rmap (if id (inck rmap id) rmap) + ggens (fn [ps ks forms] + (let [gen (fn [p k f] + ;;(prn {:k k :path path :rmap rmap :op op :id id}) + (when-not (c/and rmap id k (recur-limit? rmap id path k)) + (if id + (gen/delay (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))) + (re-gen p overrides (if k (conj path k) path) rmap (c/or f p)))))] + (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] + (c/or (when-let [gfn (c/or (get overrides (spec-name origp)) + (get overrides (spec-name p) ) + (get overrides path))] + (case op + (:accept nil) (gen/fmap vector (gfn)) + (gfn))) + (when gfn + (gfn)) + (when p + (case op + ::accept (if (= ret ::nil) + (gen/return []) + (gen/return [ret])) + nil (when-let [g (gensub p overrides path rmap f)] + (gen/fmap vector g)) + ::amp (re-gen p1 overrides path rmap (op-describe p1)) + ::pcat (let [gens (ggens ps ks forms)] + (when (every? identity gens) + (apply gen/cat gens))) + ::alt (let [gens (remove nil? (ggens ps ks forms))] + (when-not (empty? gens) + (gen/one-of gens))) + ::rep (if (recur-limit? rmap id [id] id) + (gen/return []) + (when-let [g (re-gen p2 overrides path rmap forms)] + (gen/fmap #(apply concat %) + (gen/vector g))))))))) + +(defn- re-conform [p [x & xs :as data]] + ;;(prn {:p p :x x :xs xs}) + (if (empty? data) + (if (accept-nil? p) + (let [ret (preturn p)] + (if (= ret ::nil) + nil + ret)) + ::invalid) + (if-let [dp (deriv p x)] + (recur dp xs) + ::invalid))) + +(defn- re-explain [path via in re input] + (loop [p re [x & xs :as data] input i 0] + ;;(prn {:p p :x x :xs xs :re re}) (prn) + (if (empty? data) + (if (accept-nil? p) + nil ;;success + (op-explain (op-describe p) p path via in nil)) + (if-let [dp (deriv p x)] + (recur dp xs (inc i)) + (if (accept? p) + (if (= (::op p) ::pcat) + (op-explain (op-describe p) p path via (conj in i) (seq data)) + [{:path path + :reason "Extra input" + :pred (op-describe re) + :val data + :via via + :in (conj in i)}]) + (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) + [{:path path + :reason "Extra input" + :pred (op-describe p) + :val data + :via via + :in (conj in i)}])))))) + +(defn ^:skip-wiki regex-spec-impl + "Do not call this directly, use 'spec' with a regex op argument" + [re gfn] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (if (c/or (nil? x) (sequential? x)) + (re-conform re (seq x)) + ::invalid)) + (unform* [_ x] (op-unform re x)) + (explain* [_ path via in x] + (if (c/or (nil? x) (sequential? x)) + (re-explain path via in re (seq x)) + [{:path path :pred (res `#(c/or (nil? %) (sequential? %))) :val x :via via :in in}])) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (re-gen re overrides path rmap (op-describe re)))) + (with-gen* [_ gfn] (regex-spec-impl re gfn)) + (describe* [_] (op-describe re)))) + +;;;;;;;;;;;;;;;;; HOFs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- call-valid? + [f specs args] + (let [cargs (conform (:args specs) args)] + (when-not (invalid? cargs) + (let [ret (apply f args) + cret (conform (:ret specs) ret)] + (c/and (not (invalid? cret)) + (if (:fn specs) + (pvalid? (:fn specs) {:args cargs :ret cret}) + true)))))) + +(defn- validate-fn + "returns f if valid, else smallest" + [f specs iters] + (let [g (gen (:args specs)) + prop (gen/for-all* [g] #(call-valid? f specs %))] + (let [ret (gen/quick-check iters prop)] + (if-let [[smallest] (-> ret :shrunk :smallest)] + smallest + f)))) + +(defn ^:skip-wiki fspec-impl + "Do not call this directly, use 'fspec'" + [argspec aform retspec rform fnspec fform gfn] + (let [specs {:args argspec :ret retspec :fn fnspec}] + (reify + clojure.lang.ILookup + (valAt [this k] (get specs k)) + (valAt [_ k not-found] (get specs k not-found)) + + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [this f] (if argspec + (if (ifn? f) + (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) + ::invalid) + (throw (Exception. (str "Can't conform fspec without args spec: " (pr-str (describe this))))))) + (unform* [_ f] f) + (explain* [_ path via in f] + (if (ifn? f) + (let [args (validate-fn f specs 100)] + (if (identical? f args) ;;hrm, we might not be able to reproduce + nil + (let [ret (try (apply f args) (catch Throwable t t))] + (if (instance? Throwable ret) + ;;TODO add exception data + [{:path path :pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}] + + (let [cret (dt retspec ret rform)] + (if (invalid? cret) + (explain-1 rform retspec (conj path :ret) via in ret) + (when fnspec + (let [cargs (conform argspec args)] + (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) + [{:path path :pred 'ifn? :val f :via via :in in}])) + (gen* [_ overrides _ _] (if gfn + (gfn) + (gen/return + (fn [& args] + (c/assert (pvalid? argspec args) (with-out-str (explain argspec args))) + (gen/generate (gen retspec overrides)))))) + (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) + (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(clojure.spec.alpha/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)) #(map (fn [[k v]] {::k k ::v v}) %))) + +(defmacro keys* + "takes the same arguments as spec/keys and returns a regex op that matches sequences of key/values, + converts them into a map, and conforms that map with a corresponding + spec/keys call: + + user=> (s/conform (s/keys :req-un [::a ::c]) {:a 1 :c 2}) + {:a 1, :c 2} + user=> (s/conform (s/keys* :req-un [::a ::c]) [:a 1 :c 2]) + {:a 1, :c 2} + + the resulting regex op can be composed into a larger regex: + + user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) + {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" + [& kspecs] + `(let [mspec# (keys ~@kspecs)] + (with-gen (clojure.spec.alpha/& (* (cat ::k keyword? ::v any?)) ::kvs->map mspec#) + (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) + +(defn ^:skip-wiki nonconforming + "takes a spec and returns a spec that has the same properties except + 'conform' returns the original (not the conformed) value. Note, will specize regex ops." + [spec] + (let [spec (delay (specize spec))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ret (conform* @spec x)] + (if (invalid? ret) + ::invalid + x))) + (unform* [_ x] x) + (explain* [_ path via in x] (explain* @spec path via in x)) + (gen* [_ overrides path rmap] (gen* @spec overrides path rmap)) + (with-gen* [_ gfn] (nonconforming (with-gen* @spec gfn))) + (describe* [_] `(nonconforming ~(describe* @spec)))))) + +(defn ^:skip-wiki nilable-impl + "Do not call this directly, use 'nilable'" + [form pred gfn] + (let [spec (delay (specize pred form))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (if (nil? x) nil (conform* @spec x))) + (unform* [_ x] (if (nil? x) nil (unform* @spec x))) + (explain* [_ path via in x] + (when-not (c/or (pvalid? @spec x) (nil? x)) + (conj + (explain-1 form pred (conj path ::pred) via in x) + {:path (conj path ::nil) :pred 'nil? :val x :via via :in in}))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/frequency + [[1 (gen/delay (gen/return nil))] + [9 (gen/delay (gensub pred overrides (conj path ::pred) rmap form))]]))) + (with-gen* [_ gfn] (nilable-impl form pred gfn)) + (describe* [_] `(nilable ~(res form)))))) + +(defmacro nilable + "returns a spec that accepts nil and values satisfying pred" + [pred] + (let [pf (res pred)] + `(nilable-impl '~pf ~pred nil))) + +(defn exercise + "generates a number (default 10) of values compatible with spec and maps conform over them, + returning a sequence of [val conformed-val] tuples. Optionally takes + a generator overrides map as per gen" + ([spec] (exercise spec 10)) + ([spec n] (exercise spec n nil)) + ([spec n overrides] + (map #(vector % (conform spec %)) (gen/sample (gen spec overrides) n)))) + +(defn exercise-fn + "exercises the fn named by sym (a symbol) by applying it to + n (default 10) generated samples of its args spec. When fspec is + supplied its arg spec is used, and sym-or-f can be a fn. Returns a + sequence of tuples of [args ret]. " + ([sym] (exercise-fn sym 10)) + ([sym n] (exercise-fn sym n (get-spec sym))) + ([sym-or-f n fspec] + (let [f (if (symbol? sym-or-f) (resolve sym-or-f) sym-or-f)] + (if-let [arg-spec (c/and fspec (:args fspec))] + (for [args (gen/sample (gen arg-spec) n)] + [args (apply f args)]) + (throw (Exception. "No :args spec found, can't generate")))))) + +(defn inst-in-range? + "Return true if inst at or after start and before end" + [start end inst] + (c/and (inst? inst) + (let [t (inst-ms inst)] + (c/and (<= (inst-ms start) t) (< t (inst-ms end)))))) + +(defmacro inst-in + "Returns a spec that validates insts in the range from start +(inclusive) to end (exclusive)." + [start end] + `(let [st# (inst-ms ~start) + et# (inst-ms ~end) + mkdate# (fn [d#] (java.util.Date. ^{:tag ~'long} d#))] + (spec (and inst? #(inst-in-range? ~start ~end %)) + :gen (fn [] + (gen/fmap mkdate# + (gen/large-integer* {:min st# :max et#})))))) + +(defn int-in-range? + "Return true if start <= val, val < end and val is a fixed + precision integer." + [start end val] + (c/and (int? val) (<= start val) (< val end))) + +(defmacro int-in + "Returns a spec that validates fixed precision integers in the + range from start (inclusive) to end (exclusive)." + [start end] + `(spec (and int? #(int-in-range? ~start ~end %)) + :gen #(gen/large-integer* {:min ~start :max (dec ~end)}))) + +(defmacro double-in + "Specs a 64-bit floating point number. Options: + + :infinite? - whether +/- infinity allowed (default true) + :NaN? - whether NaN allowed (default true) + :min - minimum value (inclusive, default none) + :max - maximum value (inclusive, default none)" + [& {:keys [infinite? NaN? min max] + :or {infinite? true NaN? true} + :as m}] + `(spec (and c/double? + ~@(when-not infinite? '[#(not (Double/isInfinite %))]) + ~@(when-not NaN? '[#(not (Double/isNaN %))]) + ~@(when max `[#(<= % ~max)]) + ~@(when min `[#(<= ~min %)])) + :gen #(gen/double* ~m))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; assert ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce + ^{:dynamic true + :doc "If true, compiler will enable spec asserts, which are then +subject to runtime control via check-asserts? If false, compiler +will eliminate all spec assert overhead. See 'assert'. + +Initially set to boolean value of clojure.spec.compile-asserts +system property. Defaults to true."} + *compile-asserts* + (not= "false" (System/getProperty "clojure.spec.compile-asserts"))) + +(def ^:private check-spec-asserts (atom true)) + +(defn check-asserts? + "Returns the value set by check-asserts." + [] + @check-spec-asserts) + +(defn check-asserts + "Enable or disable spec asserts that have been compiled +with '*compile-asserts*' true. See 'assert'. + +Initially set to boolean value of clojure.spec.check-asserts +system property. Defaults to false." + [flag] + (reset! check-spec-asserts flag)) + +(defn assert* + "Do not call this directly, use 'assert'." + [spec x] + (if (valid? spec x) + x + (let [ed (c/merge (assoc (explain-data* spec [] [] [] x) + ::failure :assertion-failed))] + (throw (ex-info + (str "Spec assertion failed\n" (with-out-str (explain-out ed))) + ed))))) + +(defmacro assert + "spec-checking assert expression. Returns x if x is valid? according +to spec, else throws an ex-info with explain-data plus ::failure of +:assertion-failed. + +Can be disabled at either compile time or runtime: + +If *compile-asserts* is false at compile time, compiles to x. Defaults +to value of 'clojure.spec.compile-asserts' system property, or true if +not set. + +If (check-asserts?) is false at runtime, always returns x. Defaults to +value of 'clojure.spec.check-asserts' system property, or false if not +set. You can toggle check-asserts? with (check-asserts bool)." + [spec x] + (if *compile-asserts* + `(if @check-spec-asserts + (assert* ~spec ~x) + ~x) + x)) + + diff --git a/resources/src/babashka/clojure/spec/gen/alpha.clj b/resources/src/babashka/clojure/spec/gen/alpha.clj new file mode 100644 index 00000000..c35f9ff1 --- /dev/null +++ b/resources/src/babashka/clojure/spec/gen/alpha.clj @@ -0,0 +1,227 @@ +; 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 clojure.spec.gen.alpha + (:refer-clojure :exclude [boolean bytes cat hash-map list map not-empty set vector + char double int keyword symbol string uuid delay shuffle])) + +(alias 'c 'clojure.core) + +(defonce ^:private dynalock (Object.)) + +(defn- dynaload + [s] + (let [ns (namespace s)] + (assert ns) + (locking dynalock + (require (c/symbol ns))) + (let [v (resolve s)] + (if v + @v + (throw (RuntimeException. (str "Var " s " is not on the classpath"))))))) + +(def ^:private quick-check-ref + (c/delay (dynaload 'clojure.test.check/quick-check))) +(defn quick-check + [& args] + (apply @quick-check-ref args)) + +(def ^:private for-all*-ref + (c/delay (dynaload 'clojure.test.check.properties/for-all*))) +(defn for-all* + "Dynamically loaded clojure.test.check.properties/for-all*." + [& args] + (apply @for-all*-ref args)) + +(let [g? (c/delay (dynaload 'clojure.test.check.generators/generator?)) + g (c/delay (dynaload 'clojure.test.check.generators/generate)) + mkg (c/delay (dynaload 'clojure.test.check.generators/->Generator))] + (defn- generator? + [x] + (@g? x)) + (defn- generator + [gfn] + (@mkg gfn)) + (defn generate + "Generate a single value using generator." + [generator] + (@g generator))) + +(defn ^:skip-wiki delay-impl + [gfnd] + ;;N.B. depends on test.check impl details + (generator (fn [rnd size] + ((:gen @gfnd) rnd size)))) + +(defmacro delay + "given body that returns a generator, returns a + generator that delegates to that, but delays + creation until used." + [& body] + `(delay-impl (c/delay ~@body))) + +(defn gen-for-name + "Dynamically loads test.check generator named s." + [s] + (let [g (dynaload s)] + (if (generator? g) + g + (throw (RuntimeException. (str "Var " s " is not a generator")))))) + +(defmacro ^:skip-wiki lazy-combinator + "Implementation macro, do not call directly." + [s] + (let [fqn (c/symbol "clojure.test.check.generators" (name s)) + doc (str "Lazy loaded version of " fqn)] + `(let [g# (c/delay (dynaload '~fqn))] + (defn ~s + ~doc + [& ~'args] + (apply @g# ~'args))))) + +(defmacro ^:skip-wiki lazy-combinators + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(c/map + (fn [s] (c/list 'lazy-combinator s)) + syms))) + +(lazy-combinators hash-map list map not-empty set vector vector-distinct fmap elements + bind choose fmap one-of such-that tuple sample return + large-integer* double* frequency shuffle) + +(defmacro ^:skip-wiki lazy-prim + "Implementation macro, do not call directly." + [s] + (let [fqn (c/symbol "clojure.test.check.generators" (name s)) + doc (str "Fn returning " fqn)] + `(let [g# (c/delay (dynaload '~fqn))] + (defn ~s + ~doc + [& ~'args] + @g#)))) + +(defmacro ^:skip-wiki lazy-prims + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(c/map + (fn [s] (c/list 'lazy-prim s)) + syms))) + +(lazy-prims any any-printable boolean bytes char char-alpha char-alphanumeric char-ascii double + int keyword keyword-ns large-integer ratio simple-type simple-type-printable + string string-ascii string-alphanumeric symbol symbol-ns uuid) + +(defn cat + "Returns a generator of a sequence catenated from results of +gens, each of which should generate something sequential." + [& gens] + (fmap #(apply concat %) + (apply tuple gens))) + +(defn- qualified? [ident] (not (nil? (namespace ident)))) + +(def ^:private + gen-builtins + (c/delay + (let [simple (simple-type-printable)] + {any? (one-of [(return nil) (any-printable)]) + some? (such-that some? (any-printable)) + number? (one-of [(large-integer) (double)]) + integer? (large-integer) + int? (large-integer) + pos-int? (large-integer* {:min 1}) + neg-int? (large-integer* {:max -1}) + nat-int? (large-integer* {:min 0}) + float? (double) + double? (double) + boolean? (boolean) + string? (string-alphanumeric) + ident? (one-of [(keyword-ns) (symbol-ns)]) + simple-ident? (one-of [(keyword) (symbol)]) + qualified-ident? (such-that qualified? (one-of [(keyword-ns) (symbol-ns)])) + keyword? (keyword-ns) + simple-keyword? (keyword) + qualified-keyword? (such-that qualified? (keyword-ns)) + symbol? (symbol-ns) + simple-symbol? (symbol) + qualified-symbol? (such-that qualified? (symbol-ns)) + uuid? (uuid) + uri? (fmap #(java.net.URI/create (str "http://" % ".com")) (uuid)) + decimal? (fmap #(BigDecimal/valueOf %) + (double* {:infinite? false :NaN? false})) + inst? (fmap #(java.util.Date. %) + (large-integer)) + seqable? (one-of [(return nil) + (list simple) + (vector simple) + (map simple simple) + (set simple) + (string-alphanumeric)]) + indexed? (vector simple) + map? (map simple simple) + vector? (vector simple) + list? (list simple) + seq? (list simple) + char? (char) + set? (set simple) + nil? (return nil) + false? (return false) + true? (return true) + zero? (return 0) + rational? (one-of [(large-integer) (ratio)]) + coll? (one-of [(map simple simple) + (list simple) + (vector simple) + (set simple)]) + empty? (elements [nil '() [] {} #{}]) + associative? (one-of [(map simple simple) (vector simple)]) + sequential? (one-of [(list simple) (vector simple)]) + ratio? (such-that ratio? (ratio)) + bytes? (bytes)}))) + +(defn gen-for-pred + "Given a predicate, returns a built-in generator if one exists." + [pred] + (if (set? pred) + (elements pred) + (get @gen-builtins pred))) + +(comment + (require :reload 'clojure.spec.gen.alpha) + (in-ns 'clojure.spec.gen.alpha) + + ;; combinators, see call to lazy-combinators above for complete list + (generate (one-of [(gen-for-pred integer?) (gen-for-pred string?)])) + (generate (such-that #(< 10000 %) (gen-for-pred integer?))) + (let [reqs {:a (gen-for-pred number?) + :b (gen-for-pred ratio?)} + opts {:c (gen-for-pred string?)}] + (generate (bind (choose 0 (count opts)) + #(let [args (concat (seq reqs) (c/shuffle (seq opts)))] + (->> args + (take (+ % (count reqs))) + (mapcat identity) + (apply hash-map)))))) + (generate (cat (list (gen-for-pred string?)) + (list (gen-for-pred ratio?)))) + + ;; load your own generator + (gen-for-name 'clojure.test.check.generators/int) + + ;; failure modes + (gen-for-name 'unqualified) + (gen-for-name 'clojure.core/+) + (gen-for-name 'clojure.core/name-does-not-exist) + (gen-for-name 'ns.does.not.exist/f) + + ) + + diff --git a/resources/src/babashka/clojure/spec/test/alpha.clj b/resources/src/babashka/clojure/spec/test/alpha.clj new file mode 100644 index 00000000..4f1524ce --- /dev/null +++ b/resources/src/babashka/clojure/spec/test/alpha.clj @@ -0,0 +1,579 @@ +; 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 clojure.spec.test.alpha + (:refer-clojure :exclude [test]) + (:require + [clojure.pprint :as pp] + [clojure.spec.alpha :as s] + [clojure.spec.gen.alpha :as gen] + [clojure.string :as str])) + +(in-ns 'clojure.spec.test.check) +(in-ns 'clojure.spec.test.alpha) +(alias 'stc 'clojure.spec.test.check) + +(defn- throwable? + [x] + (instance? Throwable x)) + +(defn ->sym + [x] + (@#'s/->sym x)) + +(defn- ->var + [s-or-v] + (if (var? s-or-v) + s-or-v + (let [v (and (symbol? s-or-v) (resolve s-or-v))] + (if (var? v) + v + (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) + +(defn- collectionize + [x] + (if (symbol? x) + (list x) + x)) + +(defn enumerate-namespace + "Given a symbol naming an ns, or a collection of such symbols, +returns the set of all symbols naming vars in those nses." + [ns-sym-or-syms] + (into + #{} + (mapcat (fn [ns-sym] + (map + (fn [name-sym] + (symbol (name ns-sym) (name name-sym))) + (keys (ns-interns ns-sym))))) + (collectionize ns-sym-or-syms))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:private ^:dynamic *instrument-enabled* + "if false, instrumented fns call straight through" + true) + +(defn- fn-spec? + "Fn-spec must include at least :args or :ret specs." + [m] + (or (:args m) (:ret m))) + +(defmacro with-instrument-disabled + "Disables instrument's checking of calls, within a scope." + [& body] + `(binding [*instrument-enabled* nil] + ~@body)) + +(defn- thunk-frame? [s] + (str/includes? s "--KVS--EMULATION--THUNK--")) + +(defn- interpret-stack-trace-element + "Given the vector-of-syms form of a stacktrace element produced +by e.g. Throwable->map, returns a map form that adds some keys +guessing the original Clojure names. Returns a map with + + :class class name symbol from stack trace + :method method symbol from stack trace + :file filename from stack trace + :line line number from stack trace + :var-scope optional Clojure var symbol scoping fn def + :local-fn optional local Clojure symbol scoping fn def + +For non-Clojure fns, :scope and :local-fn will be absent." + [[cls method file line]] + (let [clojure? (contains? '#{invoke invokeStatic} method) + demunge #(s/demunge %) + degensym #(str/replace % #"--.*" "") + [ns-sym name-sym local] (when clojure? + (->> (str/split (str cls) #"\$" 3) + (map demunge)))] + (merge {:file file + :line line + :method method + :class cls} + (when (and ns-sym name-sym) + {:var-scope (symbol ns-sym name-sym)}) + (when local + {:local-fn (symbol (degensym local)) + :thunk? (thunk-frame? local)})))) + +(defn- stacktrace-relevant-to-instrument + "Takes a coll of stack trace elements (as returned by +StackTraceElement->vec) and returns a coll of maps as per +interpret-stack-trace-element that are relevant to a +failure in instrument." + [elems] + (let [plumbing? (fn [{:keys [var-scope thunk?]}] + (or thunk? + (contains? '#{clojure.spec.test.alpha/spec-checking-fn + clojure.core/apply} + var-scope)))] + (sequence (comp (map StackTraceElement->vec) + (map interpret-stack-trace-element) + (filter :var-scope) + (drop-while plumbing?)) + elems))) + +(defn- spec-checking-fn + "Takes a function name, a function f, and an fspec and returns a thunk that + first conforms the arguments given then calls f with those arguments if + the conform succeeds. Otherwise, an exception is thrown containing information + about the conform failure." + [fn-name f fn-spec] + (let [fn-spec (@#'s/maybe-spec fn-spec) + conform! (fn [fn-name role spec data args] + (let [conformed (s/conform spec data)] + (if (= ::s/invalid conformed) + (let [caller (->> (.getStackTrace (Thread/currentThread)) + stacktrace-relevant-to-instrument + first) + ed (merge (assoc (s/explain-data* spec [] [] [] data) + ::s/fn fn-name + ::s/args args + ::s/failure :instrument) + (when caller + {::caller (dissoc caller :class :method)}))] + (throw (ex-info + (str "Call to " fn-name " did not conform to spec.") + ed))) + conformed)))] + (fn + [& args] + (if *instrument-enabled* + (with-instrument-disabled + (when (:args fn-spec) (conform! fn-name :args (:args fn-spec) args args)) + (binding [*instrument-enabled* true] + (.applyTo ^clojure.lang.IFn f args))) + (.applyTo ^clojure.lang.IFn f args))))) + +(defn- no-fspec + [v spec] + (ex-info (str "Fn at " v " is not spec'ed.") + {:var v :spec spec ::s/failure :no-fspec})) + +(defonce ^:private instrumented-vars (atom {})) + +(defn- find-varargs-decl + "Takes an arglist and returns the restargs binding form if found, else nil." + [arglist] + (let [[_ decl :as restargs] (->> arglist + (split-with (complement #{'&})) + second)] + (and (= 2 (count restargs)) + decl))) + +(defn- has-kwargs? [arglists] + (->> arglists (some find-varargs-decl) map?)) + +(defn- kwargs->kvs + "Takes the restargs of a kwargs function call and checks for a trailing element. + If found, that element is flattened into a sequence of key->value pairs and + concatenated onto the preceding arguments." + [args] + (if (even? (count args)) + args + (concat (butlast args) + (reduce-kv (fn [acc k v] (->> acc (cons v) (cons k))) + () + (last args))))) + +(defn- gen-fixed-args-syms + "Takes an arglist and generates a vector of names corresponding to the fixed + args found." + [arglist] + (->> arglist (take-while (complement #{'&})) (map (fn [_] (gensym))) vec)) + +(defn- build-kwargs-body + "Takes a function name fn-name and arglist and returns code for a function body that + handles kwargs by calling fn-name with any fixed followed by its restargs transformed + from kwargs to kvs." + [fn-name arglist] + (let [alias (gensym "kwargs") + head-args (gen-fixed-args-syms arglist)] + (list (conj head-args '& alias) + `(apply ~fn-name ~@head-args (@#'kwargs->kvs ~alias))))) + +(defn- build-varargs-body + "Takes a function name fn-name and arglist and returns code for a function body that + handles varargs by calling fn-name with any fixed args followed by its rest args." + [fn-name arglist] + (let [head-args (gen-fixed-args-syms arglist) + alias (gensym "restargs")] + (list (conj head-args '& alias) + `(apply ~fn-name ~@head-args ~alias)))) + +(defn- build-fixed-args-body + "Takes a function name fn-name and arglist and returns code for a function body that + handles fixed args by calling fn-name with its fixed args." + [fn-name arglist] + (let [arglist (gen-fixed-args-syms arglist)] + (list arglist + `(~fn-name ~@arglist)))) + +(defn- build-flattener-code + "Takes argslists and generates code for a HOF that given a function, returns a forwarding thunk + of analogous arglists that ensures that kwargs are passed as kvs to the original function." + [arglists] + (let [closed-over-name (gensym "inner")] + `(fn [~closed-over-name] + (fn ~'--KVS--EMULATION--THUNK-- + ~@(map (fn [arglist] + (let [varargs-decl (find-varargs-decl arglist)] + (cond (map? varargs-decl) (build-kwargs-body closed-over-name arglist) + varargs-decl (build-varargs-body closed-over-name arglist) + :default (build-fixed-args-body closed-over-name arglist)))) + (or arglists + '([& args]))))))) + +(comment + ;; Given a function with the arglists (([a]) ([a b]) ([a b & kvs])) + ;; the flattener generated is below (with some gensym name cleanup for readability) + (fn [inner] + (fn + ([G__a] (inner G__a)) + ([G__a G__b] (inner G__a G__b)) + ([G__a G__b & G__kvs] + (apply inner G__a G__b (if (even? (count G__kvs)) + G__kvs + (reduce-kv (fn [acc k v] + (->> acc (cons v) (cons k))) + (butlast G__kvs) + (last G__kvs))))))) +) + +(defn- maybe-wrap-kvs-emulation + "Takes an argslist and function f and returns f except when arglists + contains a kwargs binding, else wraps f with a forwarding thunk that + flattens a trailing map into kvs if present in the kwargs call." + [f arglists] + (if (has-kwargs? arglists) + (let [flattener-code (build-flattener-code arglists) + kvs-emu (eval flattener-code)] + (kvs-emu f)) + f)) + +(defn- instrument-choose-fn + "Helper for instrument." + [f spec sym {over :gen :keys [stub replace]}] + (if (some #{sym} stub) + (-> spec (s/gen over) gen/generate) + (get replace sym f))) + +(defn- instrument-choose-spec + "Helper for instrument" + [spec sym {overrides :spec}] + (get overrides sym spec)) + +(defn- instrument-1 + [s opts] + (when-let [v (resolve s)] + (when-not (-> v meta :macro) + (let [spec (s/get-spec v) + {:keys [raw wrapped]} (get @instrumented-vars v) + current @v + to-wrap (if (= wrapped current) raw current) + ospec (or (instrument-choose-spec spec s opts) + (throw (no-fspec v spec))) + ofn (instrument-choose-fn to-wrap ospec s opts) + checked (spec-checking-fn (->sym v) ofn ospec) + arglists (->> v meta :arglists (sort-by count) seq) + wrapped (maybe-wrap-kvs-emulation checked arglists)] + (alter-var-root v (constantly wrapped)) + (swap! instrumented-vars assoc v {:raw to-wrap :wrapped wrapped}) + (->sym v))))) + +(defn- unstrument-1 + [s] + (when-let [v (resolve s)] + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (swap! instrumented-vars dissoc v) + (let [current @v] + (when (= wrapped current) + (alter-var-root v (constantly raw)) + (->sym v)))))) + +(defn- opt-syms + "Returns set of symbols referenced by 'instrument' opts map" + [opts] + (reduce into #{} [(:stub opts) (keys (:replace opts)) (keys (:spec opts))])) + +(defn- fn-spec-name? + [s] + (and (symbol? s) + (not (some-> (resolve s) meta :macro)))) + +(defn instrumentable-syms + "Given an opts map as per instrument, returns the set of syms +that can be instrumented." + ([] (instrumentable-syms nil)) + ([opts] + (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys") + (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) + (keys (:spec opts)) + (:stub opts) + (keys (:replace opts))]))) + +(defn instrument + "Instruments the vars named by sym-or-syms, a symbol or collection +of symbols, or all instrumentable vars if sym-or-syms is not +specified. + +If a var has an :args fn-spec, sets the var's root binding to a +fn that checks arg conformance (throwing an exception on failure) +before delegating to the original fn. + +The opts map can be used to override registered specs, and/or to +replace fn implementations entirely. Opts for symbols not included +in sym-or-syms are ignored. This facilitates sharing a common +options map across many different calls to instrument. + +The opts map may have the following keys: + + :spec a map from var-name symbols to override specs + :stub a set of var-name symbols to be replaced by stubs + :gen a map from spec names to generator overrides + :replace a map from var-name symbols to replacement fns + +:spec overrides registered fn-specs with specs your provide. Use +:spec overrides to provide specs for libraries that do not have +them, or to constrain your own use of a fn to a subset of its +spec'ed contract. + +:stub replaces a fn with a stub that checks :args, then uses the +:ret spec to generate a return value. + +:gen overrides are used only for :stub generation. + +:replace replaces a fn with a fn that checks args conformance, then +invokes the fn you provide, enabling arbitrary stubbing and mocking. + +:spec can be used in combination with :stub or :replace. + +Returns a collection of syms naming the vars instrumented." + ([] (instrument (instrumentable-syms))) + ([sym-or-syms] (instrument sym-or-syms nil)) + ([sym-or-syms opts] + (locking instrumented-vars + (into + [] + (comp (filter (instrumentable-syms opts)) + (distinct) + (map #(instrument-1 % opts)) + (remove nil?)) + (collectionize sym-or-syms))))) + +(defn unstrument + "Undoes instrument on the vars named by sym-or-syms, specified +as in instrument. With no args, unstruments all instrumented vars. +Returns a collection of syms naming the vars unstrumented." + ([] (unstrument (map ->sym (keys @instrumented-vars)))) + ([sym-or-syms] + (locking instrumented-vars + (into + [] + (comp (filter symbol?) + (map unstrument-1) + (remove nil?)) + (collectionize sym-or-syms))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- explain-check + [args spec v role] + (ex-info + "Specification-based check failed" + (when-not (s/valid? spec v nil) + (assoc (s/explain-data* spec [role] [] [] v) + ::args args + ::val v + ::s/failure :check-failed)))) + +(defn- check-call + "Returns true if call passes specs, otherwise *returns* an exception +with explain-data + ::s/failure." + [f specs args] + (let [cargs (when (:args specs) (s/conform (:args specs) args))] + (if (= cargs ::s/invalid) + (explain-check args (:args specs) args :args) + (let [ret (apply f args) + cret (when (:ret specs) (s/conform (:ret specs) ret))] + (if (= cret ::s/invalid) + (explain-check args (:ret specs) ret :ret) + (if (and (:args specs) (:ret specs) (:fn specs)) + (if (s/valid? (:fn specs) {:args cargs :ret cret}) + true + (explain-check args (:fn specs) {:args cargs :ret cret} :fn)) + true)))))) + +(defn- quick-check + [f specs {gen :gen opts ::stc/opts}] + (let [{:keys [num-tests] :or {num-tests 1000}} opts + g (try (s/gen (:args specs) gen) (catch Throwable t t))] + (if (throwable? g) + {:result g} + (let [prop (gen/for-all* [g] #(check-call f specs %))] + (apply gen/quick-check num-tests prop (mapcat identity opts)))))) + +(defn- make-check-result + "Builds spec result map." + [check-sym spec test-check-ret] + (merge {:spec spec + ::stc/ret test-check-ret} + (when check-sym + {:sym check-sym}) + (when-let [result (-> test-check-ret :result)] + (when-not (true? result) {:failure result})) + (when-let [shrunk (-> test-check-ret :shrunk)] + {:failure (:result shrunk)}))) + +(defn- check-1 + [{:keys [s f v spec]} opts] + (let [re-inst? (and v (seq (unstrument s)) true) + f (or f (when v @v)) + specd (s/spec spec)] + (try + (cond + (or (nil? f) (some-> v meta :macro)) + {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) + :sym s :spec spec} + + (:args specd) + (let [tcret (quick-check f specd opts)] + (make-check-result s spec tcret)) + + :default + {:failure (ex-info "No :args spec" {::s/failure :no-args-spec}) + :sym s :spec spec}) + (finally + (when re-inst? (instrument s)))))) + +(defn- sym->check-map + [s] + (let [v (resolve s)] + {:s s + :v v + :spec (when v (s/get-spec v))})) + +(defn- validate-check-opts + [opts] + (assert (every? ident? (keys (:gen opts))) "check :gen expects ident keys")) + +(defn check-fn + "Runs generative tests for fn f using spec and opts. See +'check' for options and return." + ([f spec] (check-fn f spec nil)) + ([f spec opts] + (validate-check-opts opts) + (check-1 {:f f :spec spec} opts))) + +(defn checkable-syms + "Given an opts map as per check, returns the set of syms that +can be checked." + ([] (checkable-syms nil)) + ([opts] + (validate-check-opts opts) + (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) + (keys (:spec opts))]))) + +(defn check + "Run generative tests for spec conformance on vars named by +sym-or-syms, a symbol or collection of symbols. If sym-or-syms +is not specified, check all checkable vars. + +The opts map includes the following optional keys, where stc +aliases clojure.spec.test.check: + +::stc/opts opts to flow through test.check/quick-check +:gen map from spec names to generator overrides + +The ::stc/opts include :num-tests in addition to the keys +documented by test.check. Generator overrides are passed to +spec/gen when generating function args. + +Returns a lazy sequence of check result maps with the following +keys + +:spec the spec tested +:sym optional symbol naming the var tested +:failure optional test failure +::stc/ret optional value returned by test.check/quick-check + +The value for :failure can be any exception. Exceptions thrown by +spec itself will have an ::s/failure value in ex-data: + +:check-failed at least one checked return did not conform +:no-args-spec no :args spec provided +:no-fn no fn provided +:no-fspec no fspec provided +:no-gen unable to generate :args +:instrument invalid args detected by instrument +" + ([] (check (checkable-syms))) + ([sym-or-syms] (check sym-or-syms nil)) + ([sym-or-syms opts] + (->> (collectionize sym-or-syms) + (filter (checkable-syms opts)) + (pmap + #(check-1 (sym->check-map %) opts))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- failure-type + [x] + (::s/failure (ex-data x))) + +(defn- unwrap-failure + [x] + (if (failure-type x) + (ex-data x) + x)) + +(defn- result-type + "Returns the type of the check result. This can be any of the +::s/failure keywords documented in 'check', or: + + :check-passed all checked fn returns conformed + :check-threw checked fn threw an exception" + [ret] + (let [failure (:failure ret)] + (cond + (nil? failure) :check-passed + (failure-type failure) (failure-type failure) + :default :check-threw))) + +(defn abbrev-result + "Given a check result, returns an abbreviated version +suitable for summary use." + [x] + (if (:failure x) + (-> (dissoc x ::stc/ret) + (update :spec s/describe) + (update :failure unwrap-failure)) + (dissoc x :spec ::stc/ret))) + +(defn summarize-results + "Given a collection of check-results, e.g. from 'check', pretty +prints the summary-result (default abbrev-result) of each. + +Returns a map with :total, the total number of results, plus a +key with a count for each different :type of result." + ([check-results] (summarize-results check-results abbrev-result)) + ([check-results summary-result] + (reduce + (fn [summary result] + (pp/pprint (summary-result result)) + (-> summary + (update :total inc) + (update (result-type result) (fnil inc 0)))) + {:total 0} + check-results))) + + + diff --git a/sci b/sci index 34a9f8a9..92a07126 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 34a9f8a99e497b16553505d63c6daf04ebbaf068 +Subproject commit 92a071269f1a4e3f4fda262b33b47ec827be3d4e diff --git a/script/add-libtest.clj b/script/add_libtest.clj similarity index 99% rename from script/add-libtest.clj rename to script/add_libtest.clj index 9eeea5f2..dd968c1e 100755 --- a/script/add-libtest.clj +++ b/script/add_libtest.clj @@ -11,11 +11,11 @@ (:require [babashka.deps :as deps] [babashka.fs :as fs] [babashka.tasks :refer [shell]] - [org.httpkit.client :as http] - [clojure.string :as str] + [clojure.edn :as edn] [clojure.java.io :as io] + [clojure.string :as str] [clojure.tools.cli :as cli] - [clojure.edn :as edn])) + [org.httpkit.client :as http])) (deps/add-deps '{:deps {org.clojure/tools.gitlibs {:mvn/version "2.4.172"} borkdude/rewrite-edn {:mvn/version "0.1.0"} diff --git a/script/babashka/release_artifact.clj b/script/babashka/release_artifact.clj index b88a1e20..92da4564 100644 --- a/script/babashka/release_artifact.clj +++ b/script/babashka/release_artifact.clj @@ -7,24 +7,32 @@ (or (System/getenv "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH") (System/getenv "APPVEYOR_REPO_BRANCH") (System/getenv "CIRCLE_BRANCH") + (System/getenv "GITHUB_REF_NAME") + (System/getenv "CIRRUS_BRANCH") (-> (sh "git" "rev-parse" "--abbrev-ref" "HEAD") :out str/trim))) (defn release [& args] (let [ght (System/getenv "GITHUB_TOKEN") + _ (println "Github token found") file (first args) + _ (println "File" file) branch (current-branch) + _ (println "On branch:" branch) current-version (-> (slurp "resources/BABASHKA_VERSION") str/trim)] (if (and ght (contains? #{"master" "main"} branch)) (do (assert file "File name must be provided") + (println "On main branch. Publishing asset.") (ghr/overwrite-asset {:org "babashka" :repo "babashka" :file file :tag (str "v" current-version) - :draft true}) + :draft true + :overwrite (str/ends-with? current-version "SNAPSHOT") + :sha256 true}) (ghr/overwrite-asset {:org "babashka" :repo "babashka-dev-builds" :file file @@ -32,6 +40,8 @@ ;; do not set, because we are posting to another repo :target-commitish false :draft false - :prerelease true})) + :prerelease true + :overwrite (str/ends-with? current-version "SNAPSHOT") + :sha256 true})) (println "Skipping release artifact (no GITHUB_TOKEN or not on main branch)")) nil)) diff --git a/script/built_in.clj b/script/built_in.clj new file mode 100755 index 00000000..743b2b02 --- /dev/null +++ b/script/built_in.clj @@ -0,0 +1,15 @@ +#!/usr/bin/env bb + +(ns built-in + (:require [babashka.fs :as fs] + [babashka.process :refer [shell]])) + +;; copy clojure spec as built-in +(fs/with-temp-dir [tmp-dir {}] + (let [tmp-dir (fs/file tmp-dir)] + (shell {:dir tmp-dir} "git clone https://github.com/babashka/spec.alpha") + (let [spec-dir (fs/file tmp-dir "spec.alpha")] + (shell {:dir spec-dir} "git reset 1d9df099be4fbfd30b9b903642ad376373c16298 --hard") + (fs/copy-tree (fs/file spec-dir "src" "main" "clojure") (fs/file "resources" "src" "babashka"))))) + + diff --git a/script/bump_graal_version.clj b/script/bump_graal_version.clj index 29fa58f6..191c8e01 100755 --- a/script/bump_graal_version.clj +++ b/script/bump_graal_version.clj @@ -43,7 +43,10 @@ ".github/workflows/build.yml" ".circleci/config.yml" "appveyor.yml" - "project.clj"]) + "project.clj" + "script/bump_graal_version.clj" + ".circleci/script/short_ci.clj" + ".cirrus.yml"]) ;; We might have to keep changing these from ;; time to time whenever the version is bumped @@ -51,10 +54,8 @@ ;; OR ;; ;; We could have them as environment variables -(def current-graal-version "21.3.0") +(def current-graal-version "22.3.0") (def current-java-version "java11") -(def valid-graal-bumps ["19.3.2" "20.1.0" "20.2.0" "20.3.0" "21.0.0" "21.1.0" "21.2.0" "21.3.0" "22.0.0.2"]) -(def valid-java-bumps ["java8" "java11"]) (def cl-options [["-g" "--graal VERSION" "graal version"] @@ -65,8 +66,8 @@ (:options (cli/parse-opts *command-line-args* cl-options))) (defn is-valid-bump? - [version valid-bumps] - (some #(= % version) valid-bumps)) + [_version _valid-bumps] + true) (defn replace-current [file current new] @@ -93,13 +94,13 @@ (let [new-graal-version (:graal args) new-java-version (:java args)] (when (not (nil? new-graal-version)) - (if (is-valid-bump? new-graal-version valid-graal-bumps) + (if (is-valid-bump? new-graal-version nil) (do (println "Performing Graal bump...") (bump-current current-graal-version new-graal-version)) (show-error new-graal-version))) (when (not (nil? new-java-version)) - (if (is-valid-bump? new-java-version valid-java-bumps) + (if (is-valid-bump? new-java-version nil) (do (println "Performing Java bump...") (bump-current current-java-version new-java-version)) diff --git a/script/compile b/script/compile index 22ea8eec..17b7dc76 100755 --- a/script/compile +++ b/script/compile @@ -54,6 +54,7 @@ args=("-jar" "$BABASHKA_JAR" "--verbose" "--no-fallback" "--native-image-info" + # --trace-class-initialization=jdk.internal.net.http.common.DebugLogger,jdk.internal.net.http.websocket.WebSocketImpl,jdk.internal.net.http.common.Utils "$BABASHKA_XMX") BABASHKA_STATIC=${BABASHKA_STATIC:-} @@ -77,6 +78,12 @@ if [ "$BABASHKA_FEATURE_HSQLDB" = "true" ]; then args+=("-H:IncludeResources=org/hsqldb/.*\.properties" "-H:IncludeResources=org/hsqldb/.*\.sql") fi +BABASHKA_FEATURE_POSTGRESQL=${BABASHKA_FEATURE_POSTGRESQL:-} + +if [ "$BABASHKA_FEATURE_POSTGRESQL" = "true" ]; then + args+=("--initialize-at-build-time=org.postgresql.PGProperty") +fi + BABASHKA_FEATURE_SELMER=${BABASHKA_FEATURE_SELMER:-} if [ "$BABASHKA_FEATURE_SELMER" = "true" ]; then @@ -91,7 +98,7 @@ then export BABASHKA_FEATURE_XML="${BABASHKA_FEATURE_XML:-false}" export BABASHKA_FEATURE_YAML="${BABASHKA_FEATURE_YAML:-false}" export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}" - export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}" + export BABASHKA_FEATURE_TRANSIT="${BABASHKA_FEATURE_TRANSIT:-false}" export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}" export BABASHKA_FEATURE_JAVA_NET_HTTP="${BABASHKA_FEATURE_JAVA_NET_HTTP:-false}" export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}" diff --git a/script/install-clojure b/script/install-clojure index 40b096a7..d0d5e24b 100755 --- a/script/install-clojure +++ b/script/install-clojure @@ -2,7 +2,7 @@ set -euo pipefail -CLOJURE_TOOLS_VERSION="1.10.3.1040" +CLOJURE_TOOLS_VERSION="1.11.1.1200" install_dir="${1:-/usr/local}" mkdir -p "$install_dir" diff --git a/script/lib_tests/run_all_libtests b/script/lib_tests/run_all_libtests index 99f55079..a5409d57 100755 --- a/script/lib_tests/run_all_libtests +++ b/script/lib_tests/run_all_libtests @@ -9,7 +9,7 @@ else fi export BABASHKA_CLASSPATH -BABASHKA_CLASSPATH=$(clojure -A:lib-tests -Spath) +BABASHKA_CLASSPATH=$(clojure -Spath -A:lib-tests) $BB_CMD -cp "$BABASHKA_CLASSPATH" \ -f "test-resources/lib_tests/babashka/run_all_libtests.clj" "$@" diff --git a/script/setup-musl b/script/setup-musl index 3a042d3b..dcb552e5 100755 --- a/script/setup-musl +++ b/script/setup-musl @@ -19,12 +19,13 @@ fi apt-get update -y && apt-get install musl-tools -y -ZLIB_VERSION="1.2.11" +ZLIB_VERSION="1.2.13" +ZLIB_SHA256="b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30" -curl -O -sL "https://zlib.net/zlib-${ZLIB_VERSION}.tar.gz" +# stable archive path +curl -O -sL --fail --show-error "https://zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz" -echo "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 zlib-${ZLIB_VERSION}.tar.gz" | - sha256sum --check +echo "${ZLIB_SHA256} zlib-${ZLIB_VERSION}.tar.gz" | sha256sum --check tar xf "zlib-${ZLIB_VERSION}.tar.gz" arch=${BABASHKA_ARCH:-"x86_64"} diff --git a/script/uberjar b/script/uberjar index 880aba4e..42025c74 100755 --- a/script/uberjar +++ b/script/uberjar @@ -16,7 +16,7 @@ then export BABASHKA_FEATURE_XML="${BABASHKA_FEATURE_XML:-false}" export BABASHKA_FEATURE_YAML="${BABASHKA_FEATURE_YAML:-false}" export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}" - export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}" + export BABASHKA_FEATURE_TRANSIT="${BABASHKA_FEATURE_TRANSIT:-false}" export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}" export BABASHKA_FEATURE_JAVA_NET_HTTP="${BABASHKA_FEATURE_JAVA_NET_HTTP:-false}" export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}" @@ -168,6 +168,13 @@ else BABASHKA_LEIN_PROFILES+=",-feature/priority-map" fi +if [ "$BABASHKA_FEATURE_RRB_VECTOR" = "true" ] +then + BABASHKA_LEIN_PROFILES+=",+feature/rrb-vector" +else + BABASHKA_LEIN_PROFILES+=",-feature/rrb-vector" +fi + cp deps.edn resources/META-INF/babashka/deps.edn if [ -z "$BABASHKA_JAR" ]; then diff --git a/script/uberjar.bat b/script/uberjar.bat index 2ae3beb6..65cdd182 100755 --- a/script/uberjar.bat +++ b/script/uberjar.bat @@ -130,6 +130,12 @@ set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/priority-map set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/priority-map ) +if not "%BABASHKA_FEATURE_RRB_VECTOR%"=="false" ( +set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/rrb-vector +) else ( +set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/rrb-vector +) + call lein with-profiles %BABASHKA_LEIN_PROFILES% bb "(+ 1 2 3)" call lein with-profiles %BABASHKA_LEIN_PROFILES%,+reflection,-uberjar do run diff --git a/src/aaaa_this_has_to_be_first/because_patches.clj b/src/aaaa_this_has_to_be_first/because_patches.clj index f13b10a4..d0b5cda1 100644 --- a/src/aaaa_this_has_to_be_first/because_patches.clj +++ b/src/aaaa_this_has_to_be_first/because_patches.clj @@ -2,3 +2,29 @@ ;; we need pprint loaded first, it patches pprint to not bloat the GraalVM binary (:require [babashka.impl.patches.datafy] [babashka.impl.pprint])) + +;; Enable this for scanning requiring-resolve usage: +;; --- +;; (def old-requiring-resolve requiring-resolve) + +;; (defmacro static-requiring-resolve [sym] +;; (prn :sym sym) +;; `(old-requiring-resolve ~sym)) + +;; (alter-var-root #'requiring-resolve (constantly @#'static-requiring-resolve)) +;; (doto #'requiring-resolve (.setMacro)) +;; --- + +;; ((requiring-resolve 'clojure.pprint/pprint) (range 20)) + +;; Enable this for detecting literal usages of require +;; --- +;; (def old-require require) + +;; (defmacro static-require [& syms] +;; (when (meta &form) +;; (prn :require &form )) +;; `(old-require ~@syms)) +;; (alter-var-root #'require (constantly @#'static-require)) +;; (doto #'require (.setMacro)) +;; --- diff --git a/src/babashka/deps.clj b/src/babashka/deps.clj index 16e56668..ea8daa59 100644 --- a/src/babashka/deps.clj +++ b/src/babashka/deps.clj @@ -1,5 +1,6 @@ (ns babashka.deps - (:require [babashka.process :as p] + (:require [babashka.impl.process :as pp] + [babashka.process :as p] [borkdude.deps :as deps] [sci.core :as sci])) @@ -26,25 +27,28 @@ (-> @(clojure) :exit) starts a clojure REPL, waits for it to finish and returns the exit code from the process." - ([] (clojure [])) - ([args] (clojure args nil)) - ([args opts] - (let [opts (merge {:in :inherit - :out :inherit - :err :inherit - :shutdown p/destroy-tree} - opts)] - (binding [*in* @sci/in - *out* @sci/out - *err* @sci/err - deps/*dir* (:dir opts) - deps/*env* (:env opts) - deps/*extra-env* (:extra-env opts) - deps/*process-fn* (fn - ([cmd] (p/process cmd opts)) - ([cmd _] (p/process cmd opts))) - deps/*exit-fn* (fn - ([_]) - ([_exit-code msg] - (throw (Exception. msg))))] - (apply deps/-main (map str args)))))) + [& args] + (let [{:keys [cmd opts prev]} (p/parse-args args) + opts (merge {:in :inherit + :out :inherit + :err :inherit + :shutdown p/destroy-tree} + opts)] + (binding [*in* @sci/in + *out* @sci/out + *err* @sci/err + deps/*dir* (:dir opts) + deps/*env* (:env opts) + deps/*extra-env* (:extra-env opts) + deps/*process-fn* (fn + ([cmd] (pp/process* {:cmd cmd + :prev prev + :opts opts})) + ([cmd _] (pp/process* {:cmd cmd + :prev prev + :opts opts}))) + deps/*exit-fn* (fn + ([_]) + ([_exit-code msg] + (throw (Exception. msg))))] + (apply deps/-main cmd)))) diff --git a/src/babashka/impl/bencode.clj b/src/babashka/impl/bencode.clj index 70ed7ea1..b608b7d9 100644 --- a/src/babashka/impl/bencode.clj +++ b/src/babashka/impl/bencode.clj @@ -1,10 +1,10 @@ (ns babashka.impl.bencode {:no-doc true} - (:require [bencode.core :as bencode] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + (:require + [bencode.core :as bencode] + [sci.core :as sci :refer [copy-var]])) -(def tns (vars/->SciNamespace 'bencode.core nil)) +(def tns (sci/create-ns 'bencode.core nil)) (def bencode-namespace {'read-bencode (copy-var bencode/read-bencode tns) diff --git a/src/babashka/impl/cheshire.clj b/src/babashka/impl/cheshire.clj index 6ce73d43..4e89afcb 100644 --- a/src/babashka/impl/cheshire.clj +++ b/src/babashka/impl/cheshire.clj @@ -1,10 +1,9 @@ (ns babashka.impl.cheshire {:no-doc true} (:require [cheshire.core :as json] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + [sci.core :as sci :refer [copy-var]])) -(def tns (vars/->SciNamespace 'cheshire.core nil)) +(def tns (sci/create-ns 'cheshire.core nil)) (def cheshire-core-namespace {'encode (copy-var json/encode tns) diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index bfda0e17..b7e46ad9 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -4,6 +4,8 @@ [babashka.impl.features :as features] [babashka.impl.proxy :as proxy] [cheshire.core :as json] + [clojure.core.async] + [sci.core :as sci] [sci.impl.types :as t])) (def base-custom-map @@ -78,11 +80,25 @@ ;; this fixes clojure.lang.Reflector for Java 11 java.lang.reflect.AccessibleObject {:methods [{:name "canAccess"}]} - java.lang.reflect.Method + java.lang.Package {:methods [{:name "getName"}]} + java.lang.reflect.Member + {:methods [{:name "getModifiers"}]} + java.lang.reflect.Method + {:methods [{:name "getName"} + {:name "getModifiers"} + {:name "getParameterTypes"} + {:name "getReturnType"}]} + java.lang.reflect.Modifier + {:methods [{:name "isStatic"}]} + java.lang.reflect.Field + {:methods [{:name "getName"} + {:name "getModifiers"}]} java.lang.reflect.Array {:methods [{:name "newInstance"} {:name "set"}]} + java.lang.Runnable + {:methods [{:name "run"}]} java.net.Inet4Address {:methods [{:name "getHostAddress"}]} java.net.Inet6Address @@ -97,9 +113,34 @@ {:name "aset"} {:name "aclone"}]} clojure.lang.Compiler - {:fields [{:name "specials"}]} + {:fields [{:name "specials"} + {:name "CHAR_MAP"}]} clojure.lang.PersistentHashMap - {:fields [{:name "EMPTY"}]}}) + {:fields [{:name "EMPTY"}]} + clojure.lang.APersistentVector + {:methods [{:name "indexOf"}]} + clojure.lang.LazySeq + {:allPublicConstructors true, + :methods [{:name "indexOf"}]} + clojure.lang.ILookup + {:methods [{:name "valAt"}]} + clojure.lang.IPersistentMap + {:methods [{:name "without"}]} + clojure.lang.IPersistentSet + {:methods [{:name "disjoin"}]} + clojure.lang.Indexed + {:methods [{:name "nth"}]} + clojure.lang.Ratio + {:fields [{:name "numerator"} + {:name "denominator"}]} + clojure.lang.Agent + {:fields [{:name "pooledExecutor"} + {:name "soloExecutor"}]} + java.util.Iterator + {:methods [{:name "hasNext"} + {:name "next"}]} + java.util.TimeZone + {:methods [{:name "getTimeZone"}]}}) (def custom-map (cond-> @@ -110,7 +151,56 @@ :parameterTypes ["org.hsqldb.Database"]}]} `java.util.ResourceBundle {:methods [{:name "getBundle" - :parameterTypes ["java.lang.String","java.util.Locale","java.lang.ClassLoader"]}]}))) + :parameterTypes ["java.lang.String","java.util.Locale", + "java.lang.ClassLoader"]}]}))) + +(def java-net-http-classes + "These classes must be initialized at run time since GraalVM 22.1" + '[java.net.Authenticator + java.net.CookieHandler + java.net.CookieManager + java.net.CookieStore + java.net.CookiePolicy + java.net.HttpCookie + java.net.PasswordAuthentication + java.net.ProxySelector + java.net.SocketTimeoutException + java.net.http.HttpClient + java.net.http.HttpClient$Builder + java.net.http.HttpClient$Redirect + java.net.http.HttpClient$Version + java.net.http.HttpHeaders + java.net.http.HttpRequest + java.net.http.HttpRequest$BodyPublisher + java.net.http.HttpRequest$BodyPublishers + java.net.http.HttpRequest$Builder + java.net.http.HttpResponse + java.net.http.HttpResponse$BodyHandler + java.net.http.HttpResponse$BodyHandlers + java.net.http.HttpTimeoutException + java.net.http.WebSocket + java.net.http.WebSocket$Builder + java.net.http.WebSocket$Listener + java.security.cert.X509Certificate + javax.crypto.Mac + javax.crypto.spec.SecretKeySpec + javax.net.ssl.HostnameVerifier ;; clj-http-lite + javax.net.ssl.HttpsURLConnection ;; clj-http-lite + javax.net.ssl.KeyManagerFactory + javax.net.ssl.SSLContext + javax.net.ssl.SSLException + javax.net.ssl.SSLParameters + javax.net.ssl.SSLSession ;; clj-http-lite + javax.net.ssl.TrustManager + javax.net.ssl.TrustManagerFactory + javax.net.ssl.X509TrustManager + jdk.internal.net.http.HttpClientBuilderImpl + jdk.internal.net.http.HttpClientFacade + jdk.internal.net.http.HttpRequestBuilderImpl + jdk.internal.net.http.HttpResponseImpl + jdk.internal.net.http.common.MinimalFuture + jdk.internal.net.http.websocket.BuilderImpl + jdk.internal.net.http.websocket.WebSocketImpl]) (def classes `{:all [clojure.lang.ArityException @@ -165,6 +255,7 @@ java.lang.ClassNotFoundException java.lang.Comparable java.lang.Double + java.lang.Error java.lang.Exception java.lang.Float java.lang.IllegalArgumentException @@ -193,10 +284,14 @@ java.lang.System java.lang.Throwable ;; java.lang.UnsupportedOperationException + java.lang.ref.WeakReference + java.lang.ref.ReferenceQueue + java.lang.ref.Cleaner java.math.BigDecimal java.math.BigInteger java.math.MathContext java.math.RoundingMode + java.net.BindException java.net.ConnectException java.net.DatagramSocket java.net.DatagramPacket @@ -208,54 +303,10 @@ java.net.SocketException java.net.UnknownHostException java.net.URI - ;; java.net.URL, see below + ;; java.net.URL, see custom map + java.net.URLConnection java.net.URLEncoder java.net.URLDecoder - ;; java.net.http - ~@(when features/java-net-http? - '[java.net.Authenticator - java.net.CookieHandler - java.net.CookieManager - java.net.CookieStore - java.net.CookiePolicy - java.net.HttpCookie - java.net.PasswordAuthentication - java.net.ProxySelector - java.net.http.HttpClient - java.net.http.HttpClient$Builder - java.net.http.HttpClient$Redirect - java.net.http.HttpClient$Version - java.net.http.HttpHeaders - java.net.http.HttpRequest - java.net.http.HttpRequest$BodyPublisher - java.net.http.HttpRequest$BodyPublishers - java.net.http.HttpRequest$Builder - java.net.http.HttpResponse - java.net.http.HttpResponse$BodyHandler - java.net.http.HttpResponse$BodyHandlers - java.net.http.HttpTimeoutException - java.net.http.WebSocket - java.net.http.WebSocket$Builder - java.net.http.WebSocket$Listener - java.security.cert.X509Certificate - javax.crypto.Mac - javax.crypto.spec.SecretKeySpec - javax.net.ssl.HostnameVerifier ;; clj-http-lite - javax.net.ssl.HttpsURLConnection ;; clj-http-lite - javax.net.ssl.KeyManagerFactory - javax.net.ssl.SSLContext - javax.net.ssl.SSLParameters - javax.net.ssl.SSLSession ;; clj-http-lite - javax.net.ssl.TrustManager - javax.net.ssl.TrustManagerFactory - javax.net.ssl.X509TrustManager - jdk.internal.net.http.HttpClientBuilderImpl - jdk.internal.net.http.HttpClientFacade - jdk.internal.net.http.HttpRequestBuilderImpl - jdk.internal.net.http.HttpResponseImpl - jdk.internal.net.http.common.MinimalFuture - jdk.internal.net.http.websocket.BuilderImpl - jdk.internal.net.http.websocket.WebSocketImpl]) ~@(when features/java-nio? '[java.nio.ByteBuffer java.nio.ByteOrder @@ -319,6 +370,7 @@ java.time.Year java.time.YearMonth java.time.ZoneRegion + java.time.zone.ZoneRules java.time.ZonedDateTime java.time.ZoneId java.time.ZoneOffset @@ -338,10 +390,26 @@ java.time.temporal.Temporal java.time.temporal.TemporalAccessor java.time.temporal.TemporalAdjuster]) + java.util.concurrent.atomic.AtomicInteger + java.util.concurrent.atomic.AtomicLong + java.util.concurrent.atomic.AtomicReference + java.util.concurrent.CancellationException + java.util.concurrent.CompletionException java.util.concurrent.ExecutionException + java.util.concurrent.Executor java.util.concurrent.LinkedBlockingQueue java.util.concurrent.ScheduledThreadPoolExecutor java.util.concurrent.ThreadPoolExecutor + java.util.concurrent.ThreadPoolExecutor$AbortPolicy + java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy + java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy + java.util.concurrent.ThreadPoolExecutor$DiscardPolicy + java.util.concurrent.ScheduledExecutorService + java.util.concurrent.Future + java.util.concurrent.FutureTask + java.util.concurrent.CompletableFuture + java.util.concurrent.Executors + java.util.concurrent.TimeUnit java.util.jar.Attributes$Name java.util.jar.JarFile java.util.jar.JarEntry @@ -362,19 +430,26 @@ java.util.Base64$Decoder java.util.Base64$Encoder java.util.Date + java.util.HashMap + java.util.IdentityHashMap + java.util.InputMismatchException + java.util.List java.util.Locale java.util.Map java.util.MissingResourceException java.util.NoSuchElementException java.util.Optional java.util.Properties + java.util.Scanner java.util.Set java.util.StringTokenizer + java.util.WeakHashMap java.util.UUID - java.util.concurrent.CompletableFuture - java.util.concurrent.Executors - java.util.concurrent.TimeUnit + java.util.function.Consumer java.util.function.Function + java.util.function.BiConsumer + java.util.function.BiFunction + java.util.function.Predicate java.util.function.Supplier java.util.zip.Inflater java.util.zip.InflaterInputStream @@ -388,6 +463,8 @@ java.util.zip.ZipEntry java.util.zip.ZipException java.util.zip.ZipFile + sun.misc.Signal + sun.misc.SignalHandler ~(symbol "[B") ~(symbol "[I") ~(symbol "[Ljava.lang.Object;") @@ -417,6 +494,7 @@ ;; list above and then everything reachable via the public class will be ;; visible in the native image. :instance-checks [clojure.lang.AFn + clojure.lang.AFunction clojure.lang.AMapEntry ;; for proxy clojure.lang.APersistentMap ;; for proxy clojure.lang.APersistentSet @@ -434,12 +512,8 @@ clojure.lang.IEditableCollection clojure.lang.IMapEntry clojure.lang.IMeta - clojure.lang.ILookup clojure.lang.IPersistentCollection - clojure.lang.IPersistentMap - clojure.lang.IPersistentSet clojure.lang.IPersistentStack - clojure.lang.IPersistentVector clojure.lang.IPersistentList clojure.lang.IRecord clojure.lang.IReduce @@ -447,10 +521,9 @@ clojure.lang.IKVReduce clojure.lang.IRef clojure.lang.ISeq + clojure.lang.IPersistentVector clojure.lang.ITransientVector - clojure.lang.Indexed clojure.lang.Iterate - clojure.lang.LazySeq clojure.lang.LispReader$Resolver clojure.lang.Named clojure.lang.Keyword @@ -471,11 +544,18 @@ clojure.lang.Sequential clojure.lang.Seqable clojure.lang.Volatile - java.util.concurrent.atomic.AtomicInteger - java.util.concurrent.atomic.AtomicLong + ;; the only way to check if something is a channel is to + ;; call instance? on this... + clojure.core.async.impl.channels.ManyToManyChannel + java.lang.AbstractMethodError + java.lang.ExceptionInInitializerError + java.lang.LinkageError + java.lang.ThreadDeath + java.lang.VirtualMachineError + java.net.URLClassLoader + java.sql.Timestamp + java.util.concurrent.TimeoutException java.util.Collection - java.util.List - java.util.Iterator java.util.Map$Entry ~@(when features/xml? ['clojure.data.xml.node.Element])] :custom ~custom-map}) @@ -490,66 +570,89 @@ m (apply hash-map (for [c classes c [(list 'quote c) c]] - c))] - (assoc m :public-class - (fn [v] - (cond (instance? java.lang.Process v) - java.lang.Process - (instance? java.lang.ProcessHandle v) - java.lang.ProcessHandle - (instance? java.lang.ProcessHandle$Info v) - java.lang.ProcessHandle$Info - ;; added for calling .put on .environment from ProcessBuilder - (instance? java.util.Map v) - java.util.Map - ;; added for issue #239 regarding clj-http-lite - ;; can potentially be removed due to fix for #1061 - (instance? java.io.ByteArrayOutputStream v) - java.io.ByteArrayOutputStream - (instance? java.security.MessageDigest v) - java.security.MessageDigest - ;; streams - (instance? java.io.InputStream v) - java.io.InputStream - (instance? java.io.OutputStream v) - java.io.OutputStream - ;; java nio - (instance? java.nio.file.Path v) - java.nio.file.Path - (instance? java.nio.file.FileSystem v) - java.nio.file.FileSystem - (instance? java.nio.file.PathMatcher v) - java.nio.file.PathMatcher - (instance? java.util.stream.BaseStream v) - java.util.stream.BaseStream - (instance? java.nio.ByteBuffer v) - java.nio.ByteBuffer - (instance? java.nio.charset.Charset v) - java.nio.charset.Charset - (instance? java.nio.charset.CharsetEncoder v) - java.nio.charset.CharsetEncoder - (instance? java.nio.CharBuffer v) - java.nio.CharBuffer - (instance? java.nio.channels.FileChannel v) - java.nio.channels.FileChannel - (instance? java.net.CookieStore v) - java.net.CookieStore - ;; this makes interop on reified classes work - ;; see java_net_http_test/interop-test - (instance? sci.impl.types.IReified v) - (first (t/getInterfaces v)) - ;; fix for #1061 - (instance? java.io.Closeable v) - java.io.Closeable - (instance? java.nio.file.attribute.BasicFileAttributes v) - java.nio.file.attribute.BasicFileAttributes - ;; keep commas for merge friendliness - ,,,))))) + c)) + m (assoc m :public-class + (fn [v] + ;; NOTE: a series of instance check, so far, is still cheaper + ;; than piggybacking on defmulti or defprotocol + (cond (instance? java.lang.Process v) + java.lang.Process + (instance? java.lang.ProcessHandle v) + java.lang.ProcessHandle + (instance? java.lang.ProcessHandle$Info v) + java.lang.ProcessHandle$Info + ;; added for calling .put on .environment from ProcessBuilder + (instance? java.util.Map v) + java.util.Map + ;; added for issue #239 regarding clj-http-lite + ;; can potentially be removed due to fix for #1061 + (instance? java.io.ByteArrayOutputStream v) + java.io.ByteArrayOutputStream + (instance? java.security.MessageDigest v) + java.security.MessageDigest + ;; streams + (instance? java.io.InputStream v) + java.io.InputStream + (instance? java.io.OutputStream v) + java.io.OutputStream + ;; java nio + (instance? java.nio.file.Path v) + java.nio.file.Path + (instance? java.nio.file.FileSystem v) + java.nio.file.FileSystem + (instance? java.nio.file.PathMatcher v) + java.nio.file.PathMatcher + (instance? java.util.stream.BaseStream v) + java.util.stream.BaseStream + (instance? java.nio.ByteBuffer v) + java.nio.ByteBuffer + (instance? java.nio.charset.Charset v) + java.nio.charset.Charset + (instance? java.nio.charset.CharsetEncoder v) + java.nio.charset.CharsetEncoder + (instance? java.nio.CharBuffer v) + java.nio.CharBuffer + (instance? java.nio.channels.FileChannel v) + java.nio.channels.FileChannel + (instance? java.net.CookieStore v) + java.net.CookieStore + ;; this makes interop on reified classes work + ;; see java_net_http_test/interop-test + (instance? sci.impl.types.IReified v) + (first (t/getInterfaces v)) + ;; fix for #1061 + (instance? java.io.Closeable v) + java.io.Closeable + (instance? java.nio.file.attribute.BasicFileAttributes v) + java.nio.file.attribute.BasicFileAttributes + (instance? java.util.concurrent.Future v) + java.util.concurrent.Future + (instance? java.util.concurrent.ScheduledExecutorService v) + java.util.concurrent.ScheduledExecutorService + (instance? java.util.Iterator v) + java.util.Iterator + ;; keep commas for merge friendliness + ,,,))) + m (assoc m (list 'quote 'clojure.lang.Var) 'sci.lang.Var) + m (assoc m (list 'quote 'clojure.lang.Namespace) 'sci.lang.Namespace)] + m)) -(def class-map (gen-class-map)) + +(def class-map* + "This contains mapping of symbol to class of all classes that are + allowed to be initialized at build time." + (gen-class-map)) + +(def class-map + "A delay to delay initialization of java-net-http classes to run time, since GraalVM 22.1" + (delay (persistent! (reduce (fn [acc c] + (assoc! acc c (Class/forName (str c)))) + (transient class-map*) (when features/java-net-http? + java-net-http-classes))))) (def imports - '{Appendable java.lang.Appendable + '{AbstractMethodError java.lang.AbstractMethodError + Appendable java.lang.Appendable ArithmeticException java.lang.ArithmeticException AssertionError java.lang.AssertionError BigDecimal java.math.BigDecimal @@ -563,7 +666,9 @@ ClassNotFoundException java.lang.ClassNotFoundException Comparable java.lang.Comparable Double java.lang.Double + Error java.lang.Error Exception java.lang.Exception + ExceptionInInitializerError java.lang.ExceptionInInitializerError IndexOutOfBoundsException java.lang.IndexOutOfBoundsException IllegalArgumentException java.lang.IllegalArgumentException IllegalStateException java.lang.IllegalStateException @@ -573,6 +678,7 @@ File java.io.File Float java.lang.Float Long java.lang.Long + LinkageError java.lang.LinkageError Math java.lang.Math NullPointerException java.lang.NullPointerException Number java.lang.Number @@ -589,11 +695,15 @@ System java.lang.System Thread java.lang.Thread Throwable java.lang.Throwable + VirtualMachineError java.lang.VirtualMachineError + ThreadDeath java.lang.ThreadDeath ;; UnsupportedOperationException java.lang.UnsupportedOperationException }) (defn reflection-file-entries [] - (let [entries (vec (for [c (sort (:all classes)) + (let [entries (vec (for [c (sort (concat (:all classes) + (when features/java-net-http? + java-net-http-classes))) :let [class-name (str c)]] {:name class-name :allPublicMethods true @@ -627,7 +737,8 @@ (let [all-entries (reflection-file-entries)] (spit (or (first args) - "resources/META-INF/native-image/babashka/babashka/reflect-config.json") (json/generate-string all-entries {:pretty true})))) + "resources/META-INF/native-image/babashka/babashka/reflect-config.json") + (json/generate-string all-entries {:pretty true})))) (defn public-declared-method? [c m] (and (= c (.getDeclaringClass m)) @@ -643,18 +754,25 @@ (sort-by :name) (vec))) -(defn all-methods [] +(defn all-classes [] + "Returns every java.lang.Class instance Babashka supports." (->> (reflection-file-entries) (map :name) - (map #(Class/forName %)) - (mapcat public-declared-method-names))) + (map #(Class/forName %)))) + +(defn all-methods [] + (mapcat public-declared-method-names (all-classes))) + +(def cns (sci/create-ns 'babashka.classes nil)) + +(def classes-namespace + {:obj cns + 'all-classes (sci/copy-var all-classes cns)}) (comment (public-declared-method-names java.net.URL) (public-declared-method-names java.util.Properties) - (->> (reflection-file-entries) - (map :name) - (map #(Class/forName %))) + (all-classes) ) diff --git a/src/babashka/impl/classpath.clj b/src/babashka/impl/classpath.clj index cd223e1f..11691390 100644 --- a/src/babashka/impl/classpath.clj +++ b/src/babashka/impl/classpath.clj @@ -5,7 +5,8 @@ [clojure.java.io :as io] [clojure.string :as str] [sci.core :as sci]) - (:import [java.util.jar JarFile Manifest])) + (:import [java.util.jar JarFile Manifest] + (java.net URL))) (set! *warn-on-reflection* true) @@ -65,14 +66,18 @@ entries (keep part->entry parts)] (Loader. entries))) -(defn source-for-namespace [loader namespace opts] +(defn resource-paths [namespace] (let [ns-str (name namespace) - ^String ns-str (munge ns-str) + ^String ns-str (namespace-munge ns-str) ;; do NOT pick the platform specific file separator here, since that doesn't work for searching in .jar files ;; (io/file "foo" "bar/baz") does work on Windows, despite the forward slash base-path (.replace ns-str "." "/") resource-paths (mapv #(str base-path %) [".bb" ".clj" ".cljc"])] - (getResource loader resource-paths opts))) + resource-paths)) + +(defn source-for-namespace [loader namespace opts] + (let [rps (resource-paths namespace)] + (getResource loader rps opts))) (defn main-ns [manifest-resource] (with-open [is (io/input-stream manifest-resource)] @@ -106,11 +111,11 @@ [] (:cp @cp-state)) -(defn resource [path] - (when-let [st @cp-state] - (let [loader (:loader st)] - (if (str/starts-with? path "/") nil ;; non-relative paths always return nil - (getResource loader [path] true))))) +(defn resource + (^URL [path] (when-let [st @cp-state] (resource (:loader st) path))) + (^URL [loader path] + (if (str/starts-with? path "/") nil ;; non-relative paths always return nil + (getResource loader [path] true)))) (def cns (sci/create-ns 'babashka.classpath nil)) @@ -126,5 +131,5 @@ (def cp "src:feature-xml:feature-core-async:feature-yaml:feature-csv:feature-transit:feature-java-time:feature-java-nio:sci/src:babashka.curl/src:babashka.pods/src:resources:sci/resources:/Users/borkdude/.m2/repository/com/cognitect/transit-java/1.0.343/transit-java-1.0.343.jar:/Users/borkdude/.m2/repository/org/clojure/clojure/1.10.2-alpha1/clojure-1.10.2-alpha1.jar:/Users/borkdude/.m2/repository/commons-codec/commons-codec/1.10/commons-codec-1.10.jar:/Users/borkdude/.m2/repository/org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar:/Users/borkdude/.m2/repository/org/clojure/tools.logging/0.6.0/tools.logging-0.6.0.jar:/Users/borkdude/.m2/repository/org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar:/Users/borkdude/.m2/repository/org/clojure/spec.alpha/0.2.187/spec.alpha-0.2.187.jar:/Users/borkdude/.m2/repository/org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.jar:/Users/borkdude/.m2/repository/org/clojure/tools.analyzer.jvm/1.0.0/tools.analyzer.jvm-1.0.0.jar:/Users/borkdude/.m2/repository/borkdude/graal.locking/0.0.2/graal.locking-0.0.2.jar:/Users/borkdude/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.10.2/jackson-dataformat-cbor-2.10.2.jar:/Users/borkdude/.m2/repository/com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar:/Users/borkdude/.m2/repository/org/flatland/ordered/1.5.9/ordered-1.5.9.jar:/Users/borkdude/.m2/repository/org/postgresql/postgresql/42.2.12/postgresql-42.2.12.jar:/Users/borkdude/.m2/repository/fipp/fipp/0.6.22/fipp-0.6.22.jar:/Users/borkdude/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.10.2/jackson-core-2.10.2.jar:/Users/borkdude/.m2/repository/org/yaml/snakeyaml/1.25/snakeyaml-1.25.jar:/Users/borkdude/.m2/repository/org/ow2/asm/asm/5.2/asm-5.2.jar:/Users/borkdude/.gitlibs/libs/clj-commons/conch/9aa7724e925cb8bf163e0b62486dd420b84e5f0b/src:/Users/borkdude/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/Users/borkdude/.m2/repository/seancorfield/next.jdbc/1.0.424/next.jdbc-1.0.424.jar:/Users/borkdude/.m2/repository/org/clojure/data.xml/0.2.0-alpha6/data.xml-0.2.0-alpha6.jar:/Users/borkdude/.m2/repository/org/msgpack/msgpack/0.6.12/msgpack-0.6.12.jar:/Users/borkdude/.m2/repository/borkdude/edamame/0.0.11-alpha.9/edamame-0.0.11-alpha.9.jar:/Users/borkdude/.m2/repository/org/clojure/data.csv/1.0.0/data.csv-1.0.0.jar:/Users/borkdude/.m2/repository/com/cognitect/transit-clj/1.0.324/transit-clj-1.0.324.jar:/Users/borkdude/.m2/repository/clj-commons/clj-yaml/0.7.1/clj-yaml-0.7.1.jar:/Users/borkdude/.m2/repository/org/clojure/core.rrb-vector/0.1.1/core.rrb-vector-0.1.1.jar:/Users/borkdude/.m2/repository/persistent-sorted-set/persistent-sorted-set/0.1.2/persistent-sorted-set-0.1.2.jar:/Users/borkdude/.m2/repository/cheshire/cheshire/5.10.0/cheshire-5.10.0.jar:/Users/borkdude/.m2/repository/tigris/tigris/0.1.2/tigris-0.1.2.jar:/Users/borkdude/.m2/repository/org/clojure/tools.reader/1.3.2/tools.reader-1.3.2.jar:/Users/borkdude/.m2/repository/datascript/datascript/0.18.11/datascript-0.18.11.jar:/Users/borkdude/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar:/Users/borkdude/.m2/repository/org/clojure/core.memoize/0.8.2/core.memoize-0.8.2.jar:/Users/borkdude/.m2/repository/org/clojure/data.priority-map/0.0.7/data.priority-map-0.0.7.jar:/Users/borkdude/.m2/repository/org/clojure/java.data/1.0.64/java.data-1.0.64.jar:/Users/borkdude/.m2/repository/borkdude/sci.impl.reflector/0.0.1/sci.impl.reflector-0.0.1.jar:/Users/borkdude/.m2/repository/nrepl/bencode/1.1.0/bencode-1.1.0.jar:/Users/borkdude/.m2/repository/org/clojure/core.cache/0.8.2/core.cache-0.8.2.jar:/Users/borkdude/.m2/repository/org/clojure/core.async/1.1.587/core.async-1.1.587.jar:/Users/borkdude/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.10.2/jackson-dataformat-smile-2.10.2.jar:/Users/borkdude/.m2/repository/org/clojure/data.codec/0.1.0/data.codec-0.1.0.jar:/Users/borkdude/.m2/repository/javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar") (def l (loader cp)) (source-for-namespace l 'babashka.impl.cheshire nil) - (time (:file (source-for-namespace l 'cheshire.core nil))) ;; 20ms -> 2.25ms - ) + (time (:file (source-for-namespace l 'cheshire.core nil)))) ;; 20ms -> 2.25ms + diff --git a/src/babashka/impl/cli.clj b/src/babashka/impl/cli.clj new file mode 100644 index 00000000..753e99a7 --- /dev/null +++ b/src/babashka/impl/cli.clj @@ -0,0 +1,37 @@ +(ns babashka.impl.cli + (:require + [babashka.cli] + [sci.core :as sci])) + +(def cns (sci/create-ns 'babashka.cli)) + +(def cli-namespace + (sci/copy-ns babashka.cli cns)) + +(defn exec-fn-snippet + ([sym] (exec-fn-snippet sym nil)) + ([sym extra-opts] + (format " +(ns exec-%s + (:require [babashka.cli :as cli])) +(let [extra-opts '%s + sym `%s + the-var (requiring-resolve sym) + the-var-meta (meta the-var) + ns (:ns (meta the-var)) + ns-meta (meta ns) + ct (babashka.tasks/current-task) + cli-opts (babashka.cli/merge-opts (:org.babashka/cli ns-meta) + (:org.babashka/cli the-var-meta) + (:org.babashka/cli ct) + extra-opts) + task-exec-args (:exec-args ct) + cli-exec-args (:exec-args cli-opts) + exec-args {:exec-args (babashka.cli/merge-opts cli-exec-args task-exec-args)} + cli-opts (babashka.cli/merge-opts exec-args cli-opts) + opts (babashka.cli/parse-opts *command-line-args* cli-opts)] +(the-var opts))" + (random-uuid) + (pr-str extra-opts) + sym + ))) diff --git a/src/babashka/impl/clojure/core.clj b/src/babashka/impl/clojure/core.clj index ece78db3..fa202d41 100644 --- a/src/babashka/impl/clojure/core.clj +++ b/src/babashka/impl/clojure/core.clj @@ -7,9 +7,10 @@ [clojure.core :as c] [clojure.string :as str] [sci.core :as sci] - [sci.impl.namespaces :refer [copy-core-var core-var macrofy]] + [sci.impl.copy-vars :refer [copy-core-var new-var macrofy]] [sci.impl.parser :as parser] - [sci.impl.vars :as vars :refer [clojure-core-ns]])) + [sci.impl.utils :refer [clojure-core-ns]] + [sci.impl.vars :as vars])) (defn locking* [form bindings v f & args] (apply @#'locking/locking form bindings v f args)) @@ -31,6 +32,7 @@ (def command-line-args (core-dynamic-var '*command-line-args*)) (def warn-on-reflection (core-dynamic-var '*warn-on-reflection* false)) (def compile-files (core-dynamic-var '*compile-files* false)) +(def unchecked-math (core-dynamic-var '*unchecked-math* false)) (def math-context (core-dynamic-var '*math-context*)) (defn read+string @@ -164,11 +166,12 @@ '*data-readers* data-readers 'default-data-readers (copy-core-var default-data-readers) 'xml-seq (copy-core-var xml-seq) - 'read+string (core-var 'read+string (fn [& args] - (apply read+string @common/ctx args))) + 'read+string (new-var 'read+string (fn [& args] + (apply read+string (common/ctx) args))) '*command-line-args* command-line-args '*warn-on-reflection* warn-on-reflection '*compile-files* compile-files + '*unchecked-math* unchecked-math '*math-context* math-context 'with-precision (sci/copy-var with-precision clojure-core-ns) '-with-precision (sci/copy-var -with-precision clojure-core-ns) @@ -192,6 +195,8 @@ 'iteration (sci/copy-var iteration clojure-core-ns) 'abs (sci/copy-var abs clojure-core-ns) 'StackTraceElement->vec (sci/copy-var StackTraceElement->vec clojure-core-ns) - 'memfn (sci/copy-var memfn clojure-core-ns) - 'into-array (sci/copy-var into-array clojure-core-ns)} + 'into-array (sci/copy-var into-array clojure-core-ns) + 'print-method (sci/copy-var print-method clojure-core-ns) + 'print-dup (sci/copy-var print-dup clojure-core-ns) + 'PrintWriter-on (sci/copy-var PrintWriter-on clojure-core-ns)} ) diff --git a/src/babashka/impl/clojure/core/async.clj b/src/babashka/impl/clojure/core/async.clj index f6d87959..391c4d9b 100644 --- a/src/babashka/impl/clojure/core/async.clj +++ b/src/babashka/impl/clojure/core/async.clj @@ -2,7 +2,8 @@ {:no-doc true} (:require [clojure.core.async :as async] [clojure.core.async.impl.protocols :as protocols] - [sci.impl.namespaces :refer [copy-var macrofy]] + [sci.core :as sci :refer [copy-var]] + [sci.impl.copy-vars :refer [macrofy]] [sci.impl.vars :as vars])) (def ^java.util.concurrent.Executor executor @#'async/thread-macro-executor) @@ -39,14 +40,13 @@ [_ _ bindings & body] (list 'clojure.core.async/thread (list* 'loop bindings body))) -(def core-async-namespace (vars/->SciNamespace 'clojure.core.async nil)) +(def core-async-namespace (sci/create-ns 'clojure.core.async nil)) (def async-namespace {:obj core-async-namespace '!! (copy-var async/>!! core-async-namespace) 'admix (copy-var async/admix core-async-namespace) - 'alts! (copy-var async/alts! core-async-namespace) 'alts!! (copy-var async/alts!! core-async-namespace) 'alt!! (macrofy 'alt!! alt!! core-async-namespace) 'buffer (copy-var async/buffer core-async-namespace) @@ -68,6 +68,8 @@ 'mult (copy-var async/mult core-async-namespace) 'offer! (copy-var async/offer! core-async-namespace) 'onto-chan (copy-var async/onto-chan core-async-namespace) + 'onto-chan! (copy-var async/onto-chan! core-async-namespace) + 'onto-chan!! (copy-var async/onto-chan!! core-async-namespace) 'partition (copy-var async/partition core-async-namespace) 'partition-by (copy-var async/partition-by core-async-namespace) 'pipe (copy-var async/pipe core-async-namespace) @@ -92,6 +94,8 @@ 'thread-call (copy-var thread-call core-async-namespace) 'timeout (copy-var async/timeout core-async-namespace) 'to-chan (copy-var async/to-chan core-async-namespace) + 'to-chan! (copy-var async/to-chan! core-async-namespace) + 'to-chan!! (copy-var async/to-chan!! core-async-namespace) 'toggle (copy-var async/toggle core-async-namespace) 'transduce (copy-var async/transduce core-async-namespace) 'unblocking-buffer? (copy-var async/unblocking-buffer? core-async-namespace) @@ -104,12 +108,13 @@ 'untap-all (copy-var async/untap-all core-async-namespace) ;; polyfill 'go (macrofy 'go thread core-async-namespace) - '! (copy-var async/>!! core-async-namespace) + '! (copy-var async/>!! core-async-namespace {:name '>!}) 'alt! (macrofy 'alt! alt!! core-async-namespace) + 'alts! (copy-var async/alts!! core-async-namespace {:name 'alts!}) 'go-loop (macrofy 'go-loop go-loop core-async-namespace)}) -(def async-protocols-ns (vars/->SciNamespace 'clojure.core.async.impl.protocols nil)) +(def async-protocols-ns (sci/create-ns 'clojure.core.async.impl.protocols nil)) (def async-protocols-namespace {:obj async-protocols-ns diff --git a/src/babashka/impl/clojure/core/server.clj b/src/babashka/impl/clojure/core/server.clj index 6211d7a5..fb3c1e90 100644 --- a/src/babashka/impl/clojure/core/server.clj +++ b/src/babashka/impl/clojure/core/server.clj @@ -18,7 +18,7 @@ [babashka.impl.common :refer [debug]] [sci.core :as sci] [sci.impl.parser :as p] - [sci.impl.vars :as vars]) + [sci.impl.utils :as utils]) (:import [clojure.lang LineNumberingPushbackReader] [java.io BufferedWriter InputStreamReader OutputStreamWriter] @@ -214,12 +214,11 @@ :val (if (instance? Throwable ret) (Throwable->map ret) ret) - :ns (str (vars/current-ns-name)) + :ns (str (utils/current-ns-name)) :ms ms :form s}) true))) (catch Throwable ex - (prn (ex-message ex)) (set! *e ex) (out-fn {:tag :ret :val (ex->data ex (or (-> ex ex-data :clojure.error/phase) :execution)) :ns (str (.name *ns*)) :form s diff --git a/src/babashka/impl/clojure/test.clj b/src/babashka/impl/clojure/test.clj index 0053e289..8903e911 100644 --- a/src/babashka/impl/clojure/test.clj +++ b/src/babashka/impl/clojure/test.clj @@ -232,13 +232,13 @@ For additional event types, see the examples in the code. "} babashka.impl.clojure.test - (:require [babashka.impl.common :refer [ctx]] - [clojure.stacktrace :as stack] - [clojure.template :as temp] - [sci.core :as sci] - [sci.impl.namespaces :as sci-namespaces] - [sci.impl.resolve :as resolve] - [sci.impl.vars :as vars])) + (:require + [babashka.impl.common :refer [ctx]] + [clojure.stacktrace :as stack] + [clojure.template :as temp] + [sci.core :as sci] + [sci.impl.namespaces :as sci-namespaces] + [sci.impl.resolve :as resolve])) ;; Nothing is marked "private" here, so you can rebind things to plug ;; in your own testing or reporting frameworks. @@ -297,7 +297,7 @@ current assertion." {:added "1.1"} [m] - (let [{:keys [:file :line]} (meta (first @testing-vars))] + (let [{:keys [:file :line]} (merge m (meta (first @testing-vars)))] (str ;; Uncomment to include namespace in failure report: ;;(ns-name (:ns (meta (first *testing-vars*)))) "/ " @@ -332,7 +332,7 @@ :added "1.1"} report-impl :type) -(def report (sci/copy-var report-impl tns)) +(def report (sci/copy-var report-impl tns {:name 'report})) (defn do-report "Add file and line information to a test result and call report. @@ -408,8 +408,8 @@ {:added "1.1"} [x] (if (symbol? x) - (when-let [v (second (resolve/lookup @ctx x false))] - (when-let [value (if (vars/var? v) + (when-let [v (second (resolve/lookup (ctx) x false))] + (when-let [value (if (instance? sci.lang.Var v) (get-possibly-unbound-var v) v)] (and (fn? value) @@ -667,7 +667,7 @@ value of key." {:added "1.1"} [key coll] - (swap! ns->fixtures assoc-in [(sci-namespaces/sci-ns-name @vars/current-ns) key] coll)) + (swap! ns->fixtures assoc-in [(sci-namespaces/sci-ns-name @sci/ns) key] coll)) (defmulti use-fixtures "Wrap test runs in a fixture function to perform setup and @@ -722,7 +722,7 @@ :expected nil, :actual e}))) (do-report {:type :end-test-var, :var v})))) -(def test-var (sci/copy-var test-var-impl tns)) +(def test-var (sci/copy-var test-var-impl tns {:name 'test-var})) (defn test-vars "Groups vars by their namespace and runs test-vars on them with @@ -779,7 +779,7 @@ Defaults to current namespace if none given. Returns a map summarizing test results." {:added "1.1"} - ([ctx] (run-tests ctx @vars/current-ns)) + ([ctx] (run-tests ctx @sci/ns)) ([ctx & namespaces] (let [summary (assoc (apply merge-with + (map #(test-ns ctx %) namespaces)) :type :summary)] @@ -804,3 +804,39 @@ [summary] (and (zero? (:fail summary 0)) (zero? (:error summary 0)))) + +(defn run-test-var + "Runs the tests for a single Var, with fixtures executed around the test, and summary output after." + {:added "1.11"} + [v] + (sci/binding [report-counters (atom @initial-report-counters)] + (let [ns-obj (-> v meta :ns) + summary (do + (do-report {:type :begin-test-ns + :ns ns-obj}) + (test-vars [v]) + (do-report {:type :end-test-ns + :ns ns-obj}) + (assoc @@report-counters :type :summary))] + (do-report summary) + summary))) + +(defmacro run-test + "Runs a single test. + Because the intent is to run a single test, there is no check for the namespace test-ns-hook." + {:added "1.11"} + [test-symbol] + (let [test-var (sci/resolve (ctx) test-symbol)] + (cond + (nil? test-var) + (sci/binding [sci/out sci/err] + (binding [*out* sci/out] + (println "Unable to resolve" test-symbol "to a test function."))) + + (not (-> test-var meta :test)) + (sci/binding [sci/out sci/err] + (binding [*out* sci/out] + (println test-symbol "is not a test."))) + + :else + `(clojure.test/run-test-var ~test-var)))) diff --git a/src/babashka/impl/clojure/tools/reader.clj b/src/babashka/impl/clojure/tools/reader.clj new file mode 100644 index 00000000..29f67289 --- /dev/null +++ b/src/babashka/impl/clojure/tools/reader.clj @@ -0,0 +1,56 @@ +(ns babashka.impl.clojure.tools.reader + (:refer-clojure :exclude [read]) + (:require + [edamame.core :as e] + [sci.core :as sci] + [clojure.tools.reader.reader-types :as rt])) + +(def rns (sci/create-ns 'clojure.tools.reader)) + +(def default-opts + (e/normalize-opts + {:all true + :row-key :line + :col-key :column + :location? seq? + :end-location false})) + +;; Added for compatibility with tools.namespace +(defn read + "Reads the first object from an IPushbackReader or a java.io.PushbackReader. + Returns the object read. If EOF, throws if eof-error? is true. + Otherwise returns sentinel. If no stream is provided, *in* will be used. + Opts is a persistent map with valid keys: + :read-cond - :allow to process reader conditionals, or + :preserve to keep all branches + :features - persistent set of feature keywords for reader conditionals + :eof - on eof, return value unless :eofthrow, then throw. + if not specified, will throw + ***WARNING*** + Note that read can execute code (controlled by *read-eval*), + and as such should be used only with trusted sources. + To read data structures only, use clojure.tools.reader.edn/read + Note that the function signature of clojure.tools.reader/read and + clojure.tools.reader.edn/read is not the same for eof-handling" + {:arglists '([] [reader] [opts reader] [reader eof-error? eof-value])} + ([] (read @sci/in true nil)) + ([reader] (read reader true nil)) + ([{eof :eof :as opts :or {eof :eofthrow}} reader] + (let [opts (assoc default-opts + :read-cond (:read-cond opts) + :features (:features opts)) + v (e/parse-next reader opts)] + (if (identical? ::e/eof v) + (if (identical? :eofthrow eof) + (throw (java.io.EOFException.)) + eof) + v))) + ([reader eof-error? sentinel] + (let [v (e/parse-next reader default-opts)] + (if (identical? ::e/eof v) + (if eof-error? + (throw (java.io.EOFException.)) + sentinel) + v)))) + +(def reader-namespace {'read (sci/copy-var read rns)}) diff --git a/src/babashka/impl/clojure/tools/reader_types.clj b/src/babashka/impl/clojure/tools/reader_types.clj index c88f036d..4d45ee25 100644 --- a/src/babashka/impl/clojure/tools/reader_types.clj +++ b/src/babashka/impl/clojure/tools/reader_types.clj @@ -13,4 +13,5 @@ 'peek-char (sci/copy-var rt/peek-char rtns) 'read-char (sci/copy-var rt/read-char rtns) 'unread (sci/copy-var rt/unread rtns) - 'source-logging-push-back-reader (sci/copy-var rt/source-logging-push-back-reader rtns)}) + 'source-logging-push-back-reader (sci/copy-var rt/source-logging-push-back-reader rtns) + 'source-logging-reader? (sci/copy-var rt/source-logging-reader? rtns)}) diff --git a/src/babashka/impl/clojure/zip.clj b/src/babashka/impl/clojure/zip.clj index 8a101d26..67ee39d1 100644 --- a/src/babashka/impl/clojure/zip.clj +++ b/src/babashka/impl/clojure/zip.clj @@ -1,10 +1,9 @@ (ns babashka.impl.clojure.zip {:no-doc true} (:require [clojure.zip :as zip] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + [sci.core :as sci :refer [copy-var]])) -(def zip-ns (vars/->SciNamespace 'clojure.zip nil)) +(def zip-ns (sci/create-ns 'clojure.zip nil)) (def zip-namespace {'zipper (copy-var zip/zipper zip-ns) diff --git a/src/babashka/impl/common.clj b/src/babashka/impl/common.clj index 9cf5c2a2..ec03b968 100644 --- a/src/babashka/impl/common.clj +++ b/src/babashka/impl/common.clj @@ -1,6 +1,11 @@ -(ns babashka.impl.common) +(ns babashka.impl.common + (:require + [clojure.java.io :as io] + [clojure.string :as str] + [sci.ctx-store :as ctx-store])) ;; placeholder for ctx -(def ctx (volatile! nil)) +(defn ctx [] (ctx-store/get-ctx)) (def bb-edn (volatile! nil)) (def debug (volatile! false)) +(def version (str/trim (slurp (io/resource "BABASHKA_VERSION")))) diff --git a/src/babashka/impl/curl.clj b/src/babashka/impl/curl.clj index 264a5443..ce0d36bc 100644 --- a/src/babashka/impl/curl.clj +++ b/src/babashka/impl/curl.clj @@ -1,10 +1,10 @@ (ns babashka.impl.curl {:no-doc true} - (:require [babashka.curl :as curl] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + (:require + [babashka.curl :as curl] + [sci.core :as sci :refer [copy-var]])) -(def tns (vars/->SciNamespace 'babashka.curl nil)) +(def tns (sci/create-ns 'babashka.curl nil)) (def curl-namespace {'request (copy-var curl/request tns) diff --git a/src/babashka/impl/data.clj b/src/babashka/impl/data.clj index fd38dfa5..6d2e69ba 100644 --- a/src/babashka/impl/data.clj +++ b/src/babashka/impl/data.clj @@ -1,10 +1,11 @@ (ns babashka.impl.data {:no-doc true} - (:require [babashka.impl.clojure.data :as data] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + (:require + [babashka.impl.clojure.data :as data] + [sci.core :as sci :refer [copy-var]])) -(def data-ns (vars/->SciNamespace 'clojure.data nil)) +(def data-ns (sci/create-ns 'clojure.data nil)) (def data-namespace - {'diff (copy-var data/diff data-ns)}) + {'diff (copy-var data/diff data-ns) + 'equality-partition (copy-var data/equality-partition data-ns)}) diff --git a/src/babashka/impl/datafy.clj b/src/babashka/impl/datafy.clj index d0c7a3e5..4f389fc8 100644 --- a/src/babashka/impl/datafy.clj +++ b/src/babashka/impl/datafy.clj @@ -3,8 +3,7 @@ (:refer-clojure :exclude [create-ns]) (:require [babashka.impl.protocols :as protocols] - [sci.core :as sci :refer [copy-var]] - [sci.impl.vars])) + [sci.core :as sci :refer [copy-var]])) (def datafy-ns (sci/create-ns 'clojure.datafy nil)) diff --git a/src/babashka/impl/deps.clj b/src/babashka/impl/deps.clj index 735ec477..c6cfb413 100644 --- a/src/babashka/impl/deps.clj +++ b/src/babashka/impl/deps.clj @@ -66,7 +66,7 @@ paths) paths)] (cp/add-classpath (str/join cp/path-sep paths)))) - (let [need-deps? (or (:deps deps-map) + (let [need-deps? (or (seq (:deps deps-map)) (and (:aliases deps-map) aliases))] (when need-deps? @@ -101,4 +101,4 @@ 'clojure (sci/copy-var bdeps/clojure dns) 'merge-deps (sci/copy-var merge-deps dns) ;; undocumented - 'merge-defaults (sci/copy-var merge-default-deps dns)}) + 'merge-defaults (sci/copy-var merge-default-deps dns {:name 'merge-defaults})}) diff --git a/src/babashka/impl/edamame.clj b/src/babashka/impl/edamame.clj new file mode 100644 index 00000000..62035e3d --- /dev/null +++ b/src/babashka/impl/edamame.clj @@ -0,0 +1,7 @@ +(ns babashka.impl.edamame + (:require [edamame.core] + [sci.core :as sci])) + +(def ens (sci/create-ns 'edamame.core)) + +(def edamame-namespace (sci/copy-ns edamame.core ens)) diff --git a/src/babashka/impl/error_handler.clj b/src/babashka/impl/error_handler.clj index b48adb76..e780c33c 100644 --- a/src/babashka/impl/error_handler.clj +++ b/src/babashka/impl/error_handler.clj @@ -108,7 +108,7 @@ stacktrace (dedupe (concat (sequence (comp (map StackTraceElement->vec) (take-while #(not (str/starts-with? (first %) "sci.impl."))) - (map render-native-stacktrace-elem)) + (keep render-native-stacktrace-elem)) (.getStackTrace (or (ex-cause e) e))) (sci/stacktrace e)))] (if exit-code diff --git a/src/babashka/impl/features.clj b/src/babashka/impl/features.clj index 00eaa26e..7639997c 100644 --- a/src/babashka/impl/features.clj +++ b/src/babashka/impl/features.clj @@ -27,6 +27,7 @@ (def datascript? (= "true" (System/getenv "BABASHKA_FEATURE_DATASCRIPT"))) (def lanterna? (= "true" (System/getenv "BABASHKA_FEATURE_LANTERNA"))) (def spec-alpha? (= "true" (System/getenv "BABASHKA_FEATURE_SPEC_ALPHA"))) +(def rrb-vector? (= "true" (System/getenv "BABASHKA_FEATURE_RRB_VECTOR"))) (when xml? (require '[babashka.impl.xml])) @@ -76,3 +77,6 @@ (when priority-map? (require '[babashka.impl.priority-map])) + +(when rrb-vector? + (require '[babashka.impl.rrb-vector])) diff --git a/src/babashka/impl/nrepl_server.clj b/src/babashka/impl/nrepl_server.clj new file mode 100644 index 00000000..5b3569cb --- /dev/null +++ b/src/babashka/impl/nrepl_server.clj @@ -0,0 +1,23 @@ +(ns babashka.impl.nrepl-server + {:no-doc true} + (:require + [babashka.impl.clojure.core] + [babashka.impl.common :as common] + [babashka.nrepl.server :as server] + [sci.core :as sci])) + +(defn start-server! + ([] + (start-server! nil)) + ([opts] + (let [dev? (= "true" (System/getenv "BABASHKA_DEV")) + opts (merge {:debug dev? + :describe {"versions" {"babashka" common/version}} + :thread-bind [babashka.impl.clojure.core/warn-on-reflection]} + opts)] + (server/start-server! (common/ctx) opts)))) + +(def nrepl-server-namespace + (let [ns-sci (sci/create-ns 'babashka.nrepl.server)] + {'start-server! (sci/copy-var start-server! ns-sci) + 'stop-server! (sci/copy-var server/stop-server! ns-sci)})) diff --git a/src/babashka/impl/patches/datafy.clj b/src/babashka/impl/patches/datafy.clj index c0127599..386cb5dc 100644 --- a/src/babashka/impl/patches/datafy.clj +++ b/src/babashka/impl/patches/datafy.clj @@ -1,13 +1,15 @@ (ns babashka.impl.patches.datafy - (:require [babashka.impl.common :refer [ctx]] - [clojure.core.protocols :as p] - [clojure.datafy] - ;; ensure datafy is loaded, we're going to override its - ;; clojure.lang.Namespace implementation for datafy - [clojure.reflect] - [sci.impl.namespaces :refer [sci-ns-name sci-ns-publics sci-ns-imports sci-ns-interns]] - [sci.impl.vars]) - (:import [sci.impl.vars SciNamespace])) + (:require ;; ensure datafy is loaded, we're going to override its + ;; clojure.lang.Namespace implementation for datafy + [babashka.impl.common :refer [ctx]] + [clojure.core.protocols :as p] + [clojure.datafy] + [clojure.reflect] + [sci.impl.namespaces :refer [sci-ns-imports sci-ns-interns sci-ns-name + sci-ns-publics]] + [sci.impl.vars]) + (:import + [sci.lang Namespace])) (defn- sortmap [m] (into (sorted-map) m)) @@ -29,10 +31,10 @@ (assoc ret :name (-> c .getName symbol) :members (->> members (group-by :name) sortmap))))) (extend-protocol p/Datafiable - SciNamespace + Namespace (datafy [n] (with-meta {:name (sci-ns-name n) - :publics (->> n (sci-ns-publics @ctx) sortmap) - :imports (->> n (sci-ns-imports @ctx) sortmap) - :interns (->> n (sci-ns-interns @ctx) sortmap)} + :publics (->> n (sci-ns-publics (ctx)) sortmap) + :imports (->> n (sci-ns-imports (ctx)) sortmap) + :interns (->> n (sci-ns-interns (ctx)) sortmap)} (meta n)))) diff --git a/src/babashka/impl/pods.clj b/src/babashka/impl/pods.clj index 92e671a2..75752c20 100644 --- a/src/babashka/impl/pods.clj +++ b/src/babashka/impl/pods.clj @@ -1,12 +1,37 @@ (ns babashka.impl.pods {:no-doc true} (:refer-clojure :exclude [read]) - (:require [babashka.impl.common :refer [ctx]] - [babashka.pods.sci :as pods] - [sci.core :as sci])) + (:require + [babashka.impl.common :refer [bb-edn ctx]] + [babashka.pods.sci :as pods] + [clojure.java.io :as io] + [sci.core :as sci])) (defn load-pod [& args] - (apply pods/load-pod @ctx args)) + (apply pods/load-pod (ctx) args)) + +(defn load-pods-metadata [pods-map opts] + (reduce-kv + (fn [pod-namespaces pod-spec coord] + (merge pod-namespaces + (condp #(contains? %2 %1) coord + :version + (pods/load-pod-metadata pod-spec + (merge opts {:cache true} + (select-keys coord [:version :cache]))) + + :path + (pods/load-pod-metadata (-> @bb-edn :file io/file) + pod-spec + (merge opts {:cache true} + (select-keys coord [:path :cache]))) + + (throw (IllegalArgumentException. + (str (-> coord keys first) + " is not a supported pod coordinate type. " + "Use :version for registry-hosted pods or :path " + "for pods on your local filesystem.")))))) + {} pods-map)) (def podns (sci/create-ns 'babashka.pods nil)) diff --git a/src/babashka/impl/pprint.clj b/src/babashka/impl/pprint.clj index 8047638b..88ada2e6 100644 --- a/src/babashka/impl/pprint.clj +++ b/src/babashka/impl/pprint.clj @@ -1,7 +1,8 @@ (ns babashka.impl.pprint {:no-doc true} (:require [clojure.pprint :as pprint] - [sci.core :as sci])) + [sci.core :as sci] + [sci.pprint])) (defonce patched? (volatile! false)) @@ -95,32 +96,34 @@ pprint/*print-pprint-dispatch* @print-pprint-dispatch pprint/*print-miser-width* @print-miser-width *print-meta* @sci/print-meta - *print-readably* @sci/print-readably] + *print-readably* @sci/print-readably + *print-length* @sci/print-length + *print-namespace-maps* @sci/print-namespace-maps] (pprint/pprint s writer)))) (defn cl-format - "An implementation of a Common Lisp compatible format function. cl-format formats its -arguments to an output stream or string based on the format control string given. It -supports sophisticated formatting of structured data. -Writer is an instance of java.io.Writer, true to output to *out* or nil to output -to a string, format-in is the format control string and the remaining arguments -are the data to be formatted. -The format control string is a string to be output with embedded 'format directives' -describing how to format the various arguments passed in. -If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format -returns nil. -For example: - (let [results [46 38 22]] - (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\" + "An implementation of a Common Lisp compatible format function. cl-format formats its + arguments to an output stream or string based on the format control string given. It + supports sophisticated formatting of structured data. + Writer is an instance of java.io.Writer, true to output to *out* or nil to output + to a string, format-in is the format control string and the remaining arguments + are the data to be formatted. + The format control string is a string to be output with embedded 'format directives' + describing how to format the various arguments passed in. + If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format + returns nil. + For example: + (let [results [46 38 22]] + (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\" (count results) results)) -Prints to *out*: - There are 3 results: 46, 38, 22 -Detailed documentation on format control strings is available in the \"Common Lisp the -Language, 2nd edition\", Chapter 22 (available online at: -http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000) -and in the Common Lisp HyperSpec at -http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm -" + Prints to *out*: + There are 3 results: 46, 38, 22 + Detailed documentation on format control strings is available in the \"Common Lisp the + Language, 2nd edition\", Chapter 22 (available online at: + http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000) + and in the Common Lisp HyperSpec at + http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm + " [& args] ;; bind *out* to sci/out, so with-out-str works (binding [*out* @sci/out] @@ -134,36 +137,63 @@ http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm (apply #'pprint/execute-format args))) (defn get-pretty-writer - "Returns the java.io.Writer passed in wrapped in a pretty writer proxy, unless it's -already a pretty writer. Generally, it is unnecessary to call this function, since pprint, -write, and cl-format all call it if they need to. However if you want the state to be -preserved across calls, you will want to wrap them with this. -For example, when you want to generate column-aware output with multiple calls to cl-format, -do it like in this example: + "Returns the java.io.Writer passed in wrapped in a pretty writer proxy, unless it's + already a pretty writer. Generally, it is unnecessary to call this function, since pprint, + write, and cl-format all call it if they need to. However if you want the state to be + preserved across calls, you will want to wrap them with this. + For example, when you want to generate column-aware output with multiple calls to cl-format, + do it like in this example: (defn print-table [aseq column-width] (binding [*out* (get-pretty-writer *out*)] (doseq [row aseq] (doseq [col row] (cl-format true \"~4D~7,vT\" col column-width)) (prn)))) -Now when you run: + Now when you run: user> (print-table (map #(vector % (* % %) (* % % %)) (range 1 11)) 8) -It prints a table of squares and cubes for the numbers from 1 to 10: - 1 1 1 - 2 4 8 - 3 9 27 - 4 16 64 - 5 25 125 - 6 36 216 - 7 49 343 - 8 64 512 - 9 81 729 + It prints a table of squares and cubes for the numbers from 1 to 10: + 1 1 1 + 2 4 8 + 3 9 27 + 4 16 64 + 5 25 125 + 6 36 216 + 7 49 343 + 8 64 512 + 9 81 729 10 100 1000" [writer] (binding [pprint/*print-right-margin* @print-right-margin pprint/*print-miser-width* @print-miser-width] (pprint/get-pretty-writer writer))) +(def current-length #'pprint/*current-length*) + +(defn write-out + "Write an object to *out* subject to the current bindings of the printer control + variables. Use the kw-args argument to override individual variables for this call (and + any recursive calls). + *out* must be a PrettyWriter if pretty printing is enabled. This is the responsibility + of the caller. + This method is primarily intended for use by pretty print dispatch functions that + already know that the pretty printer will have set up their environment appropriately. + Normal library clients should use the standard \"write\" interface. " + {:added "1.2"} + [object] + (let [length-reached (and + @current-length + @sci/print-length + (>= @current-length @sci/print-length))] + (if-not pprint/*print-pretty* + (pr object) + (if length-reached + (print "...") + (do + (when @current-length + (.set ^clojure.lang.Var current-length (inc @current-length))) + (print-pprint-dispatch object)))) + length-reached)) + (def pprint-namespace {'pp (sci/copy-var pprint/pp pprint-ns) 'pprint (sci/copy-var pprint pprint-ns) @@ -178,8 +208,10 @@ It prints a table of squares and cubes for the numbers from 1 to 10: 'init-navigator (sci/copy-var pprint/init-navigator pprint-ns) 'execute-format (sci/copy-var execute-format pprint-ns) 'with-pprint-dispatch (sci/copy-var pprint/with-pprint-dispatch pprint-ns) + 'code-dispatch (sci/copy-var pprint/code-dispatch pprint-ns) '*print-pprint-dispatch* print-pprint-dispatch '*print-miser-width* print-miser-width - 'get-pretty-writer (sci/copy-var get-pretty-writer pprint-ns)}) + 'get-pretty-writer (sci/copy-var get-pretty-writer pprint-ns) + 'write-out (sci/copy-var write-out pprint-ns)}) (vreset! patched? true) diff --git a/src/babashka/impl/print_deps.clj b/src/babashka/impl/print_deps.clj index e11703b5..0e9d0288 100644 --- a/src/babashka/impl/print_deps.clj +++ b/src/babashka/impl/print_deps.clj @@ -14,12 +14,14 @@ edn/read-string) deps (:deps deps) deps (assoc deps - 'babashka/fs {:mvn/version "0.1.2"} - 'babashka/babashka.curl {:mvn/version "0.1.1"} + 'babashka/fs {:mvn/version "0.2.12"} + 'babashka/babashka.curl {:mvn/version "0.1.2"} 'babashka/babashka.core {:git/url "https://github.com/babashka/babashka.core" - :git/sha "52a6037bd4b632bffffb04394fb4efd0cdab6b1e"}) + :git/sha "52a6037bd4b632bffffb04394fb4efd0cdab6b1e"} + 'babashka/process {:mvn/version "0.4.13"}) deps (dissoc deps 'borkdude/sci + 'org.babashka/sci 'borkdude/graal.locking 'org.postgresql/postgresql 'babashka/clojure-lanterna diff --git a/src/babashka/impl/process.clj b/src/babashka/impl/process.clj index 28aef471..33d77ea3 100644 --- a/src/babashka/impl/process.clj +++ b/src/babashka/impl/process.clj @@ -1,12 +1,17 @@ (ns babashka.impl.process {:no-doc true} (:require [babashka.process :as process] + [babashka.process.pprint] [sci.core :as sci :refer [copy-var]])) (def tns (sci/create-ns 'babashka.process nil)) (def defaults (sci/copy-var process/*defaults* tns)) +(defn process* [& args] + (binding [process/*defaults* @defaults] + (apply process/process* args))) + (defn process [& args] (binding [process/*defaults* @defaults] (apply process/process args))) @@ -16,7 +21,9 @@ (apply process/pb args))) (def process-namespace - {'process (copy-var process tns) + {'parse-args (copy-var process/parse-args tns) + 'process* (copy-var process/process* tns) + 'process (copy-var process tns) 'check (copy-var process/check tns) 'pb (copy-var pb tns) 'start (copy-var process/start tns) @@ -26,4 +33,7 @@ 'tokenize (copy-var process/tokenize tns) '*defaults* defaults 'destroy (copy-var process/destroy tns) - 'destroy-tree (copy-var process/destroy-tree tns)}) + 'destroy-tree (copy-var process/destroy-tree tns) + 'exec (copy-var process/exec tns) + 'shell (copy-var process/shell tns) + 'alive? (copy-var process/alive? tns)}) diff --git a/src/babashka/impl/proxy.clj b/src/babashka/impl/proxy.clj index b4341d06..f40b7ab1 100644 --- a/src/babashka/impl/proxy.clj +++ b/src/babashka/impl/proxy.clj @@ -72,7 +72,36 @@ ["javax.net.ssl.HostnameVerifier" #{}] (proxy [javax.net.ssl.HostnameVerifier] [] - (verify [host-name session] ((method-or-bust methods 'verify) this host-name session)))))) + (verify [host-name session] ((method-or-bust methods 'verify) this host-name session))) + + ["sun.misc.SignalHandler" #{}] + (proxy [sun.misc.SignalHandler] [] + (handle [sig] + ((method-or-bust methods 'handle) this sig))) + + ["java.io.PipedInputStream" #{}] + (proxy [java.io.PipedInputStream] [] + (available [] ((method-or-bust methods 'available) this)) + (close [] ((method-or-bust methods 'close) this)) + (read + ([] + ((method-or-bust methods 'read) this)) + ([bs] + ((method-or-bust methods 'read) this bs)) + ([bs off len] + ((method-or-bust methods 'read) this bs off len))) + (receive [b] ((method-or-bust methods 'receive) this b))) + + ["java.io.PipedOutputStream" #{}] + (proxy [java.io.PipedOutputStream] [] + (close [] ((method-or-bust methods 'close) this)) + (connect [snk] ((method-or-bust methods 'connect) this snk)) + (flush [] ((method-or-bust methods 'flush) this)) + (write + ([b] ((method-or-bust methods 'write) this b)) + ([b off len] ((method-or-bust methods 'write) this b off len)))) + , ;; keep this for merge friendliness + ))) (defn class-sym [c] (symbol (class-name c))) diff --git a/src/babashka/impl/reify.clj b/src/babashka/impl/reify.clj index f9f5895a..63dbf9e4 100644 --- a/src/babashka/impl/reify.clj +++ b/src/babashka/impl/reify.clj @@ -161,7 +161,9 @@ java.util.Iterator {hasNext [[this]] - next [[this]]} + next [[this]] + remove [[this]] + forEachRemaining [[this action]]} java.util.function.Function {apply [[this t]]} @@ -183,4 +185,7 @@ resolveAlias [[this sym]] resolveVar [[this sym]]} + sun.misc.SignalHandler + {handle [[this signal]]} + })) diff --git a/src/babashka/impl/repl.clj b/src/babashka/impl/repl.clj index 9cbf4d95..11dcf370 100644 --- a/src/babashka/impl/repl.clj +++ b/src/babashka/impl/repl.clj @@ -9,7 +9,7 @@ [sci.impl.interpreter :refer [eval-form]] [sci.impl.io :as sio] [sci.impl.parser :as parser] - [sci.impl.vars :as vars])) + [sci.impl.utils :as utils])) (set! *warn-on-reflection* true) @@ -63,13 +63,14 @@ (eval-form sci-ctx `(apply require (quote ~m/repl-requires))))) :read (or read (fn [_request-prompt request-exit] - (let [v (parser/parse-next sci-ctx in)] - (skip-if-eol in) - (if (or (identical? :repl/quit v) - (identical? :repl/exit v) - (identical? parser/eof v)) - request-exit - v)))) + (if (nil? (r/peek-char in)) + request-exit + (let [v (parser/parse-next sci-ctx in)] + (skip-if-eol in) + (if (or (identical? :repl/quit v) + (identical? :repl/exit v)) + request-exit + v))))) :eval (or eval (fn [expr] (sci/with-bindings {sci/file "" @@ -80,7 +81,7 @@ (let [ret (eval-form sci-ctx expr)] ret)))) :need-prompt (or need-prompt (fn [] true)) - :prompt (or prompt #(sio/printf "%s=> " (vars/current-ns-name))) + :prompt (or prompt #(sio/printf "%s=> " (utils/current-ns-name))) :flush (or flush sio/flush) :print (or print sio/prn) :caught (or caught repl-caught))))) diff --git a/src/babashka/impl/server.clj b/src/babashka/impl/server.clj index 687056a4..78af54c7 100644 --- a/src/babashka/impl/server.clj +++ b/src/babashka/impl/server.clj @@ -7,18 +7,19 @@ (def sns (sci/create-ns 'clojure.core.server nil)) (def prepl (fn [& args] - (apply server/prepl @common/ctx args))) + (apply server/prepl (common/ctx) args))) (def io-prepl (fn [& args] - (apply server/io-prepl @common/ctx args))) + (apply server/io-prepl (common/ctx) args))) (def start-server (fn [& args] - (apply server/start-server @common/ctx args))) + (apply server/start-server (common/ctx) args))) (def clojure-core-server-namespace {'repl (sci/copy-var socket-repl/repl sns) 'prepl (sci/copy-var prepl sns) 'io-prepl (sci/copy-var io-prepl sns) - 'start-server (sci/copy-var start-server sns)}) + 'start-server (sci/copy-var start-server sns) + 'stop-server (sci/copy-var server/stop-server sns)}) diff --git a/src/babashka/impl/sigint_handler.clj b/src/babashka/impl/sigint_handler.clj index 508e01b2..49903518 100644 --- a/src/babashka/impl/sigint_handler.clj +++ b/src/babashka/impl/sigint_handler.clj @@ -12,4 +12,4 @@ (reify SignalHandler (handle [_ _] ;; This is needed to run shutdown hooks on interrupt, System/exit triggers those - (System/exit 0)))))) + (System/exit 130)))))) diff --git a/src/babashka/impl/socket_repl.clj b/src/babashka/impl/socket_repl.clj index b5532fec..55ed2ca9 100644 --- a/src/babashka/impl/socket_repl.clj +++ b/src/babashka/impl/socket_repl.clj @@ -11,7 +11,7 @@ ;; this is mapped to clojure.core.server/repl in babashka.main (def repl (fn [] - (repl/repl @common/ctx))) + (repl/repl (common/ctx)))) (defn parse-opts [opts] (let [opts (str/trim opts) diff --git a/src/babashka/impl/tasks.clj b/src/babashka/impl/tasks.clj index 2b376f0d..f2775b8f 100644 --- a/src/babashka/impl/tasks.clj +++ b/src/babashka/impl/tasks.clj @@ -1,15 +1,19 @@ (ns babashka.impl.tasks - (:require [babashka.deps :as deps] - [babashka.impl.common :refer [ctx bb-edn debug]] - [babashka.process :as p] - [clojure.core.async :refer [> (key-order raw-edn) + (map str) + (remove #(str/starts-with? % "-")) + (remove #(:private (get tasks (symbol %))))))] + (if (seq names) + (let [longest (apply max (map count names)) fmt (str "%1$-" longest "s")] (println "The following tasks are available:") (println) @@ -448,7 +438,15 @@ ([task {:keys [:parallel] :or {parallel (:parallel (current-task))}}] (let [[[expr]] (assemble-task task parallel)] - (sci/eval-string* @ctx expr)))) + (sci/eval-string* (ctx) expr)))) + +(defn exec + ([sym] + (let [snippet (cli/exec-fn-snippet sym)] + (sci/eval-string* (ctx) snippet))) + ([sym extra-opts] + (let [snippet (cli/exec-fn-snippet sym extra-opts)] + (sci/eval-string* (ctx) snippet)))) (def tasks-namespace {'shell (sci/copy-var shell sci-ns) @@ -460,4 +458,5 @@ 'current-task current-task 'current-state state 'run (sci/copy-var run sci-ns) + 'exec (sci/copy-var exec sci-ns) #_#_'log log}) diff --git a/src/babashka/impl/test.clj b/src/babashka/impl/test.clj index 62b1b494..4566b4ce 100644 --- a/src/babashka/impl/test.clj +++ b/src/babashka/impl/test.clj @@ -5,7 +5,7 @@ (defn contextualize [f] (fn [& args] - (apply f @ctx args))) + (apply f (ctx) args))) (def tns t/tns) @@ -55,6 +55,8 @@ 'test-ns (new-var 'test-ns (contextualize t/test-ns)) ;; running tests: high level 'run-tests (new-var 'run-tests (contextualize t/run-tests)) + 'run-test-var (sci/copy-var t/run-test-var tns) + 'run-test (sci/copy-var t/run-test tns) 'run-all-tests (new-var 'run-all-tests (contextualize t/run-all-tests)) 'successful? (sci/copy-var t/successful? tns) 'with-test-out (sci/copy-var t/with-test-out tns)}) diff --git a/src/babashka/impl/tools/cli.clj b/src/babashka/impl/tools/cli.clj index 10f4045b..f1db19c1 100644 --- a/src/babashka/impl/tools/cli.clj +++ b/src/babashka/impl/tools/cli.clj @@ -1,10 +1,9 @@ (ns babashka.impl.tools.cli {:no-doc true} (:require [clojure.tools.cli :as tools.cli] - [sci.impl.namespaces :refer [copy-var]] - [sci.impl.vars :as vars])) + [sci.core :as sci :refer [copy-var]])) -(def cli-ns (vars/->SciNamespace 'clojure.tools.cli nil)) +(def cli-ns (sci/create-ns 'clojure.tools.cli nil)) (def tools-cli-namespace {'format-lines (copy-var tools.cli/format-lines cli-ns) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index a899e8c5..99ff3bbc 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -7,10 +7,12 @@ [babashka.fs :as fs] [babashka.impl.bencode :refer [bencode-namespace]] [babashka.impl.cheshire :refer [cheshire-core-namespace]] - [babashka.impl.classes :as classes] + [babashka.impl.classes :as classes :refer [classes-namespace]] [babashka.impl.classpath :as cp :refer [classpath-namespace]] + [babashka.impl.cli :as cli] [babashka.impl.clojure.core :as core :refer [core-extras]] - [babashka.impl.clojure.core.async :refer [async-namespace async-protocols-namespace]] + [babashka.impl.clojure.core.async :refer [async-namespace + async-protocols-namespace]] [babashka.impl.clojure.instant :as instant] [babashka.impl.clojure.java.browse :refer [browse-namespace]] [babashka.impl.clojure.java.io :refer [io-namespace]] @@ -18,7 +20,9 @@ [babashka.impl.clojure.main :as clojure-main :refer [demunge]] [babashka.impl.clojure.math :refer [math-namespace]] [babashka.impl.clojure.stacktrace :refer [stacktrace-namespace]] - [babashka.impl.clojure.tools.reader-types :refer [edn-namespace reader-types-namespace]] + [babashka.impl.clojure.tools.reader :refer [reader-namespace]] + [babashka.impl.clojure.tools.reader-types :refer [edn-namespace + reader-types-namespace]] [babashka.impl.clojure.zip :refer [zip-namespace]] [babashka.impl.common :as common] [babashka.impl.core :as bbcore] @@ -26,16 +30,18 @@ [babashka.impl.data :as data] [babashka.impl.datafy :refer [datafy-namespace]] [babashka.impl.deps :as deps :refer [deps-namespace]] + [babashka.impl.edamame :refer [edamame-namespace]] [babashka.impl.error-handler :refer [error-handler]] [babashka.impl.features :as features] [babashka.impl.fs :refer [fs-namespace]] + [babashka.impl.nrepl-server :refer [nrepl-server-namespace]] [babashka.impl.pods :as pods] [babashka.impl.pprint :refer [pprint-namespace]] [babashka.impl.print-deps :as print-deps] [babashka.impl.process :refer [process-namespace]] [babashka.impl.protocols :refer [protocols-namespace]] [babashka.impl.proxy :refer [proxy-fn]] - [babashka.impl.reify :refer [reify-fn]] + [babashka.impl.reify2 :refer [reify-fn]] [babashka.impl.repl :as repl] [babashka.impl.rewrite-clj :as rewrite] [babashka.impl.server :refer [clojure-core-server-namespace]] @@ -52,11 +58,13 @@ [hf.depstar.uberjar :as uberjar] [sci.addons :as addons] [sci.core :as sci] + [sci.ctx-store :as ctx-store] + [sci.impl.copy-vars :as sci-copy-vars] + [sci.impl.io :as sio] [sci.impl.namespaces :as sci-namespaces] [sci.impl.types :as sci-types] [sci.impl.unrestrict :refer [*unrestricted*]] - [sci.impl.vars :as vars] - [sci.impl.io :as sio]) + [sci.impl.vars :as vars]) (:gen-class)) (def windows? @@ -81,16 +89,19 @@ (def signal-ns {'pipe-signal-received? (sci/copy-var pipe-signal-received? (sci/create-ns 'babashka.signal nil))}) +(sci/enable-unrestricted-access!) (sci/alter-var-root sci/in (constantly *in*)) (sci/alter-var-root sci/out (constantly *out*)) (sci/alter-var-root sci/err (constantly *err*)) +(sci/alter-var-root sci/read-eval (constantly *read-eval*)) (set! *warn-on-reflection* true) ;; To detect problems when generating the image, run: ;; echo '1' | java -agentlib:native-image-agent=config-output-dir=/tmp -jar target/babashka-xxx-standalone.jar '...' ;; with the java provided by GraalVM. -(def version (str/trim (slurp (io/resource "BABASHKA_VERSION")))) +(def version common/version) + (defn parse-version [version] (mapv #(Integer/parseInt %) (-> version @@ -115,7 +126,8 @@ "socket-repl" "nrepl-server" "describe" - "print-deps") true + "print-deps" + "prepare") true false)) (defn print-error [& msgs] @@ -138,10 +150,11 @@ Global opts: -cp, --classpath Classpath to use. Overrides bb.edn classpath. --debug Print debug information and internal stacktrace in case of exception. - --force Passes -Sforce to deps.clj, forcing recalculation of the classpath. --init Load file after any preloads and prior to evaluation/subcommands. --config Replacing bb.edn with file. Relative paths are resolved relative to file. --deps-root Treat dir as root of relative paths in config. + -Sforce Force recalculation of the classpath (don't use the cache) + -Sdeps Deps data to use as the last deps file to be merged Help: @@ -155,6 +168,7 @@ Evaluation: -e, --eval Evaluate an expression. -f, --file Evaluate a file. -m, --main Call the -main function from a namespace or call a fully qualified var. + -x, --exec Call the fully qualified var. Args are parsed by babashka CLI. REPL: @@ -175,6 +189,7 @@ Packaging: uberscript [eval-opt] Collect all required namespaces from the classpath into a single file. Accepts additional eval opts, like `-m`. uberjar [eval-opt] Similar to uberscript but creates jar file. + prepare Download deps & pods defined in bb.edn and cache their metadata. Only an optimization, this will happen on demand when needed. In- and output flags (only to be used with -e one-liners): @@ -244,6 +259,7 @@ Use bb run --help to show this help output. :feature/yaml %s :feature/jdbc %s :feature/postgresql %s + :feature/sqlite %s :feature/hsqldb %s :feature/oracledb %s :feature/httpkit-client %s @@ -262,8 +278,8 @@ Use bb run --help to show this help output. features/xml? features/yaml? features/jdbc? - features/sqlite? features/postgresql? + features/sqlite? features/hsqldb? features/oracledb? features/httpkit-client? @@ -289,27 +305,24 @@ Use bb run --help to show this help output. s (slurp f)] (sci/with-bindings {sci/ns @sci/ns sci/file (.getAbsolutePath f)} - (sci/eval-string* @common/ctx s)))) + (sci/eval-string* (common/ctx) s)))) (defn start-socket-repl! [address ctx] (socket-repl/start-repl! address ctx)) -(defn start-nrepl! [address ctx] - (let [dev? (= "true" (System/getenv "BABASHKA_DEV")) - nrepl-opts (nrepl-server/parse-opt address) - nrepl-opts (assoc nrepl-opts - :debug dev? - :describe {"versions" {"babashka" version}} - :thread-bind [core/warn-on-reflection])] - (nrepl-server/start-server! ctx nrepl-opts) - (binding [*out* *err*] - (println "For more info visit: https://book.babashka.org/#_nrepl"))) +(defn start-nrepl! [address] + (let [opts (nrepl-server/parse-opt address)] + (babashka.impl.nrepl-server/start-server! opts)) + (binding [*out* *err*] + (println "For more info visit: https://book.babashka.org/#_nrepl")) ;; hang until SIGINT @(promise)) (def aliases (cond-> - '{tools.cli clojure.tools.cli + '{str clojure.string + set clojure.set + tools.cli clojure.tools.cli edn clojure.edn wait babashka.wait signal babashka.signal @@ -335,6 +348,11 @@ Use bb run --help to show this help output. (require 'babashka.impl.instaparse) +(defn catvec [& xs] + (into [] cat xs)) + +(def sci-ns (sci/create-ns 'sci.core)) + (def namespaces (cond-> {'user {'*input* (reify @@ -344,6 +362,7 @@ Use bb run --help to show this help output. 'clojure.tools.cli tools-cli-namespace 'clojure.java.shell shell-namespace 'babashka.core bbcore/core-namespace + 'babashka.nrepl.server nrepl-server-namespace 'babashka.wait wait-namespace 'babashka.signal signal-ns 'clojure.java.io io-namespace @@ -358,10 +377,12 @@ Use bb run --help to show this help output. 'repl (sci/new-var 'repl (fn [& opts] (let [opts (apply hash-map opts)] - (repl/start-repl! @common/ctx opts))) {:ns clojure-main-ns})} + (repl/start-repl! (common/ctx) opts))) {:ns clojure-main-ns}) + 'with-bindings (sci/copy-var clojure-main/with-bindings clojure-main-ns)} 'clojure.test t/clojure-test-namespace 'clojure.math math-namespace 'babashka.classpath classpath-namespace + 'babashka.classes classes-namespace 'clojure.pprint pprint-namespace 'babashka.curl curl-namespace 'babashka.fs fs-namespace @@ -376,13 +397,28 @@ Use bb run --help to show this help output. 'babashka.tasks tasks-namespace 'clojure.tools.reader.edn edn-namespace 'clojure.tools.reader.reader-types reader-types-namespace + 'clojure.tools.reader reader-namespace 'clojure.core.async async-namespace 'clojure.core.async.impl.protocols async-protocols-namespace 'rewrite-clj.node rewrite/node-namespace 'rewrite-clj.paredit rewrite/paredit-namespace 'rewrite-clj.parser rewrite/parser-namespace 'rewrite-clj.zip rewrite/zip-namespace - 'rewrite-clj.zip.subedit rewrite/subedit-namespace} + 'rewrite-clj.zip.subedit rewrite/subedit-namespace + 'clojure.core.rrb-vector (if features/rrb-vector? + @(resolve 'babashka.impl.rrb-vector/rrb-vector-namespace) + {'catvec (sci/copy-var catvec + (sci/create-ns 'clojure.core.rrb-vector))}) + 'edamame.core edamame-namespace + 'sci.core {'format-stacktrace (sci/copy-var sci/format-stacktrace sci-ns) + 'stacktrace (sci/copy-var sci/stacktrace sci-ns) + ;; 'eval-string (sci/copy-var sci/eval-string sci-ns) + ;; 'eval-string* (sci/copy-var sci/eval-string* sci-ns) + ;; 'init (sci/copy-var sci/init sci-ns) + ;; 'fork (sci/copy-var sci/fork sci-ns) + } + 'babashka.cli cli/cli-namespace + } features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace) 'clojure.data.xml.event @(resolve 'babashka.impl.xml/xml-event-namespace) 'clojure.data.xml.tree @(resolve 'babashka.impl.xml/xml-tree-namespace)) @@ -524,9 +560,6 @@ Use bb run --help to show this help output. ("--verbose") (recur (next options) (assoc opts-map :verbose? true)) - ("--force") (recur (next options) - (assoc opts-map - :force? true)) ("--describe") (recur (next options) (assoc opts-map :describe? true)) @@ -620,6 +653,12 @@ Use bb run --help to show this help output. :command-line-args (if (= "--" (second options)) (nthrest options 2) (rest options)))) + ("--exec", "-x",) + (let [options (next options)] + (assoc opts-map :exec (first options) + :command-line-args (if (= "--" (second options)) + (nthrest options 2) + (rest options)))) ("--run") (parse-run-opts opts-map (next options)) ("--tasks") @@ -627,6 +666,10 @@ Use bb run --help to show this help output. :command-line-args (next options)) ("--print-deps") (parse-print-deps-opts opts-map (next options)) + ("--prepare") + (let [options (next options)] + (recur (next options) + (assoc opts-map :prepare true))) ;; fallback (if (and opts-map (some opts-map [:file :jar :socket-repl :expressions :main :run])) @@ -661,6 +704,12 @@ Use bb run --help to show this help output. ("--init") (recur (nnext options) (assoc opts-map :init (second options))) + ("--force" "-Sforce") + (recur (next options) (assoc opts-map :force? true)) + + ("-Sdeps") + (recur (nnext options) (assoc opts-map :merge-deps (second options))) + ("--config") (recur (nnext options) (assoc opts-map :config (second options))) @@ -669,6 +718,15 @@ Use bb run --help to show this help output. [options opts-map]) [options opts-map]))) +(defn parse-file-opt + [options opts-map] + (let [opt (first options) + opts-key (if (str/ends-with? opt ".jar") + :jar :file)] + (assoc opts-map + opts-key opt + :command-line-args (next options)))) + (defn parse-opts ([options] (parse-opts options nil)) ([options opts-map] @@ -678,29 +736,51 @@ Use bb run --help to show this help output. ;; FILE > TASK > SUBCOMMAND (cond (.isFile (io/file opt)) - (if (str/ends-with? opt ".jar") - (assoc opts-map - :jar opt - :command-line-args (next options)) - (assoc opts-map - :file opt - :command-line-args (next options))) + (if (or (:file opts-map) (:jar opts-map)) + opts-map ; we've already parsed the file opt + (parse-file-opt options opts-map)) + (contains? tasks opt) (assoc opts-map :run opt :command-line-args (next options)) + (command? opt) (recur (cons (str "--" opt) (next options)) opts-map) + :else (parse-args options opts-map)))))) (def env (atom {})) +(def pod-namespaces (volatile! {})) + +(defn download-only? + "If we're preparing pods for another OS / arch, don't try to run them." + [] + (let [env-os-name (System/getenv "BABASHKA_PODS_OS_NAME") + env-os-name-present? (not (str/blank? env-os-name)) + sys-os-name (System/getProperty "os.name") + env-os-arch (System/getenv "BABASHKA_PODS_OS_ARCH") + env-os-arch-present? (not (str/blank? env-os-arch)) + sys-os-arch (System/getProperty "os.arch")] + (when @common/debug + (binding [*out* *err*] + (println "System OS name:" sys-os-name) + (when env-os-name-present? (println "BABASHKA_PODS_OS_NAME:" env-os-name)) + (println "System OS arch:" sys-os-arch) + (when env-os-arch-present? (println "BABASHKA_PODS_OS_ARCH:" env-os-arch)))) + (cond + env-os-name-present? (not= env-os-name sys-os-name) + env-os-arch-present? (not= env-os-arch sys-os-arch)))) + (defn exec [cli-opts] (binding [*unrestricted* true] (sci/binding [core/warn-on-reflection @core/warn-on-reflection + core/unchecked-math @core/unchecked-math core/data-readers @core/data-readers - sci/ns @sci/ns] + sci/ns @sci/ns + sci/print-length @sci/print-length] (let [{version-opt :version :keys [:shell-in :edn-in :shell-out :edn-out :help :file :command-line-args @@ -710,11 +790,13 @@ Use bb run --help to show this help output. :main :uberscript :describe? :jar :uberjar :clojure :doc :run :list-tasks - :print-deps]} + :print-deps :prepare] + exec-fn :exec} cli-opts _ (when debug (vreset! common/debug true)) _ (do ;; set properties (when main (System/setProperty "babashka.main" main)) + ;; TODO: what about exec here? (System/setProperty "babashka.version" version)) read-next (fn [*in*] (if (pipe-signal-received?) @@ -746,29 +828,51 @@ Use bb run --help to show this help output. _ (when jar (cp/add-classpath jar)) load-fn (fn [{:keys [:namespace :reload]}] - (or (when-let [{:keys [:loader]} - @cp/cp-state] - (if ;; ignore built-in namespaces when uberscripting, unless with :reload - (and uberscript - (not reload) - (or (contains? namespaces namespace) - (contains? sci-namespaces/namespaces namespace))) - "" - (when-let [res (cp/source-for-namespace loader namespace nil)] - (if uberscript - (do (swap! uberscript-sources conj (:source res)) - (uberscript/uberscript {:ctx @common/ctx - :expressions [(:source res)]}) - {}) - res)))) - (case namespace - clojure.spec.alpha - (binding [*out* *err*] - (println "[babashka] WARNING: Use the babashka-compatible version of clojure.spec.alpha, available here: https://github.com/babashka/spec.alpha")) - clojure.core.specs.alpha - (binding [*out* *err*] - (println "[babashka] WARNING: clojure.core.specs.alpha is removed from the classpath, unless you explicitly add the dependency.")) - nil))) + (let [{:keys [loader]} + @cp/cp-state] + (or + (when ;; ignore built-in namespaces when uberscripting, unless with :reload + (and uberscript + (not reload) + (or (contains? namespaces namespace) + (contains? sci-namespaces/namespaces namespace))) + "") + ;; pod namespaces go before namespaces from source, + ;; unless reload is used + (when-not reload + (when-let [pod (get @pod-namespaces namespace)] + (if uberscript + (do + (swap! uberscript-sources conj + (format + "(babashka.pods/load-pod '%s \"%s\" '%s)\n" + (:pod-spec pod) (:version (:opts pod)) + (dissoc (:opts pod) + :version :metadata))) + {}) + (pods/load-pod (:pod-spec pod) (:opts pod))))) + (when loader + (when-let [res (cp/source-for-namespace loader namespace nil)] + (if uberscript + (do (swap! uberscript-sources conj (:source res)) + (uberscript/uberscript {:ctx (common/ctx) + :expressions [(:source res)]}) + {}) + res))) + (let [rps (cp/resource-paths namespace) + rps (mapv #(str "src/babashka/" %) rps)] + (when-let [url (some #(io/resource %) rps)] + (let [source (slurp url)] + {:file (str url) + :source source}))) + (case namespace + clojure.spec.alpha + (binding [*out* *err*] + (println "[babashka] WARNING: Use the babashka-compatible version of clojure.spec.alpha, available here: https://github.com/babashka/spec.alpha")) + clojure.core.specs.alpha + (binding [*out* *err*] + (println "[babashka] WARNING: clojure.core.specs.alpha is removed from the classpath, unless you explicitly add the dependency.")) + nil)))) main (if (and jar (not main)) (when-let [res (cp/getResource (cp/loader jar) @@ -780,10 +884,10 @@ Use bb run --help to show this help output. :namespaces (-> namespaces (assoc 'clojure.core (assoc core-extras - 'load-file (sci-namespaces/core-var 'load-file load-file*)))) + 'load-file (sci-copy-vars/new-var 'load-file load-file*)))) :env env :features #{:bb :clj} - :classes classes/class-map + :classes @classes/class-map :imports classes/imports :load-fn load-fn :uberscript uberscript @@ -792,7 +896,11 @@ Use bb run --help to show this help output. :proxy-fn proxy-fn} opts (addons/future opts) sci-ctx (sci/init opts) - _ (vreset! common/ctx sci-ctx) + _ (ctx-store/reset-ctx! sci-ctx) + _ (when-let [pods (:pods @common/bb-edn)] + (when-let [pod-metadata (pods/load-pods-metadata + pods {:download-only (download-only?)})] + (vreset! pod-namespaces pod-metadata))) preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim)) [expressions exit-code] (cond expressions [expressions nil] @@ -805,6 +913,9 @@ Use bb run --help to show this help output. "-main")] [[(format "(ns user (:require [%1$s])) (apply %1$s/%2$s *command-line-args*)" ns var-name)] nil]) + exec-fn + (let [sym (symbol exec-fn)] + [[(cli/exec-fn-snippet sym)] nil]) run (if (:run-help cli-opts) [(print-run-help) 0] (do @@ -860,10 +971,11 @@ Use bb run --help to show this help output. describe? [(print-describe) 0] repl [(repl/start-repl! sci-ctx) 0] - nrepl [(start-nrepl! nrepl sci-ctx) 0] + nrepl [(start-nrepl! nrepl) 0] uberjar [nil 0] list-tasks [(tasks/list-tasks sci-ctx) 0] print-deps [(print-deps/print-deps (:print-deps-format cli-opts)) 0] + prepare [nil 0] uberscript [nil (do (uberscript/uberscript {:ctx sci-ctx :expressions expressions}) @@ -872,8 +984,8 @@ Use bb run --help to show this help output. ;; execute code (sci/binding [sci/file abs-path] (try - ; when evaluating expression(s), add in repl-requires so things like - ; pprint and dir are available + ;; when evaluating expression(s), add in repl-requires so things like + ;; pprint and dir are available (sci/eval-form sci-ctx `(apply require (quote ~clojure-main/repl-requires))) (loop [] (let [in (read-next *in*)] @@ -919,11 +1031,20 @@ Use bb run --help to show this help output. (spit uberscript-out expression :append true))) (when uberjar (if-let [cp (cp/get-classpath)] - (uberjar/run {:dest uberjar - :jar :uber - :classpath cp - :main-class main - :verbose debug}) + (let [uber-params {:dest uberjar + :jar :uber + :classpath cp + :main-class main + :verbose debug}] + (if-let [bb-edn-pods (:pods @common/bb-edn)] + (fs/with-temp-dir [bb-edn-dir {}] + (let [bb-edn-resource (fs/file bb-edn-dir "META-INF" "bb.edn")] + (fs/create-dirs (fs/parent bb-edn-resource)) + (->> {:pods bb-edn-pods} pr-str (spit bb-edn-resource)) + (let [cp-with-bb-edn (str bb-edn-dir cp/path-sep cp)] + (uberjar/run (assoc uber-params + :classpath cp-with-bb-edn))))) + (uberjar/run uber-params))) (throw (Exception. "The uberjar task needs a classpath.")))) exit-code)))) @@ -936,16 +1057,33 @@ Use bb run --help to show this help output. (and (= minor-current minor-min) (>= patch-current patch-min))))))) +(defn load-bb-edn [string] + (try (edn/read-string {:default tagged-literal} string) + (catch java.lang.RuntimeException e + (if (re-find #"No dispatch macro for: \"" (.getMessage e)) + (throw (ex-info "Invalid regex literal found in EDN config, use re-pattern instead" {})) + (do (binding [*out* *err*] + (println "Error during loading bb.edn:")) + (throw e)))))) + (defn main [& args] (let [[args global-opts] (parse-global-opts args) + {:keys [:jar] :as file-opt} (when (some-> args first io/file .isFile) + (parse-file-opt args global-opts)) config (:config global-opts) - bb-edn-file (or config - "bb.edn") - bb-edn (when (fs/exists? bb-edn-file) - (System/setProperty "babashka.config" - (.getAbsolutePath (io/file bb-edn-file))) - (let [raw-string (slurp bb-edn-file) - edn (edn/read-string raw-string) + merge-deps (:merge-deps global-opts) + abs-path #(-> % io/file .getAbsolutePath) + bb-edn-file (cond + config (when (fs/exists? config) (abs-path config)) + jar (some-> jar cp/loader (cp/resource "META-INF/bb.edn") .toString) + :else (when (fs/exists? "bb.edn") (abs-path "bb.edn"))) + bb-edn (when (or bb-edn-file merge-deps) + (when bb-edn-file (System/setProperty "babashka.config" bb-edn-file)) + (let [raw-string (when bb-edn-file (slurp bb-edn-file)) + edn (when bb-edn-file (load-bb-edn raw-string)) + edn (if merge-deps + (deps/merge-deps [edn (load-bb-edn merge-deps)]) + edn) edn (assoc edn :raw raw-string :file bb-edn-file) @@ -954,13 +1092,14 @@ Use bb run --help to show this help output. (assoc edn :deps-root deps-root) edn)] (vreset! common/bb-edn edn))) + ;; _ (.println System/err (str bb-edn)) min-bb-version (:min-bb-version bb-edn)] (when min-bb-version (when-not (satisfies-min-version? min-bb-version) (binding [*out* *err*] (println (str "WARNING: this project requires babashka " min-bb-version " or newer, but you have: " version))))) - (exec (parse-opts args global-opts)))) + (exec (parse-opts args (merge global-opts file-opt))))) (def musl? "Captured at compile time, to know if we are running inside a diff --git a/src/babashka/wait.clj b/src/babashka/wait.clj index 5cbea999..d8c4fe69 100644 --- a/src/babashka/wait.clj +++ b/src/babashka/wait.clj @@ -26,8 +26,9 @@ :wait-for-port.impl/timed-out :wait-for-port.impl/try-again))))] (cond (identical? :wait-for-port.impl/try-again v) - (do (Thread/sleep (or pause 100)) - (recur)) + (let [^long pause (or pause 100)] + (Thread/sleep pause) + (recur)) (identical? :wait-for-port.impl/timed-out v) default :else @@ -51,8 +52,9 @@ :wait-for-path.impl/timed-out :wait-for-path.impl/try-again)))] (cond (identical? :wait-for-path.impl/try-again v) - (do (Thread/sleep (or pause 100)) - (recur)) + (let [^long pause (or pause 100)] + (Thread/sleep pause) + (recur)) (identical? :wait-for-path.impl/timed-out v) default :else diff --git a/test-resources/babashka/exec_test.clj b/test-resources/babashka/exec_test.clj new file mode 100644 index 00000000..a18676bf --- /dev/null +++ b/test-resources/babashka/exec_test.clj @@ -0,0 +1,9 @@ +(ns babashka.exec-test + {:org.babashka/cli {:coerce {:foo []}}}) + +(defn exec-test + {:org.babashka/cli {:coerce {:bar :keyword}}} + [m] + (if (:meta m) + (prn (meta m)) + (prn m))) diff --git a/test-resources/babashka/uberjar/src/my/main_pod.clj b/test-resources/babashka/uberjar/src/my/main_pod.clj new file mode 100644 index 00000000..a3a0e46f --- /dev/null +++ b/test-resources/babashka/uberjar/src/my/main_pod.clj @@ -0,0 +1,5 @@ +(ns my.main-pod + (:require [pod.babashka.go-sqlite3 :as sqlite])) + +(defn -main [& _args] + (sqlite/query ":memory:" ["SELECT 1 + 2 as sum"])) diff --git a/test-resources/babashka/uberscript/src/my/main_pod.clj b/test-resources/babashka/uberscript/src/my/main_pod.clj new file mode 100644 index 00000000..139b5e48 --- /dev/null +++ b/test-resources/babashka/uberscript/src/my/main_pod.clj @@ -0,0 +1,6 @@ +(ns my.main-pod + (:require [my.other-ns-with-pod] + [pod.babashka.go-sqlite3 :as sqlite])) + +(defn -main [& _args] + (sqlite/query ":memory:" ["SELECT 1 + 2 as sum"])) diff --git a/test-resources/babashka/uberscript/src/my/other_ns_with_pod.clj b/test-resources/babashka/uberscript/src/my/other_ns_with_pod.clj new file mode 100644 index 00000000..b8f061ba --- /dev/null +++ b/test-resources/babashka/uberscript/src/my/other_ns_with_pod.clj @@ -0,0 +1,2 @@ +(ns my.other-ns-with-pod + (:require [pod.babashka.go-sqlite3])) diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index 56fcda74..092263f8 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -1,10 +1,17 @@ (ns babashka.run-all-libtests - (:require [babashka.core :refer [windows?]] - [clojure.edn :as edn] - [clojure.java.io :as io] - [clojure.test :as t :refer [*report-counters*]])) + (:require + [babashka.classpath :as cp :refer [add-classpath]] + [babashka.core :refer [windows?]] + [babashka.fs :as fs] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.spec.test.alpha :as st] + [clojure.string :as str] + [clojure.test :as t :refer [*report-counters*]])) -(defmethod clojure.test/report :end-test-var [_m] +(def orig-spec-checking-fn @#'st/spec-checking-fn) + +(defmethod t/report :end-test-var [_m] (when-let [rc *report-counters*] (let [{:keys [:fail :error]} @rc] (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST")) @@ -32,19 +39,33 @@ (defn test-namespaces [& namespaces] (let [namespaces (seq (filter test-namespace? namespaces))] (when (seq namespaces) - (doseq [n namespaces] - (require n) - (filter-vars! (find-ns n) #(-> % meta ((some-fn :skip-bb - :test-check-slow)) not))) - (let [m (apply t/run-tests namespaces)] - (swap! status (fn [status] - (merge-with + status (dissoc m :type)))))))) + (let [namespaces namespaces] + (doseq [n namespaces] + (let [orchestra? (str/starts-with? (str n) "orchestra")] + (if orchestra? + nil ;; (alter-var-root #'st/spec-checking-fn (constantly ot/spec-checking-fn)) + (alter-var-root #'st/spec-checking-fn (constantly orig-spec-checking-fn))) + (when-not orchestra? + (require n) + (filter-vars! (find-ns n) #(-> % meta ((some-fn :skip-bb + :test-check-slow)) not)) + (let [m (apply t/run-tests [n])] + (swap! status (fn [status] + (merge-with + status (dissoc m :type)))))))))))) ;; Standard test-runner for libtests -(let [lib-tests (edn/read-string (slurp (io/resource "bb-tested-libs.edn")))] - (doseq [{tns :test-namespaces skip-windows :skip-windows} (vals lib-tests)] +(let [lib-tests (edn/read-string (slurp (io/resource "bb-tested-libs.edn"))) + test-nss (atom [])] + (doseq [[libname {tns :test-namespaces skip-windows :skip-windows + :keys [test-paths + git-sha]}] lib-tests] + (let [git-dir (format ".gitlibs/libs/%s/%s" libname git-sha) + git-dir (fs/file (fs/home) git-dir)] + (doseq [p test-paths] + (add-classpath (str (fs/file git-dir p))))) (when-not (and skip-windows (windows?)) - (apply test-namespaces tns)))) + (swap! test-nss into tns))) + (apply test-namespaces @test-nss)) ;; Non-standard tests - These are tests with unusual setup around test-namespaces diff --git a/test-resources/lib_tests/bb-tested-libs.edn b/test-resources/lib_tests/bb-tested-libs.edn index 9297d2a2..d549dfa2 100644 --- a/test-resources/lib_tests/bb-tested-libs.edn +++ b/test-resources/lib_tests/bb-tested-libs.edn @@ -20,8 +20,6 @@ ;; BB-TEST-PATCH: Removed markdown.md-file-test b/c tests hardcode path to test ;; files. Removed markdown.benchmark b/c it depends on criterium which isn't bb compatible markdown-clj/markdown-clj {:git-sha "ac245d3049afa25a6d41fcb5ba5a268f52c610e4", :git-url "https://github.com/yogthos/markdown-clj", :test-namespaces (markdown.md-test)} - ;; BB-TEST-PATCH: Removed clojure.tools.namespace.dir-test as it fails on windows - org.clojure/tools.namespace {:git-sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b", :git-url "https://github.com/babashka/tools.namespace", :test-namespaces (clojure.tools.namespace.test-helpers clojure.tools.namespace.dependency-test clojure.tools.namespace.find-test clojure.tools.namespace.move-test clojure.tools.namespace.parse-test), :branch "babashka"} com.stuartsierra/component {:git-sha "9f9653d1d95644e3c30beadf8c8811f86758ea23", :git-url "https://github.com/stuartsierra/component", :test-namespaces (com.stuartsierra.component-test)} slingshot/slingshot {:git-sha "6961ab0593ab9633c15b7697ffd43823090720be", :git-url "https://github.com/scgilardi/slingshot", :test-namespaces (slingshot.slingshot-test slingshot.support-test slingshot.test-test)} rm-hull/jasentaa {:git-sha "f52a0e75cbdf1d2b72d9604232db264ff6473f12", :git-url "https://github.com/rm-hull/jasentaa", :test-namespaces (jasentaa.position-test jasentaa.worked-example-2 jasentaa.collections-test jasentaa.parser.basic-test jasentaa.parser.combinators-test jasentaa.test-helpers jasentaa.worked-example-1)} @@ -47,7 +45,7 @@ doric/doric {:git-sha "8747fdce565187a5c368c575cf4ca794084b0a5c", :git-url "https://github.com/joegallo/doric", :test-namespaces (doric.test.core doric.test.readme doric.test.doctest)} com.github.seancorfield/honeysql {:git-sha "6e4e1f6928450788353c181f32474d930d6afe84", :git-url "https://github.com/seancorfield/honeysql", :test-namespaces (honey.sql-test honey.sql.helpers-test honey.sql.postgres-test), :branch "develop"} honeysql/honeysql {:git-sha "1137dd12350afdc30ad4976c3718279581390b36", :git-url "https://github.com/seancorfield/honeysql", :test-namespaces (honeysql.format-test honeysql.core-test), :branch "v1"} - ; skip tests on Windows because of the :compressed thing + ; skip tests on Windows because of the :compressed thing babashka/babashka.curl {:git-url "https://github.com/babashka/babashka.curl", :test-namespaces [babashka.curl-test], :skip-windows true, :manually-added true} http-kit/http-kit {:git-url "https://github.com/http-kit/http-kit", :test-namespaces [httpkit.client-test], :manually-added true} org.clojure/core.match {:git-url "https://github.com/clojure/core.match", :test-namespaces [core-match.core-tests], :manually-added true} @@ -71,7 +69,6 @@ cprop/cprop {:git-url "https://github.com/tolitius/cprop", :test-namespaces [cprop.smoke-test], :manually-added true} org.clojure/data.zip {:git-url "https://github.com/clojure/data.zip", :test-namespaces [clojure.data.zip-test], :manually-added true} borkdude/deps {:git-url "https://github.com/borkdude/deps.clj", :test-namespaces [borkdude.deps.smoke-test], :manually-added true} - io.github.cognitect-labs/test-runner {:git-url "https://github.com/cognitect-labs/test-runner", :test-namespaces (cognitect.test-runner.samples-test cognitect.test-runner.sample-property-test cognitect.test-runner-test), :git-sha "cc75980b43011773162b485f46f939dc5fba91e4"} borkdude/missing.test.assertions {:git-url "https://github.com/borkdude/missing.test.assertions", :test-namespaces (missing.test.assertions-test missing.test.old-methods), :git-sha "603cb01bee72fb17addacc53c34c85612684ad70"} ;; No tests to run io.github.technomancy/limit-break {:git-url "https://github.com/technomancy/limit-break", :test-namespaces [], :manually-added true} @@ -79,7 +76,7 @@ ;; BB-TEST-PATCH: Deleted unused resources/ testdoc/testdoc {:git-url "https://github.com/liquidz/testdoc", :test-namespaces (testdoc.style.repl-test testdoc.style.code-first-test testdoc.core-test), :git-sha "6b995ef25f3cc6450a1ce30f72baed371476f6eb"} ;; BB-TEST-PATCH: Remove contajners.core-test as it fails - org.clojars.lispyclouds/contajners {:git-url "https://github.com/lispyclouds/contajners", :test-namespaces (contajners.impl-test), :git-sha "d163637ff36d79995516d6705da1e9afc7b44764"} + org.clojars.lispyclouds/contajners {:git-url "https://github.com/lispyclouds/contajners", :test-namespaces (contajners.impl-test), :git-sha "5d55187eebedee4cd4f120fbffcbbcfae70a8a7b"} ;; Don't run tests b/c they depend on `psql` douglass/clj-psql {:git-url "https://github.com/DarinDouglass/clj-psql", :test-namespaces [], :manually-added true} ;; Don't run tests b/c they depend on `ffmpeg` @@ -111,4 +108,78 @@ aysylu/loom {:git-url "https://github.com/aysylu/loom", :test-namespaces (loom.test.network-simplex loom.test.label loom.test.alg-generic loom.test.compliance-tester loom.test.flow loom.test.alg loom.test.attr loom.test.graph loom.test.derived), :git-sha "d458f0c0dee9021983c64381b90a470f0178cc8e"} com.layerware/hugsql-core {:test-namespaces (hugsql.babashka-test)} com.github.seancorfield/expectations {:git-url "https://github.com/clojure-expectations/clojure-test", :test-namespaces (expectations.clojure.test-test), :git-sha "b30fefd97d9eb7d1f47e06956521f354cb926b03"} -} + com.rpl/specter {:git-url "https://github.com/redplanetlabs/specter", :test-namespaces (com.rpl.specter.cljs-test-helpers com.rpl.specter.test-helpers com.rpl.specter.core-test com.rpl.specter.zipper-test), :git-sha "67e86806020b9d02fbca8cdb1efad3002fc81a32"} + com.github.askonomm/clarktown {:git-url "https://github.com/askonomm/clarktown", :test-namespaces (clarktown.core-test clarktown.parsers.horizontal-line-block-test clarktown.parsers.italic-test clarktown.parsers.link-and-image-test clarktown.parsers.empty-block-test clarktown.parsers.inline-code-test clarktown.parsers.heading-block-test clarktown.parsers.bold-test clarktown.parsers.quote-block-test clarktown.parsers.code-block-test clarktown.parsers.strikethrough-test), :git-sha "059bfa7bd9bfdde0c75646bf1dfc20d23da8a02c"} + org.clojure/math.numeric-tower {:git-url "https://github.com/clojure/math.numeric-tower", :test-namespaces (clojure.math.test-numeric-tower), :git-sha "97827be66f35feebc3c89ba81c546fef4adc7947"} + prismatic/schema {:test-namespaces [schema.core-test + schema.macros-test + schema.coerce-test + schema.experimental.abstract-map-test + schema.test-test + schema.utils-test] + :git-url "https://github.com/plumatic/schema" + :git-sha "6846dc7c3a9df5bfd718f68f183c683ce0f621ff" + :git-tag "schema-1.3.0" + ;; specify for adding tests to classpath + :test-paths ["test/clj" "test/cljc"]} + metosin/malli {:test-namespaces [malli.clj-kondo-test + malli.core-test + malli.destructure-test + malli.dot-test + malli.error-test + malli.experimental-test + ;; malli.generator-test + malli.instrument-test + malli.json-schema-test + malli.plantuml-test + malli.provider-test + malli.registry-test + malli.swagger-test + malli.transform-test + malli.util-test] + :git-url "https://github.com/metosin/malli" + :git-sha "588147ef49b2e41c7d12a8aa994b39c1c6fedd99" + :git-tag "0.8.9" + ;; specify for adding tests to classpath + :test-paths ["test"]} + meander/epsilon {:test-namespaces [meander.epsilon-test + meander.defsyntax-test + meander.syntax.epsilon-test + meander.substitute.epsilon-test + meander.strategy.epsilon-test + meander.matrix.epsilon-test + meander.match.epsilon-test + meander.match.ir.epsilon-test + meander.match.check.epsilon-test + meander.interpreter.epsilon-test + meander.defsyntax-test.gh-145] + :test-paths ["test"] + :git-url "https://github.com/noprompt/meander" + :git-sha "55f5ce70e6ef717e95c58260f6bc725d70c0cb6d"} + cc.qbits/auspex {:git-url "https://github.com/mpenet/auspex" + :git-sha "1a9d7427e60e1a434a764aa820d1c53f7e22504a" + :test-paths ["test"] + :test-namespaces [qbits.auspex-test]} + exoscale/interceptor {:git-url "https://github.com/exoscale/interceptor" + :git-sha "ca115fe00a0abf3a2f78452ab309c3aa4c00fc4e" + :test-paths ["test"] + :test-namespaces [exoscale.interceptor-test]} + clj-commons/fs {:git-url "https://github.com/clj-commons/fs", :test-namespaces (me.raynes.core-test), :git-sha "60026817c44015da8656925411d4af1d8210bad0"} + postmortem/postmortem {:git-url "https://github.com/athos/Postmortem" + :git-sha "1a29775a3d286f9f6fe3f979c78b6e2bf298d5ba" + :test-paths ["test"] + :test-namespaces [postmortem.core-test postmortem.instrument-test + postmortem.instrument-test]} + com.github.rawleyfowler/sluj {:git-url "https://github.com/rawleyfowler/sluj" + :git-sha "4a92e772b4e07bf127423448d4140748b5782198" + :test-paths ["test"] + :test-namespaces [sluj.core-test]} + io.github.cognitect-labs/test-runner {:git-url "https://github.com/cognitect-labs/test-runner", + :git-sha "7284cda41fb9edc0f3bc6b6185cfb7138fc8a023" + :test-namespaces [cognitect.test-runner.samples-test cognitect.test-runner.sample-property-test cognitect.test-runner-test], + :test-paths ["test"]} + ;; BB-TEST-PATCH: Removed clojure.tools.namespace.dir-test as it fails on windows + org.clojure/tools.namespace {:git-sha "daf82a10e70182aea4c0716a48f3922163441b32", + :git-url "https://github.com/clojure/tools.namespace", + :test-namespaces [clojure.tools.namespace.test-helpers clojure.tools.namespace.dependency-test clojure.tools.namespace.find-test clojure.tools.namespace.move-test clojure.tools.namespace.parse-test], + :test-paths ["src/test/clojure"]}} diff --git a/test-resources/lib_tests/clarktown/core.md b/test-resources/lib_tests/clarktown/core.md new file mode 100644 index 00000000..6a5b2f17 --- /dev/null +++ b/test-resources/lib_tests/clarktown/core.md @@ -0,0 +1,93 @@ +Lorem ipsum dolor **sit** amet. Lorem ipsum *dolor* _sit_ __amet__. + +There's a [link here](https://example.com/that_has_things?!???!#in-it). + +1. List item +2. Another list item + 1. Sub list item + 2. Another sub list item + 1. Sub sub list item + 3. Continuing sub list item +3. Continuing list item + +```javascript +// Detect horizontal line block +function isHorizontalLineBlock(block) { + return block === "***"; +} + +// Render horizontal line block +function horizontalLineBlock(block) { + return `
    `; +} + +// Compose an array of parsers +const parsers = [{ + matcher: isHorizontalLineBlock, + renderers: [horizontalLineBlock] +}]; + +// And finally, our parser itself +function markdownToHTML(markdown) { + // Create blocks + const blocks = content.split(/\n\n/); + + // Parse blocks + const parsedBlocks = blocks.map((block) => { + // Let's find a parser that has a matcher that matches + const parser = parsers.find((parser) => parser.matcher(block)); + + // If match was found, let's run our renderers over `block` + if (parser) { + for (const renderer of match.renderers) { + block = renderer(block); + } + } + + return block; + }); + + // And at last, join the blocks together for one big block. + return parsedBlocks.join(""); +} +``` + +- Test 123 +- Test 223 + - Test 334 + 1. Test test + +This is ___bold italic text___ and ***this is also***. *What about italic text that **has bold text***? + +## Hi there, world! + +* List item +* Another list ~~item~~ + * Sub list item + * Another sub list item + * Sub sub list item + * Continuing sub list item +* Continuing list item + +*** + +* List item +* Another list item + * Sub list item + * Another sub list item + 1. Sub sub list item + 2. Continuing sub list item +* Continuing list item + +This is a H1 heading with settext +================================= + +And this is a H2 heading with settext +------------------------------------- + +Testing paragraph right before a code block +``` +code goes here +``` +# Heading goes here +Paragraph right after heading diff --git a/test-resources/lib_tests/clarktown/core_result.html b/test-resources/lib_tests/clarktown/core_result.html new file mode 100644 index 00000000..d604bf58 --- /dev/null +++ b/test-resources/lib_tests/clarktown/core_result.html @@ -0,0 +1,69 @@ +

    Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.

    + +

    There's a link here.

    + +
    1. List item
    2. Another list item
      1. Sub list item
      2. Another sub list item
        1. Sub sub list item
      3. Continuing sub list item
    3. Continuing list item
    + +
    // Detect horizontal line block
    +function isHorizontalLineBlock(block) {
    +  return block === "***";
    +}
    +
    +// Render horizontal line block
    +function horizontalLineBlock(block) {
    +  return `<hr>`;
    +}
    +
    +// Compose an array of parsers
    +const parsers = [{
    +  matcher: isHorizontalLineBlock,
    +  renderers: [horizontalLineBlock]
    +}];
    +
    +// And finally, our parser itself
    +function markdownToHTML(markdown) {
    +  // Create blocks
    +  const blocks = content.split(/\n\n/);
    +
    +  // Parse blocks
    +  const parsedBlocks = blocks.map((block) => {
    +    // Let's find a parser that has a matcher that matches
    +    const parser = parsers.find((parser) => parser.matcher(block));
    +
    +    // If match was found, let's run our renderers over `block`
    +    if (parser) {
    +      for (const renderer of match.renderers) {
    +        block = renderer(block);
    +      }
    +    }
    +
    +    return block;
    +  });
    +
    +  // And at last, join the blocks together for one big block.
    +  return parsedBlocks.join("");
    +}
    + + + +

    This is bold italic text and this is also. What about italic text that has bold text?

    + +

    Hi there, world!

    + + + +
    + + + +

    This is a H1 heading with settext

    + +

    And this is a H2 heading with settext

    + +

    Testing paragraph right before a code block

    + +
    code goes here
    + +

    Heading goes here

    + +

    Paragraph right after heading

    diff --git a/test-resources/lib_tests/clarktown/core_test.clj b/test-resources/lib_tests/clarktown/core_test.clj new file mode 100644 index 00000000..86ca1765 --- /dev/null +++ b/test-resources/lib_tests/clarktown/core_test.clj @@ -0,0 +1,15 @@ +(ns clarktown.core-test + (:require + ;; BB-TEST-PATCH: require clojure.string for split-lines patch below + [clojure.string :as str] + [clojure.test :refer [deftest testing is]] + [clojure.java.io :as io] + [clarktown.core :as core])) + + +(deftest overall-test + (testing "Overall" + ;; BB-TEST-PATCH: library uses hard-coded \n, so using split-lines for platform-agnostic testing + ;; BB-TEST-PATCH: change file paths to match bb folder structure (and copy resource files) + (is (= (str/split-lines (core/render (slurp (io/file (io/resource "clarktown/core.md"))))) + (str/split-lines (slurp (io/file (io/resource "clarktown/core_result.html")))))))) diff --git a/test-resources/lib_tests/clarktown/parsers/bold_test.clj b/test-resources/lib_tests/clarktown/parsers/bold_test.clj new file mode 100644 index 00000000..a082d418 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/bold_test.clj @@ -0,0 +1,18 @@ +(ns clarktown.parsers.bold-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.bold :as bold])) + + +(deftest bold-test + (testing "Creating bold text with two surrounding asterisk characters" + (is (= "This is bold." + (bold/render "**This is bold.**" nil)))) + + (testing "Creating bold text with two surrounding underscore characters" + (is (= "This is bold." + (bold/render "__This is bold.__" nil)))) + + (testing "Creating bold text with both underscores and asterisks mixed" + (is (= "Hi, my name is John, what is your name?" + (bold/render "Hi, my name is **John**, what is __your name?__" nil))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/code_block.md b/test-resources/lib_tests/clarktown/parsers/code_block.md new file mode 100644 index 00000000..8e6f863f --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/code_block.md @@ -0,0 +1,41 @@ +```javascript +// Detect horizontal line block +function isHorizontalLineBlock(block) { + return block === "***"; +} + +// Render horizontal line block +function horizontalLineBlock(block) { + return `
    `; +} + +// Compose an array of parsers +const parsers = [{ + matcher: isHorizontalLineBlock, + renderers: [horizontalLineBlock] +}]; + +// And finally, our parser itself +function markdownToHTML(markdown) { + // Create blocks + const blocks = content.split(/\n\n/); + + // Parse blocks + const parsedBlocks = blocks.map((block) => { + // Let's find a parser that has a matcher that matches + const parser = parsers.find((parser) => parser.matcher(block)); + + // If match was found, let's run our renderers over `block` + if (parser) { + for (const renderer of match.renderers) { + block = renderer(block); + } + } + + return block; + }); + + // And at last, join the blocks together for one big block. + return parsedBlocks.join(""); +} +``` \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/code_block_no_language.md b/test-resources/lib_tests/clarktown/parsers/code_block_no_language.md new file mode 100644 index 00000000..dbba1f11 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/code_block_no_language.md @@ -0,0 +1,41 @@ +``` +// Detect horizontal line block +function isHorizontalLineBlock(block) { + return block === "***"; +} + +// Render horizontal line block +function horizontalLineBlock(block) { + return `
    `; +} + +// Compose an array of parsers +const parsers = [{ + matcher: isHorizontalLineBlock, + renderers: [horizontalLineBlock] +}]; + +// And finally, our parser itself +function markdownToHTML(markdown) { + // Create blocks + const blocks = content.split(/\n\n/); + + // Parse blocks + const parsedBlocks = blocks.map((block) => { + // Let's find a parser that has a matcher that matches + const parser = parsers.find((parser) => parser.matcher(block)); + + // If match was found, let's run our renderers over `block` + if (parser) { + for (const renderer of match.renderers) { + block = renderer(block); + } + } + + return block; + }); + + // And at last, join the blocks together for one big block. + return parsedBlocks.join(""); +} +``` \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/code_block_no_language_result.html b/test-resources/lib_tests/clarktown/parsers/code_block_no_language_result.html new file mode 100644 index 00000000..0512d3eb --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/code_block_no_language_result.html @@ -0,0 +1,39 @@ +
    // Detect horizontal line block
    +function isHorizontalLineBlock(block) {
    +  return block === "***";
    +}
    +
    +// Render horizontal line block
    +function horizontalLineBlock(block) {
    +  return `<hr>`;
    +}
    +
    +// Compose an array of parsers
    +const parsers = [{
    +  matcher: isHorizontalLineBlock,
    +  renderers: [horizontalLineBlock]
    +}];
    +
    +// And finally, our parser itself
    +function markdownToHTML(markdown) {
    +  // Create blocks
    +  const blocks = content.split(/\n\n/);
    +
    +  // Parse blocks
    +  const parsedBlocks = blocks.map((block) => {
    +    // Let's find a parser that has a matcher that matches
    +    const parser = parsers.find((parser) => parser.matcher(block));
    +
    +    // If match was found, let's run our renderers over `block`
    +    if (parser) {
    +      for (const renderer of match.renderers) {
    +        block = renderer(block);
    +      }
    +    }
    +
    +    return block;
    +  });
    +
    +  // And at last, join the blocks together for one big block.
    +  return parsedBlocks.join("");
    +}
    \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/code_block_result.html b/test-resources/lib_tests/clarktown/parsers/code_block_result.html new file mode 100644 index 00000000..21b92144 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/code_block_result.html @@ -0,0 +1,39 @@ +
    // Detect horizontal line block
    +function isHorizontalLineBlock(block) {
    +  return block === "***";
    +}
    +
    +// Render horizontal line block
    +function horizontalLineBlock(block) {
    +  return `<hr>`;
    +}
    +
    +// Compose an array of parsers
    +const parsers = [{
    +  matcher: isHorizontalLineBlock,
    +  renderers: [horizontalLineBlock]
    +}];
    +
    +// And finally, our parser itself
    +function markdownToHTML(markdown) {
    +  // Create blocks
    +  const blocks = content.split(/\n\n/);
    +
    +  // Parse blocks
    +  const parsedBlocks = blocks.map((block) => {
    +    // Let's find a parser that has a matcher that matches
    +    const parser = parsers.find((parser) => parser.matcher(block));
    +
    +    // If match was found, let's run our renderers over `block`
    +    if (parser) {
    +      for (const renderer of match.renderers) {
    +        block = renderer(block);
    +      }
    +    }
    +
    +    return block;
    +  });
    +
    +  // And at last, join the blocks together for one big block.
    +  return parsedBlocks.join("");
    +}
    \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/code_block_test.clj b/test-resources/lib_tests/clarktown/parsers/code_block_test.clj new file mode 100644 index 00000000..b965e5fa --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/code_block_test.clj @@ -0,0 +1,18 @@ +(ns clarktown.parsers.code-block-test + (:require + ;; require clojure.string to accomodate line break hack below + [clojure.string :as str] + [clojure.test :refer [deftest testing is]] + [clojure.java.io :as io] + [clarktown.parsers.code-block :as code-block])) + +;; BB-TEST-PATCH: change paths to match folder structure (and copy resource files) +;; BB-TEST-PATCH: use split-lines to make tests platform-agnostic +(deftest code-block-test + (testing "Code block with language specification" + (is (= (str/split-lines (slurp (io/file (io/resource "clarktown/parsers/code_block_result.html")))) + (str/split-lines (code-block/render (slurp (io/file (io/resource "clarktown/parsers/code_block.md"))) nil))))) + + (testing "Code block with NO language specification" + (is (= (str/split-lines (slurp (io/file (io/resource "clarktown/parsers/code_block_no_language_result.html")))) + (str/split-lines (code-block/render (slurp (io/file (io/resource "clarktown/parsers/code_block_no_language.md"))) nil)))))) diff --git a/test-resources/lib_tests/clarktown/parsers/empty_block_test.clj b/test-resources/lib_tests/clarktown/parsers/empty_block_test.clj new file mode 100644 index 00000000..a8d89c48 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/empty_block_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.parsers.empty-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.empty-block :as empty-block])) + + +(deftest empty-block-test + (testing "Rendering an empty block" + (is (= (empty-block/render "" nil) + ""))) + + (testing "Checking an empty block" + (is (true? (empty-block/is? ""))) + (is (true? (empty-block/is? " "))))) diff --git a/test-resources/lib_tests/clarktown/parsers/heading_block_test.clj b/test-resources/lib_tests/clarktown/parsers/heading_block_test.clj new file mode 100644 index 00000000..9bfff4fd --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/heading_block_test.clj @@ -0,0 +1,44 @@ +(ns clarktown.parsers.heading-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.heading-block :as heading-block])) + + +(deftest hashbang-heading-test + (testing "Hashbang heading block that's a H1" + (is (= "

    This is a heading block.

    " + (heading-block/render "# This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H2" + (is (= "

    This is a heading block.

    " + (heading-block/render "## This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H3" + (is (= "

    This is a heading block.

    " + (heading-block/render "### This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H4" + (is (= "

    This is a heading block.

    " + (heading-block/render "#### This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H5" + (is (= "
    This is a heading block.
    " + (heading-block/render "##### This is a heading block." nil))))) + + +(deftest settext-heading-text + (testing "Settext heading block that's a H1" + (is (= "

    This is a heading block.

    " + (heading-block/render "This is a heading block.\n=========" nil)))) + + (testing "Settext heading block that's a H1 spanning multiple lines" + (is (= "

    This is a \nheading block spanning multiple lines.

    " + (heading-block/render "This is a \nheading block spanning multiple lines.\n========" nil)))) + + (testing "Settext heading block that's a H2" + (is (= "

    This is a heading block.

    " + (heading-block/render "This is a heading block.\n---------" nil)))) + + (testing "Settext heading block that's a H2 spanning multiple lines" + (is (= "

    This is a \nheading block spanning multiple lines.

    " + (heading-block/render "This is a \nheading block spanning multiple lines.\n--------" nil))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/horizontal_line_block_test.clj b/test-resources/lib_tests/clarktown/parsers/horizontal_line_block_test.clj new file mode 100644 index 00000000..21617b6c --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/horizontal_line_block_test.clj @@ -0,0 +1,21 @@ +(ns clarktown.parsers.horizontal-line-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.horizontal-line-block :as horizontal-line-block])) + + +(deftest horizontal-line-block-test + (testing "Creating a horizontal line" + (is (= "
    " + (horizontal-line-block/render "***" nil))) + + (is (= "
    " + (horizontal-line-block/render "---" nil)))) + + (testing "Is a horizontal line block" + (is (true? (horizontal-line-block/is? "***"))) + (is (true? (horizontal-line-block/is? " ***"))) + (is (false? (horizontal-line-block/is? "Test *** 123"))) + (is (true? (horizontal-line-block/is? "---"))) + (is (true? (horizontal-line-block/is? " ---"))) + (is (false? (horizontal-line-block/is? "Test --- 123"))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/inline_code_test.clj b/test-resources/lib_tests/clarktown/parsers/inline_code_test.clj new file mode 100644 index 00000000..028c4b7d --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/inline_code_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.parsers.inline-code-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.inline-code :as inline-code])) + + +(deftest inline-code-test + (testing "Creating inline code text" + (is (= "This is inline code." + (inline-code/render "`This is inline code.`" nil)))) + + (testing "Creating inline-code text in the middle of regular text" + (is (= "This is regular text, mixed with some inline code., and it's great." + (inline-code/render "This is regular text, mixed with `some inline code.`, and it's great." nil))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/italic_test.clj b/test-resources/lib_tests/clarktown/parsers/italic_test.clj new file mode 100644 index 00000000..8ab13698 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/italic_test.clj @@ -0,0 +1,18 @@ +(ns clarktown.parsers.italic-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.italic :as italic])) + + +(deftest italic-test + (testing "Creating italic text with one surrounding asterisk character" + (is (= "This is italic." + (italic/render "*This is italic.*" nil)))) + + (testing "Creating italic text with one surrounding underscore character" + (is (= "This is italic." + (italic/render "_This is italic._" nil)))) + + (testing "Creating italic text with both underscores and asterisks mixed" + (is (= "Hi, my name is John, what is your name?" + (italic/render "Hi, my name is *John*, what is _your name?_" nil))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/link_and_image_test.clj b/test-resources/lib_tests/clarktown/parsers/link_and_image_test.clj new file mode 100644 index 00000000..348a8f90 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/link_and_image_test.clj @@ -0,0 +1,23 @@ +(ns clarktown.parsers.link-and-image-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.link-and-image :as link-and-image])) + + +(deftest link-test + (testing "Creating a link" + (is (= (link-and-image/render "[This is a link](https://example.com)" nil) + "This is a link")) + + (is (= (link-and-image/render "[This-is-a-link](https://example.com)" nil) + "This-is-a-link")) + + (is (= (link-and-image/render "[x] [label](link)" nil) + "[x] label")) + + (is (= (link-and-image/render "[ ] [label](link)" nil) + "[ ] label"))) + + (testing "Creating an image" + (is (= (link-and-image/render "![This is an image](https://example.com)" nil) + "\"This")))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/quote_block_test.clj b/test-resources/lib_tests/clarktown/parsers/quote_block_test.clj new file mode 100644 index 00000000..94553cf1 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/quote_block_test.clj @@ -0,0 +1,15 @@ +(ns clarktown.parsers.quote-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.quote-block :as quote-block])) + + +(deftest quote-block-block-test + (testing "Creating a quote block line" + (is (= (quote-block/render "> First line\n> second line" nil) + "
    First line\nsecond line
    "))) + + (testing "Checking a quote block" + (is (true? (quote-block/is? "> Test"))) + (is (true? (quote-block/is? " > Test"))) + (is (true? (quote-block/is? ">"))))) \ No newline at end of file diff --git a/test-resources/lib_tests/clarktown/parsers/strikethrough_test.clj b/test-resources/lib_tests/clarktown/parsers/strikethrough_test.clj new file mode 100644 index 00000000..fdf61888 --- /dev/null +++ b/test-resources/lib_tests/clarktown/parsers/strikethrough_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.parsers.strikethrough-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.parsers.strikethrough :as strikethrough])) + + +(deftest strikethrough-test + (testing "Creating strikethrough text" + (is (= (strikethrough/render "~~This is strikethrough text.~~" nil) + "This is strikethrough text."))) + + (testing "Creating strikethrough text mixed with regular text" + (is (= (strikethrough/render "Some other text, ~~This is strikethrough text.~~ And more text." nil) + "Some other text, This is strikethrough text. And more text.")))) \ No newline at end of file diff --git a/test-resources/lib_tests/clojure/math/test_numeric_tower.clj b/test-resources/lib_tests/clojure/math/test_numeric_tower.clj new file mode 100644 index 00000000..ba0ba266 --- /dev/null +++ b/test-resources/lib_tests/clojure/math/test_numeric_tower.clj @@ -0,0 +1,129 @@ +(ns clojure.math.test-numeric-tower + (:use clojure.test + clojure.math.numeric-tower)) + +(deftest test-expt + (are [x y] (= x y) + (expt 2 3) 8 + (expt (expt 2 16) 2) (expt 2 32) + (expt 4/3 2) 16/9 + (expt 2 -10) 1/1024 + (expt 0.5M 2) 0.25M + (expt 5 4.2) (Math/pow 5 4.2) + (expt 5.3 4) (Math/pow 5.3 4) + (expt 5.3 4) (Math/pow 5.3 4) + (expt 2 0) 1 + (expt (java.math.BigInteger. "4") 0) (java.math.BigInteger. "1") + (expt 4M 0) 1M + (expt 8M 1) 8M + (expt 16M 16) 18446744073709551616M)) + +(when-available clojure.lang.BigInt + (deftest test-expt-bigint + (are [x y] (= x y) + (expt (bigint 4) 0) (bigint 1)))) + +(deftest test-abs + (are [x y] (= x y) + (abs -2) 2 + (abs 0) 0 + (abs 5) 5 + (abs 123456789123456789) 123456789123456789 + (abs -123456789123456789) 123456789123456789 + (abs 5/3) 5/3 + (abs -4/3) 4/3 + (abs 4.3M) 4.3M + (abs -4.3M) 4.3M + (abs 2.8) 2.8 + (abs -2.8) 2.8)) + +(deftest test-gcd + (are [x y] (= x y) + (gcd 4 3) 1 + (gcd 24 12) 12 + (gcd 24 27) 3 + (gcd 1 0) 1 + (gcd 0 1) 1 + (gcd 0 0) 0) + (is (thrown? IllegalArgumentException (gcd nil 0))) + (is (thrown? IllegalArgumentException (gcd 0 nil))) + (is (thrown? IllegalArgumentException (gcd 7.0 0)))) + +(deftest test-lcm + (are [x y] (= x y) + (lcm 2 3) 6 + (lcm 3 2) 6 + (lcm -2 3) 6 + (lcm 2 -3) 6 + (lcm -2 -3) 6 + (lcm 4 10) 20 + (lcm 1 0) 0 + (lcm 0 1) 0 + (lcm 0 0) 0) + (is (thrown? IllegalArgumentException (lcm nil 0))) + (is (thrown? IllegalArgumentException (lcm 0 nil))) + (is (thrown? IllegalArgumentException (lcm 7.0 0)))) + +(deftest test-floor + (are [x y] (== x y) + (floor 6) 6 + (floor -6) -6 + (floor 123456789123456789) 123456789123456789 + (floor -123456789123456789) -123456789123456789 + (floor 4/3) 1 + (floor -4/3) -2 + (floor 4.3M) 4 + (floor -4.3M) -5 + (floor 4.3) 4.0 + (floor -4.3) -5.0)) + +(deftest test-ceil + (are [x y] (== x y) + (ceil 6) 6 + (ceil -6) -6 + (ceil 123456789123456789) 123456789123456789 + (ceil -123456789123456789) -123456789123456789 + (ceil 4/3) 2 + (ceil -4/3) -1 + (ceil 4.3M) 5 + (ceil -4.3M) -4 + (ceil 4.3) 5.0 + (ceil -4.3) -4.0)) + +(deftest test-round + (are [x y] (== x y) + (round 6) 6 + (round -6) -6 + (round 123456789123456789) 123456789123456789 + (round -123456789123456789) -123456789123456789 + (round 4/3) 1 + (round 5/3) 2 + (round 5/2) 3 + (round -4/3) -1 + (round -5/3) -2 + (round -5/2) -2 + (round 4.3M) 4 + (round 4.7M) 5 + (round -4.3M) -4 + (round -4.7M) -5 + (round 4.5M) 5 + (round -4.5M) -4 + (round 4.3) 4 + (round 4.7) 5 + (round -4.3) -4 + (round -4.7) -5 + (round 4.5) 5 + (round -4.5) -4)) + +(deftest test-sqrt + (are [x y] (= x y) + (sqrt 9) 3 + (sqrt 16/9) 4/3 + (sqrt 0.25M) 0.5M + (sqrt 2) (Math/sqrt 2))) + +(deftest test-exact-integer-sqrt + (are [x y] (= x y) + (exact-integer-sqrt 15) [3 6] + (exact-integer-sqrt (inc (expt 2 32))) [(expt 2 16) 1] + (exact-integer-sqrt 1000000000000) [1000000 0])) diff --git a/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj b/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj deleted file mode 100644 index 5aedb27f..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj +++ /dev/null @@ -1,315 +0,0 @@ -(ns clojure.tools.namespace.dependency-test - (:use clojure.test - clojure.tools.namespace.dependency)) - -;; building a graph like: -;; -;; :a -;; / | -;; :b | -;; \ | -;; :c -;; | -;; :d -;; -(def g1 (-> (graph) - (depend :b :a) ; "B depends on A" - (depend :c :b) ; "C depends on B" - (depend :c :a) ; "C depends on A" - (depend :d :c))) ; "D depends on C" - -;; 'one 'five -;; | | -;; 'two | -;; / \ | -;; / \ | -;; / \ / -;; 'three 'four -;; | / -;; 'six / -;; | / -;; | / -;; | / -;; 'seven -;; -(def g2 (-> (graph) - (depend 'two 'one) - (depend 'three 'two) - (depend 'four 'two) - (depend 'four 'five) - (depend 'six 'three) - (depend 'seven 'six) - (depend 'seven 'four))) - -;; :level0 -;; / | | \ -;; ----- | | ----- -;; / | | \ -;; :level1a :level1b :level1c :level1d -;; \ | | / -;; ----- | | ----- -;; \ | | / -;; :level2 -;; / | | \ -;; ----- | | ----- -;; / | | \ -;; :level3a :level3b :level3c :level3d -;; \ | | / -;; ----- | | ----- -;; \ | | / -;; :level4 -;; -;; ... and so on in a repeating pattern like that, up to :level26 - -(def g3 (-> (graph) - (depend :level1a :level0) - (depend :level1b :level0) - (depend :level1c :level0) - (depend :level1d :level0) - (depend :level2 :level1a) - (depend :level2 :level1b) - (depend :level2 :level1c) - (depend :level2 :level1d) - - (depend :level3a :level2) - (depend :level3b :level2) - (depend :level3c :level2) - (depend :level3d :level2) - (depend :level4 :level3a) - (depend :level4 :level3b) - (depend :level4 :level3c) - (depend :level4 :level3d) - - (depend :level5a :level4) - (depend :level5b :level4) - (depend :level5c :level4) - (depend :level5d :level4) - (depend :level6 :level5a) - (depend :level6 :level5b) - (depend :level6 :level5c) - (depend :level6 :level5d) - - (depend :level7a :level6) - (depend :level7b :level6) - (depend :level7c :level6) - (depend :level7d :level6) - (depend :level8 :level7a) - (depend :level8 :level7b) - (depend :level8 :level7c) - (depend :level8 :level7d) - - (depend :level9a :level8) - (depend :level9b :level8) - (depend :level9c :level8) - (depend :level9d :level8) - (depend :level10 :level9a) - (depend :level10 :level9b) - (depend :level10 :level9c) - (depend :level10 :level9d) - - (depend :level11a :level10) - (depend :level11b :level10) - (depend :level11c :level10) - (depend :level11d :level10) - (depend :level12 :level11a) - (depend :level12 :level11b) - (depend :level12 :level11c) - (depend :level12 :level11d) - - (depend :level13a :level12) - (depend :level13b :level12) - (depend :level13c :level12) - (depend :level13d :level12) - (depend :level14 :level13a) - (depend :level14 :level13b) - (depend :level14 :level13c) - (depend :level14 :level13d) - - (depend :level15a :level14) - (depend :level15b :level14) - (depend :level15c :level14) - (depend :level15d :level14) - (depend :level16 :level15a) - (depend :level16 :level15b) - (depend :level16 :level15c) - (depend :level16 :level15d) - - (depend :level17a :level16) - (depend :level17b :level16) - (depend :level17c :level16) - (depend :level17d :level16) - (depend :level18 :level17a) - (depend :level18 :level17b) - (depend :level18 :level17c) - (depend :level18 :level17d) - - (depend :level19a :level18) - (depend :level19b :level18) - (depend :level19c :level18) - (depend :level19d :level18) - (depend :level20 :level19a) - (depend :level20 :level19b) - (depend :level20 :level19c) - (depend :level20 :level19d) - - (depend :level21a :level20) - (depend :level21b :level20) - (depend :level21c :level20) - (depend :level21d :level20) - (depend :level22 :level21a) - (depend :level22 :level21b) - (depend :level22 :level21c) - (depend :level22 :level21d) - - (depend :level23a :level22) - (depend :level23b :level22) - (depend :level23c :level22) - (depend :level23d :level22) - (depend :level24 :level23a) - (depend :level24 :level23b) - (depend :level24 :level23c) - (depend :level24 :level23d) - - (depend :level25a :level24) - (depend :level25b :level24) - (depend :level25c :level24) - (depend :level25d :level24) - (depend :level26 :level25a) - (depend :level26 :level25b) - (depend :level26 :level25c) - (depend :level26 :level25d))) - -(deftest t-transitive-dependencies - (is (= #{:a :c :b} - (transitive-dependencies g1 :d))) - (is (= '#{two four six one five three} - (transitive-dependencies g2 'seven)))) - -(deftest t-transitive-dependencies-deep - (is (= #{:level0 - :level1a :level1b :level1c :level1d - :level2 - :level3a :level3b :level3c :level3d - :level4 - :level5a :level5b :level5c :level5d - :level6 - :level7a :level7b :level7c :level7d - :level8 - :level9a :level9b :level9c :level9d - :level10 - :level11a :level11b :level11c :level11d - :level12 - :level13a :level13b :level13c :level13d - :level14 - :level15a :level15b :level15c :level15d - :level16 - :level17a :level17b :level17c :level17d - :level18 - :level19a :level19b :level19c :level19d - :level20 - :level21a :level21b :level21c :level21d - :level22 - :level23a :level23b :level23c :level23d} - (transitive-dependencies g3 :level24))) - (is (= #{:level0 - :level1a :level1b :level1c :level1d - :level2 - :level3a :level3b :level3c :level3d - :level4 - :level5a :level5b :level5c :level5d - :level6 - :level7a :level7b :level7c :level7d - :level8 - :level9a :level9b :level9c :level9d - :level10 - :level11a :level11b :level11c :level11d - :level12 - :level13a :level13b :level13c :level13d - :level14 - :level15a :level15b :level15c :level15d - :level16 - :level17a :level17b :level17c :level17d - :level18 - :level19a :level19b :level19c :level19d - :level20 - :level21a :level21b :level21c :level21d - :level22 - :level23a :level23b :level23c :level23d - :level24 - :level25a :level25b :level25c :level25d} - (transitive-dependencies g3 :level26)))) - - -(deftest t-transitive-dependents - (is (= '#{four seven} - (transitive-dependents g2 'five))) - (is (= '#{four seven six three} - (transitive-dependents g2 'two)))) - -(defn- before? - "True if x comes before y in an ordered collection." - [coll x y] - (loop [[item & more] (seq coll)] - (cond (nil? item) true ; end of the seq - (= x item) true ; x comes first - (= y item) false - :else (recur more)))) - -(deftest t-before - (is (true? (before? [:a :b :c :d] :a :b))) - (is (true? (before? [:a :b :c :d] :b :c))) - (is (false? (before? [:a :b :c :d] :d :c))) - (is (false? (before? [:a :b :c :d] :c :a)))) - -(deftest t-topo-comparator-1 - (let [sorted (sort (topo-comparator g1) [:d :a :b :foo])] - (are [x y] (before? sorted x y) - :a :b - :a :d - :a :foo - :b :d - :b :foo - :d :foo))) - -(deftest t-topo-comparator-2 - (let [sorted (sort (topo-comparator g2) '[three seven nine eight five])] - (are [x y] (before? sorted x y) - 'three 'seven - 'three 'eight - 'three 'nine - 'five 'eight - 'five 'nine - 'seven 'eight - 'seven 'nine))) - -(deftest t-topo-sort - (let [sorted (topo-sort g2)] - (are [x y] (before? sorted x y) - 'one 'two - 'one 'three - 'one 'four - 'one 'six - 'one 'seven - 'two 'three - 'two 'four - 'two 'six - 'two 'seven - 'three 'six - 'three 'seven - 'four 'seven - 'five 'four - 'five 'seven - 'six 'seven))) - -(deftest t-no-cycles - (is (thrown? Exception - (-> (graph) - (depend :a :b) - (depend :b :c) - (depend :c :a))))) - -(deftest t-no-self-cycles - (is (thrown? Exception - (-> (graph) - (depend :a :b) - (depend :a :a))))) diff --git a/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj b/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj deleted file mode 100644 index 60e56427..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj +++ /dev/null @@ -1,20 +0,0 @@ -(ns clojure.tools.namespace.dir-test - (:require [clojure.test :refer [deftest is]] - [clojure.tools.namespace.test-helpers :as help] - [clojure.tools.namespace.dir :as dir]) - (:import - (java.io File))) - -;; Only run this test on Java 1.7+, where java.nio.file.Files is available. -(when (try (Class/forName "java.nio.file.Files") - (catch ClassNotFoundException _ false)) - (deftest t-scan-by-canonical-path - (let [dir (help/create-temp-dir "t-scan-by-canonical-path") - main-clj (help/create-source dir 'example.main :clj '[example.one]) - one-cljc (help/create-source dir 'example.one :clj) - other-dir (help/create-temp-dir "t-scan-by-canonical-path-other") - link (File. other-dir "link")] - (java.nio.file.Files/createSymbolicLink (.toPath link) (.toPath dir) - (make-array java.nio.file.attribute.FileAttribute 0)) - (is (= (::dir/files (dir/scan-dirs {} [dir])) - (::dir/files (dir/scan-dirs {} [link]))))))) diff --git a/test-resources/lib_tests/clojure/tools/namespace/find_test.clj b/test-resources/lib_tests/clojure/tools/namespace/find_test.clj deleted file mode 100644 index d081e84a..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/find_test.clj +++ /dev/null @@ -1,29 +0,0 @@ -(ns clojure.tools.namespace.find-test - (:require [clojure.test :refer [deftest is]] - [clojure.tools.namespace.test-helpers :as help] - [clojure.tools.namespace.find :as find]) - (:import (java.io File))) - -(deftest t-find-clj-and-cljc-files - "main.clj depends on one.cljc which depends on two.clj. - two.cljs also exists but should not be returned" - (let [dir (help/create-temp-dir "t-find-clj-and-cljc-files") - main-clj (help/create-source dir 'example.main :clj '[example.one]) - one-cljc (help/create-source dir 'example.one :cljc '[example.two]) - two-clj (help/create-source dir 'example.two :clj) - two-cljs (help/create-source dir 'example.two :cljs)] - (is (help/same-files? - [main-clj one-cljc two-clj] - (find/find-sources-in-dir dir))))) - -(deftest t-find-cljs-and-cljc-files - "main.cljs depends on one.cljc which depends on two.cljs. - two.clj also exists but should not be returned" - (let [dir (help/create-temp-dir "t-find-cljs-and-cljc-files") - main-cljs (help/create-source dir 'example.main :cljs '[example.one]) - one-cljc (help/create-source dir 'example.one :cljc '[example.two]) - two-clj (help/create-source dir 'example.two :clj) - two-cljs (help/create-source dir 'example.two :cljs)] - (is (help/same-files? - [main-cljs one-cljc two-cljs] - (find/find-sources-in-dir dir find/cljs))))) diff --git a/test-resources/lib_tests/clojure/tools/namespace/move_test.clj b/test-resources/lib_tests/clojure/tools/namespace/move_test.clj deleted file mode 100644 index 47de7ac4..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/move_test.clj +++ /dev/null @@ -1,52 +0,0 @@ -(ns clojure.tools.namespace.move-test - (:require [clojure.java.io :as io] - [clojure.test :refer [deftest is]] - [clojure.tools.namespace.move :refer [move-ns]] - [clojure.tools.namespace.test-helpers :as help]) - (:import (java.io File))) - -(defn- create-file-one [dir] - (help/create-source dir 'example.one :clj - '[example.two example.three] - '[(defn foo [] - (example.a.four/foo))])) - -(defn- create-file-two [dir] - (help/create-source dir 'example.two :clj - '[example.three example.a.four])) - -(defn- create-file-three [dir] - (help/create-source dir 'example.three :clj - '[example.five])) - -(defn- create-file-four [dir] - (help/create-source dir 'example.a.four :clj)) - -(deftest t-move-ns - (let [temp-dir (help/create-temp-dir "tools-namespace-t-move-ns") - src-dir (io/file temp-dir "src") - example-dir (io/file temp-dir "src" "example") - file-one (create-file-one src-dir) - file-two (create-file-two src-dir) - file-three (create-file-three src-dir) - old-file-four (create-file-four src-dir) - new-file-four (io/file example-dir "b" "four.clj")] - - (let [file-three-last-modified (.lastModified file-three)] - - (Thread/sleep 1500) ;; ensure file timestamps are different - - (move-ns 'example.a.four 'example.b.four src-dir [src-dir]) - - (is (.exists new-file-four) - "new file should exist") - (is (not (.exists old-file-four)) - "old file should not exist") - (is (not (.exists (.getParentFile old-file-four))) - "old empty directory should not exist") - (is (= file-three-last-modified (.lastModified file-three)) - "unaffected file should not have been modified") - (is (not-any? #(.contains (slurp %) "example.a.four") - [file-one file-two file-three new-file-four])) - (is (every? #(.contains (slurp %) "example.b.four") - [file-one file-two new-file-four]))))) diff --git a/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj b/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj deleted file mode 100644 index b6ac6aaa..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj +++ /dev/null @@ -1,210 +0,0 @@ -(ns clojure.tools.namespace.parse-test - (:use [clojure.test :only (deftest is)] - [clojure.tools.namespace.parse :only (deps-from-ns-decl - read-ns-decl)])) - -(def ns-decl-prefix-list - '(ns com.example.one - (:require (com.example two - [three :as three] - [four :refer (a b)]) - (com.example.sub [five :as five] - six)) - (:use (com.example seven - [eight :as eight] - (nine :only (c d)) - [ten])))) - -;; Some people like to write prefix lists as vectors, not lists. The -;; use/require functions accept this form. -(def ns-decl-prefix-list-as-vector - '(ns com.example.one - (:require [com.example - two - [three :as three] - [four :refer (a b)]] - [com.example.sub - [five :as five] - six]) - (:use [com.example - seven - [eight :as eight] - (nine :only (c d)) - [ten]]))) - -(def ns-decl-prefix-list-clauses-as-vectors - "Sometimes people even write the clauses inside ns as vectors, which - clojure.core/ns allows. See TNS-21." - '(ns com.example.one - [:require [com.example - two - [three :as three] - [four :refer (a b)]] - [com.example.sub - [five :as five] - six]] - [:use [com.example - seven - [eight :as eight] - (nine :only (c d)) - [ten]]])) - -(def deps-from-prefix-list - '#{com.example.two - com.example.three - com.example.four - com.example.sub.five - com.example.sub.six - com.example.seven - com.example.eight - com.example.nine - com.example.ten}) - -(deftest t-prefix-list - (is (= deps-from-prefix-list - (deps-from-ns-decl ns-decl-prefix-list)))) - -(deftest t-prefix-list-as-vector - (is (= deps-from-prefix-list - (deps-from-ns-decl ns-decl-prefix-list-as-vector)))) - -(deftest t-prefix-list-clauses-as-vectors - (is (= deps-from-prefix-list - (deps-from-ns-decl ns-decl-prefix-list-clauses-as-vectors)))) - -(deftest t-no-deps-returns-empty-set - (is (= #{} (deps-from-ns-decl '(ns com.example.one))))) - -(def multiple-ns-decls - '((ns one) - (ns two (:require one)) - (ns three (:require [one :as o] [two :as t])))) - -(def multiple-ns-decls-string -" (println \"Code before first ns\") - (ns one) - (println \"Some code\") - (defn hello-world [] \"Hello, World!\") - (ns two (:require one)) - (println \"Some more code\") - (ns three (:require [one :as o] [two :as t]))") - -(deftest t-read-multiple-ns-decls - (with-open [rdr (clojure.lang.LineNumberingPushbackReader. - (java.io.PushbackReader. - (java.io.StringReader. multiple-ns-decls-string)))] - (is (= multiple-ns-decls - (take-while identity (repeatedly #(read-ns-decl rdr))))))) - -(def ns-docstring-example - "The example ns declaration used in the docstring of clojure.core/ns" - '(ns foo.bar - (:refer-clojure :exclude [ancestors printf]) - (:require (clojure.contrib sql combinatorics)) - (:use (my.lib this that)) - (:import (java.util Date Timer Random) - (java.sql Connection Statement)))) - -(def deps-from-ns-docstring-example - '#{clojure.contrib.sql - clojure.contrib.combinatorics - my.lib.this - my.lib.that}) - -(deftest t-ns-docstring-example - (is (= deps-from-ns-docstring-example - (deps-from-ns-decl ns-docstring-example)))) - -(def require-docstring-example - "The example ns declaration used in the docstring of - clojure.core/require" - '(ns (:require (clojure zip [set :as s])))) - -(def deps-from-require-docstring-example - '#{clojure.zip - clojure.set}) - -(deftest t-require-docstring-example - (is (= deps-from-require-docstring-example - (deps-from-ns-decl require-docstring-example)))) - -(def multiple-clauses - "Example showing more than one :require or :use clause in one ns - declaration, which clojure.core/ns allows." - '(ns foo.bar - (:require com.example.one) - (:import java.io.File) - (:require (com.example two three)) - (:use (com.example [four :only [x]])) - (:use (com.example (five :only [x]))))) - -(def deps-from-multiple-clauses - '#{com.example.one - com.example.two - com.example.three - com.example.four - com.example.five}) - -(deftest t-deps-from-multiple-clauses - (is (= deps-from-multiple-clauses - (deps-from-ns-decl multiple-clauses)))) - -(def clauses-without-keywords - "Example of require/use clauses with symbols instead of keywords, - which clojure.core/ns allows." - '(ns foo.bar - (require com.example.one) - (import java.io.File) - (use (com.example (prefixes (two :only [x]) - three))))) - -(def deps-from-clauses-without-keywords - '#{com.example.one - com.example.prefixes.two - com.example.prefixes.three}) - -(deftest t-clauses-without-keywords - (is (= deps-from-clauses-without-keywords - (deps-from-ns-decl clauses-without-keywords)))) - -(def reader-conditionals-string - "(ns com.examples.one - (:require #?(:clj clojure.string - :cljs goog.string)))") - -(deftest t-reader-conditionals - ;; TODO: the predicate wasn't added to bb yet, will come in version after 0.6.7 - (when true #_(resolve 'clojure.core/reader-conditional?) - (let [actual (-> reader-conditionals-string - java.io.StringReader. - java.io.PushbackReader. - clojure.lang.LineNumberingPushbackReader. - read-ns-decl - deps-from-ns-decl)] - (is (= #{'clojure.string} actual))))) - -(def ns-with-npm-dependency - "(ns com.examples.one - (:require [\"foobar\"] [baz]))") - -(deftest npm-dependency - (let [actual (-> ns-with-npm-dependency - java.io.StringReader. - java.io.PushbackReader. - clojure.lang.LineNumberingPushbackReader. - read-ns-decl - deps-from-ns-decl)] - (is (= #{'baz} actual)))) - -(def ns-with-require-macros - "(ns com.examples.one - (:require-macros [foo :refer [bar]]))") - -(deftest require-macros - (let [actual (-> ns-with-require-macros - java.io.StringReader. - java.io.PushbackReader. - clojure.lang.LineNumberingPushbackReader. - read-ns-decl - deps-from-ns-decl)] - (is (= #{'foo} actual)))) diff --git a/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj b/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj deleted file mode 100644 index 62679e69..00000000 --- a/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj +++ /dev/null @@ -1,82 +0,0 @@ -(ns clojure.tools.namespace.test-helpers - "Utilities to help with testing files and namespaces." - (:require [clojure.java.io :as io] - [clojure.string :as string]) - (:import (java.io Closeable File Writer PrintWriter))) - -(defn create-temp-dir - "Creates and returns a new temporary directory java.io.File." - [name] - (let [temp-file (File/createTempFile name nil)] - (.delete temp-file) - (.mkdirs temp-file) - (println "Temporary directory" (.getAbsolutePath temp-file)) - temp-file)) - -(defn- write-contents - "Writes contents into writer. Strings are written as-is via println, - other types written as by prn." - [^Writer writer contents] - {:pre [(sequential? contents)]} - (binding [*out* (PrintWriter. writer)] - (doseq [content contents] - (if (string? content) - (println content) - (prn content)) - (newline)))) - -(defn create-file - "Creates a file from a vector of path elements. Writes contents into - the file. Elements of contents may be data, written via prn, or - strings, written directly." - [path contents] - {:pre [(vector? path)]} - (let [^File file (apply io/file path)] - (when-let [parent (.getParentFile file)] - (.mkdirs parent)) - (with-open [wtr (io/writer file)] - (write-contents wtr contents)) - file)) - -(defn- sym->path - "Converts a symbol name into a vector of path parts, not including - file extension." - [symbol] - {:pre [(symbol? symbol)] - :post [(vector? %)]} - (-> (name symbol) - (string/replace \- \_) - (string/split #"\."))) - -(defn- source-path - "Returns a vector of path components for namespace named sym, - with given file extension (keyword)." - [sym extension] - (let [path (sym->path sym) - basename (peek path) - filename (str basename \. (name extension))] - (conj (pop path) filename))) - -(defn create-source - "Creates a file at the correct path under base-dir for a namespace - named sym, with file extension (keyword), containing a ns - declaration which :require's the dependencies (symbols). Optional - contents written after the ns declaration as by write-contents." - ([base-dir sym extension] - (create-source base-dir sym extension nil nil)) - ([base-dir sym extension dependencies] - (create-source base-dir sym extension dependencies nil)) - ([base-dir sym extension dependencies contents] - (let [full-path (into [base-dir] (source-path sym extension)) - ns-decl (if (seq dependencies) - (list 'ns sym (list* :require dependencies)) - (list 'ns sym))] - (create-file full-path (into [ns-decl] contents))))) - -(defn same-files? - "True if files-a and files-b contain the same canonical File's, - regardless of order." - [files-a files-b] - (= (sort (map #(.getCanonicalPath ^File %) files-a)) - (sort (map #(.getCanonicalPath ^File %) files-b)))) - diff --git a/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj b/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj deleted file mode 100644 index 1792beae..00000000 --- a/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj +++ /dev/null @@ -1,10 +0,0 @@ -(ns cognitect.test-runner.sample-property-test - (:require [clojure.test.check :as tc] - [clojure.test.check.generators :as gen] - [clojure.test.check.properties :as prop] - [clojure.test.check.clojure-test :refer [defspec]])) - -(defspec first-element-is-min-after-sorting 100 - (prop/for-all [v (gen/not-empty (gen/vector gen/int))] - (= (apply min v) - (first (sort v))))) diff --git a/test-resources/lib_tests/cognitect/test_runner/samples_test.clj b/test-resources/lib_tests/cognitect/test_runner/samples_test.clj deleted file mode 100644 index e4ec385c..00000000 --- a/test-resources/lib_tests/cognitect/test_runner/samples_test.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns cognitect.test-runner.samples-test - (:require [clojure.test :as t :refer [deftest is testing]])) - -(deftest math-works - (testing "basic addition and subtraction" - (is (= 42 (+ 40 2))) - (is (= 42 (- 44 2))))) - -(deftest ^:integration test-i - (is (= 1 1))) - - - - diff --git a/test-resources/lib_tests/cognitect/test_runner_test.clj b/test-resources/lib_tests/cognitect/test_runner_test.clj deleted file mode 100644 index e1ea8deb..00000000 --- a/test-resources/lib_tests/cognitect/test_runner_test.clj +++ /dev/null @@ -1,23 +0,0 @@ -(ns cognitect.test-runner-test - (:require - [clojure.test :refer :all] - [cognitect.test-runner :as tr])) - -(deftest ns-filters - (are [ns-names ns-regexes available selected] - (= selected (filter (#'tr/ns-filter {:namespace ns-names :namespace-regex ns-regexes}) available)) - - ;; default settings (no -n / -r, use default for -r) - nil nil nil [] - nil nil '[ns1-test ns2-test] '[ns1-test ns2-test] - nil nil '[ns1-test ns2-test ns3 ns4 ns5] '[ns1-test ns2-test] - - ;; specific namespaces - '#{ns3} nil '[ns1-test ns2-test] '[] - '#{ns3 ns4} nil '[ns1-test ns2-test ns3 ns4 ns5] '[ns3 ns4] - - ;; regexes - nil #{#"ns1.*" #"ns3"} '[ns1-test ns2-test ns3 ns4] '[ns1-test ns3] - - ;; both - '#{ns3} '#{#"ns1.*"} '[ns1-test ns2-test ns3 ns4] '[ns1-test ns3])) \ No newline at end of file diff --git a/test-resources/lib_tests/com/rpl/specter/cljs_test_helpers.clj b/test-resources/lib_tests/com/rpl/specter/cljs_test_helpers.clj new file mode 100644 index 00000000..4986c823 --- /dev/null +++ b/test-resources/lib_tests/com/rpl/specter/cljs_test_helpers.clj @@ -0,0 +1,13 @@ +(ns com.rpl.specter.cljs-test-helpers) + +;; it seems like gen/bind and gen/return are a monad (hence the names) +(defmacro for-all+ [bindings & body] + (let [parts (partition 2 bindings) + vars (vec (map first parts)) + genned (reduce + (fn [curr [v code]] + `(clojure.test.check.generators/bind ~code (fn [~v] ~curr))) + `(clojure.test.check.generators/return ~vars) + (reverse parts))] + `(clojure.test.check.properties/for-all [~vars ~genned] + ~@body))) diff --git a/test-resources/lib_tests/com/rpl/specter/cljs_test_runner.cljs b/test-resources/lib_tests/com/rpl/specter/cljs_test_runner.cljs new file mode 100644 index 00000000..49c06c26 --- /dev/null +++ b/test-resources/lib_tests/com/rpl/specter/cljs_test_runner.cljs @@ -0,0 +1,7 @@ +(ns com.rpl.specter.cljs-test-runner + (:require [doo.runner :refer-macros [doo-tests]] + [com.rpl.specter.core-test] + [com.rpl.specter.zipper-test])) + +(doo-tests 'com.rpl.specter.core-test + 'com.rpl.specter.zipper-test) diff --git a/test-resources/lib_tests/com/rpl/specter/core_test.cljc b/test-resources/lib_tests/com/rpl/specter/core_test.cljc new file mode 100644 index 00000000..1ba123cd --- /dev/null +++ b/test-resources/lib_tests/com/rpl/specter/core_test.cljc @@ -0,0 +1,1704 @@ +(ns com.rpl.specter.core-test + #?(:cljs (:require-macros + [cljs.test :refer [is deftest]] + [clojure.test.check.clojure-test :refer [defspec]] + [com.rpl.specter.cljs-test-helpers :refer [for-all+]] + [com.rpl.specter.test-helpers :refer [ic-test]] + [com.rpl.specter + :refer [defprotocolpath defnav extend-protocolpath + nav declarepath providepath select select-one select-one! + select-first transform setval replace-in + select-any selected-any? collected? traverse + multi-transform path dynamicnav recursive-path + defdynamicnav traverse-all satisfies-protpath? end-fn + vtransform]])) + (:use + #?(:clj [clojure.test :only [deftest is]]) + #?(:clj [clojure.test.check.clojure-test :only [defspec]]) + #?(:clj [com.rpl.specter.test-helpers :only [for-all+ ic-test]]) + #?(:clj [com.rpl.specter + :only [defprotocolpath defnav extend-protocolpath + nav declarepath providepath select select-one select-one! + select-first transform setval replace-in + select-any selected-any? collected? traverse + multi-transform path dynamicnav recursive-path + defdynamicnav traverse-all satisfies-protpath? end-fn + vtransform]])) + + + + (:require #?(:clj [clojure.test.check.generators :as gen]) + #?(:clj [clojure.test.check.properties :as prop]) + #?(:cljs [clojure.test.check :as tc]) + #?(:cljs [clojure.test.check.generators :as gen]) + #?(:cljs [clojure.test.check.properties :as prop :include-macros true]) + [com.rpl.specter :as s] + [com.rpl.specter.transients :as t] + [clojure.set :as set])) + +;;TODO: +;; test walk, codewalk + +(defn limit-size [n {gen :gen}] + (gen/->Generator + (fn [rnd _size] + (gen rnd (if (< _size n) _size n))))) + +(defn gen-map-with-keys [key-gen val-gen & keys] + (gen/bind (gen/map key-gen val-gen) + (fn [m] + (gen/bind + (apply gen/hash-map (mapcat (fn [k] [k val-gen]) keys)) + (fn [m2] + (gen/return (merge m m2))))))) + +(defspec select-all-keyword-filter + (for-all+ + [kw gen/keyword + v (gen/vector (limit-size 5 + (gen-map-with-keys gen/keyword gen/int kw))) + pred (gen/elements [odd? even?])] + (= (select [s/ALL kw pred] v) + (->> v (map kw) (filter pred))))) + + +(defspec select-pos-extreme-pred + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [odd? even?]) + pos (gen/elements [[s/FIRST first] [s/LAST last]])] + (= (select-one [(s/filterer pred) (first pos)] v) + (->> v (filter pred) ((last pos)))))) + + +(defspec select-all-on-map + (for-all+ + [m (limit-size 5 (gen/map gen/keyword gen/int)) + p (gen/elements [s/MAP-VALS [s/ALL s/LAST]])] + (= (select p m) + (for [[k v] m] v)))) + + +(deftest select-one-test + (is (thrown? #?(:clj Exception :cljs js/Error) (select-one [s/ALL even?] [1 2 3 4]))) + (is (= 1 (select-one [s/ALL odd?] [2 4 1 6])))) + + +(deftest select-first-test + (is (= 7 (select-first [(s/filterer odd?) s/ALL #(> % 4)] [3 4 2 3 7 5 9 8]))) + (is (nil? (select-first [s/ALL even?] [1 3 5 9])))) + + +(defspec transform-all-on-map + (for-all+ + [m (limit-size 5 (gen/map gen/keyword gen/int)) + p (gen/elements [s/MAP-VALS [s/ALL s/LAST]])] + (= (transform p inc m) + (into {} (for [[k v] m] [k (inc v)]))))) + + +(defspec transform-all + (for-all+ + [v (gen/vector gen/int)] + (let [v2 (transform [s/ALL] inc v)] + (and (vector? v2) (= v2 (map inc v)))))) + + +(defspec transform-all-list + (for-all+ + [v (gen/list gen/int)] + (let [v2 (transform [s/ALL] inc v)] + (and (seq? v2) (= v2 (map inc v)))))) + + +(defspec transform-all-filter + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [odd? even?]) + action (gen/elements [inc dec])] + (let [v2 (transform [s/ALL pred] action v)] + (= v2 (map (fn [v] (if (pred v) (action v) v)) v))))) + + +(defspec transform-last + (for-all+ + [v (gen/not-empty (gen/vector gen/int)) + pred (gen/elements [inc dec])] + (let [v2 (transform [s/LAST] pred v)] + (= v2 (concat (butlast v) [(pred (last v))]))))) + + +(defspec transform-first + (for-all+ + [v (gen/not-empty (gen/vector gen/int)) + pred (gen/elements [inc dec])] + (let [v2 (transform [s/FIRST] pred v)] + (= v2 (concat [(pred (first v))] (rest v)))))) + + +(defspec transform-filterer-all-equivalency + (prop/for-all + [s (gen/vector gen/int) + target-type (gen/elements ['() []]) + pred (gen/elements [even? odd?]) + updater (gen/elements [inc dec])] + (let [v (into target-type s) + v2 (transform [(s/filterer pred) s/ALL] updater v) + v3 (transform [s/ALL pred] updater v)] + (and (= v2 v3) (= (type v2) (type v3)))))) + + +(defspec transform-with-context + (for-all+ + [kw1 gen/keyword + kw2 gen/keyword + m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2)) + pred (gen/elements [odd? even?])] + (= (transform [(s/collect-one kw2) kw1 pred] + m) + (if (pred (kw1 m)) + (assoc m kw1 (+ (kw1 m) (kw2 m))) + m)))) + + +(defn differing-elements [v1 v2] + (->> (map vector v1 v2) + (map-indexed (fn [i [e1 e2]] + (if (not= e1 e2) + i))) + (filter identity))) + +(defspec transform-last-compound + (for-all+ + [pred (gen/elements [odd? even?]) + v (gen/such-that #(some pred %) (gen/vector gen/int))] + (let [v2 (transform [(s/filterer pred) s/LAST] inc v) + differing-elems (differing-elements v v2)] + (and (= (count v2) (count v)) + (= (count differing-elems) 1) + (every? (complement pred) (drop (first differing-elems) v2)))))) + + +;; max sizes prevent too much data from being generated and keeps test from taking forever +(defspec transform-keyword + (for-all+ + [k1 (limit-size 3 gen/keyword) + k2 (limit-size 3 gen/keyword) + m1 (limit-size 5 + (gen-map-with-keys + gen/keyword + (gen-map-with-keys gen/keyword gen/int k2) + k1)) + pred (gen/elements [inc dec])] + (let [m2 (transform [k1 k2] pred m1)] + (and (= (assoc-in m1 [k1 k2] nil) (assoc-in m2 [k1 k2] nil)) + (= (pred (get-in m1 [k1 k2])) (get-in m2 [k1 k2])))))) + + +(defspec replace-in-test + (for-all+ + [v (gen/vector gen/int)] + (let [res (->> v (map (fn [v] (if (even? v) (inc v) v)))) + user-ret (->> v + (filter even?) + (map (fn [v] [v v])) + (apply concat)) + user-ret (if (empty? user-ret) nil user-ret)] + (= (replace-in [s/ALL even?] (fn [v] [(inc v) [v v]]) v) + [res user-ret])))) + + +(defspec replace-in-custom-merge + (for-all+ + [v (gen/vector gen/int)] + (let [res (->> v (map (fn [v] (if (even? v) (inc v) v)))) + last-even (->> v (filter even?) last) + user-ret (if last-even {:a last-even})] + (= (replace-in [s/ALL even?] (fn [v] [(inc v) v]) v :merge-fn (fn [curr new] + (assoc curr :a new))) + [res user-ret])))) + + +(defspec srange-extremes-test + (for-all+ + [v (gen/vector gen/int) + v2 (gen/vector gen/int)] + (let [b (setval s/BEGINNING v2 v) + e (setval s/END v2 v)] + (and (= b (concat v2 v)) + (= e (concat v v2)))))) + + +(defspec srange-test + (for-all+ + [v (gen/vector gen/int) + b (gen/elements (-> v count inc range)) + e (gen/elements (range b (-> v count inc)))] + + (let [sv (subvec v b e) + predcount (fn [pred v] (->> v (filter pred) count)) + even-count (partial predcount even?) + odd-count (partial predcount odd?) + b (transform (s/srange b e) (fn [r] (filter odd? r)) v)] + (and (= (odd-count v) (odd-count b)) + (= (+ (even-count b) (even-count sv)) + (even-count v)))))) + + +(deftest structure-path-directly-test + (is (= 3 (select-one :b {:a 1 :b 3}))) + (is (= 5 (select-one (s/comp-paths :a :b) {:a {:b 5}})))) + + +(deftest atom-test + (let [v (transform s/ATOM inc (atom 1))] + (is (instance? #?(:clj clojure.lang.Atom :cljs cljs.core/Atom) v)) + (is (= 2 (select-one s/ATOM v) @v)))) + +(defspec view-test + (for-all+ + [i gen/int + afn (gen/elements [inc dec])] + (= (first (select (s/view afn) i)) + (afn i) + (transform (s/view afn) identity i)))) + + +(defspec must-test + (for-all+ + [k1 gen/int + k2 (gen/such-that #(not= k1 %) gen/int) + m (gen-map-with-keys gen/int gen/int k1) + op (gen/elements [inc dec])] + + (let [m (dissoc m k2)] + (and (= (transform (s/must k1) op m) + (transform (s/keypath k1) op m)) + (= (transform (s/must k2) op m) m) + (= (select (s/must k1) m) (select (s/keypath k1) m)) + (empty? (select (s/must k2) m)))))) + + +(defspec parser-test + (for-all+ + [i gen/int + afn (gen/elements [inc dec #(* % 2)]) + bfn (gen/elements [inc dec #(* % 2)]) + cfn (gen/elements [inc dec #(* % 2)])] + (and (= (select-one! (s/parser afn bfn) i) + (afn i)) + (= (transform (s/parser afn bfn) cfn i) + (-> i afn cfn bfn))))) + + +(deftest selected?-test + (is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []] + (setval [s/ALL (s/selected? s/ALL even?) s/END] + [:a] + [[1 3 5] [2] [7 11 4 2] [10 1] []]))) + + (is (= [2 4] (select [s/ALL (s/selected? even?)] [1 2 3 4]))) + (is (= [1 3] (select [s/ALL (s/not-selected? even?)] [1 2 3 4])))) + + +(defspec identity-test + (for-all+ + [i gen/int + afn (gen/elements [inc dec])] + (and (= [i] (select nil i)) + (= (afn i) (transform nil afn i))))) + +(deftest nil-comp-test + (is (= [5] (select (com.rpl.specter.impl/comp-paths* nil) 5)))) + +(defspec putval-test + (for-all+ + [kw gen/keyword + m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw)) + c gen/int] + (= (transform [(s/putval c) kw] + m) + (transform [kw (s/putval c)] + m) + (assoc m kw (+ c (get m kw)))))) + + +(defspec empty-selector-test + (for-all+ + [v (gen/vector gen/int)] + (= [v] + (select [] v) + (select nil v) + (select (s/comp-paths) v) + (select (s/comp-paths nil) v) + (select [nil nil nil] v)))) + + +(defspec empty-selector-transform-test + (for-all+ + [kw gen/keyword + m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))] + (and (= m + (transform nil identity m) + (transform [] identity m) + (transform (s/comp-paths []) identity m) + (transform (s/comp-paths nil nil) identity m)) + + (= (transform kw inc m) + (transform [nil kw] inc m) + (transform (s/comp-paths kw nil) inc m) + (transform (s/comp-paths nil kw nil) inc m))))) + + +(deftest compose-empty-comp-path-test + (let [m {:a 1}] + (is (= [1] + (select [:a (s/comp-paths)] m) + (select [(s/comp-paths) :a] m))))) + + +(defspec mixed-selector-test + (for-all+ + [k1 (limit-size 3 gen/keyword) + k2 (limit-size 3 gen/keyword) + m (limit-size 5 + (gen-map-with-keys + gen/keyword + (gen-map-with-keys gen/keyword gen/int k2) + k1))] + (= [(-> m k1 k2)] + (select [k1 (s/comp-paths k2)] m) + (select [(s/comp-paths k1) k2] m) + (select [(s/comp-paths k1 k2) nil] m) + (select [(s/comp-paths) k1 k2] m) + (select [k1 (s/comp-paths) k2] m)))) + + +(deftest cond-path-test + (is (= [4 2 6 8 10] + (select [s/ALL (s/cond-path even? [(s/view inc) (s/view inc)] + #(= 3 %) (s/view dec))] + [1 2 3 4 5 6 7 8]))) + (is (empty? (select (s/if-path odd? (s/view inc)) 2))) + (is (= [6 2 10 6 14] + (transform [(s/putval 2) + s/ALL + (s/if-path odd? [(s/view inc) (s/view inc)] (s/view dec))] + * + [1 2 3 4 5]))) + + (is (= 2 + (transform [(s/putval 2) + (s/if-path odd? (s/view inc))] + * + 2)))) + + +(defspec cond-path-selector-test + (for-all+ + [k1 (limit-size 3 gen/keyword) + k2 (limit-size 3 gen/keyword) + k3 (limit-size 3 gen/keyword) + m (limit-size 5 + (gen-map-with-keys + gen/keyword + gen/int + k1 + k2 + k3)) + pred (gen/elements [odd? even?])] + + (let [v1 (get m k1) + k (if (pred v1) k2 k3)] + (and + (= (transform (s/if-path [k1 pred] k2 k3) inc m) + (transform k inc m)) + (= (select (s/if-path [k1 pred] k2 k3) m) + (select k m)))))) + + +(deftest optimized-if-path-test + (is (= [-4 -2] (select [s/ALL (s/if-path [even? neg?] s/STAY)] + [1 2 -3 -4 0 -2]))) + (is (= [1 2 -3 4 0 2] (transform [s/ALL (s/if-path [even? neg?] s/STAY)] + - + [1 2 -3 -4 0 -2])))) + + +(defspec multi-path-test + (for-all+ + [k1 (limit-size 3 gen/keyword) + k2 (limit-size 3 gen/keyword) + m (limit-size 5 + (gen-map-with-keys + gen/keyword + gen/int + k1 + k2))] + + (= (transform (s/multi-path k1 k2) inc m) + (->> m + (transform k1 inc) + (transform k2 inc))))) + + +(deftest empty-pos-transform + (is (empty? (select s/FIRST []))) + (is (empty? (select s/LAST []))) + (is (= [] (transform s/FIRST inc []))) + (is (= [] (transform s/LAST inc [])))) + + +(defspec set-filter-test + (for-all+ + [k1 gen/keyword + k2 (gen/such-that #(not= k1 %) gen/keyword) + k3 (gen/such-that (complement #{k1 k2}) gen/keyword) + v (gen/vector (gen/elements [k1 k2 k3]))] + (= (filter #{k1 k2} v) (select [s/ALL #{k1 k2}] v)))) + + +(deftest nil-select-one-test + (is (= nil (select-one! s/ALL [nil]))) + (is (thrown? #?(:clj Exception :cljs js/Error) (select-one! s/ALL [])))) + + + +(defspec transformed-test + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [even? odd?]) + op (gen/elements [inc dec])] + (= (select-one (s/transformed [s/ALL pred] op) v) + (transform [s/ALL pred] op v)))) + + +(defspec basic-parameterized-composition-test + (for-all+ + [k1 (limit-size 3 gen/keyword) + k2 (limit-size 3 gen/keyword) + m1 (limit-size 5 + (gen-map-with-keys + gen/keyword + (gen-map-with-keys gen/keyword gen/int k2) + k1)) + pred (gen/elements [inc dec])] + (let [p (dynamicnav [a b] (path (s/keypath a) (s/keypath b)))] + (and + (= (s/compiled-select (p k1 k2) m1) (select [k1 k2] m1)) + (= (s/compiled-transform (p k1 k2) pred m1) (transform [k1 k2] pred m1)))))) + + +(defspec filterer-param-test + (for-all+ + [k gen/keyword + k2 gen/keyword + v (gen/vector + (limit-size 5 + (gen-map-with-keys + gen/keyword + gen/int + k + k2))) + + pred (gen/elements [odd? even?]) + updater (gen/elements [inc dec])] + (and + (= (select (s/filterer (s/keypath k) pred) v) + (select (s/filterer k pred) v)) + (= (transform [(s/filterer (s/keypath k) pred) s/ALL k2] + updater + v) + (transform [(s/filterer k pred) s/ALL k2] + updater + v))))) + + +(deftest nested-param-paths + (let [p (fn [a b c] + (path + (s/filterer (s/keypath a) + (s/selected? s/ALL + (s/keypath b) + (s/filterer (s/keypath c) even?) + s/ALL)))) + p2 (p :a :b :c) + p3 (s/filterer :a (s/selected? s/ALL :b (s/filterer :c even?) s/ALL)) + data [{:a [{:b [{:c 4 :d 5}]}]} + {:a [{:c 3}]} + {:a [{:b [{:c 7}] :e [1]}]}]] + + (is (= (select p2 data) + (select p3 data) + [[{:a [{:b [{:c 4 :d 5}]}]}]])))) + + + +(defspec subselect-nested-vectors + (for-all+ + [v1 (gen/vector + (gen/vector gen/int))] + (let [path (s/comp-paths (s/subselect s/ALL s/ALL)) + v2 (s/compiled-transform path reverse v1)] + (and + (= (s/compiled-select path v1) [(flatten v1)]) + (= (flatten v1) (reverse (flatten v2))) + (= (map count v1) (map count v2)))))) + +(defspec subselect-param-test + (for-all+ + [k gen/keyword + v (gen/vector + (limit-size 5 + (gen-map-with-keys + gen/keyword + gen/int + k)))] + (and + (= (s/compiled-select (s/subselect s/ALL (s/keypath k)) v) + [(map k v)]) + (let [v2 (s/compiled-transform (s/comp-paths (s/subselect s/ALL (s/keypath k))) + reverse + v)] + (and (= (map k v) (reverse (map k v2))) + (= (map #(dissoc % k) v) + (map #(dissoc % k) v2))))))) ; only key k was touched in any of the maps + + +(defspec param-multi-path-test + (for-all+ + [k1 gen/keyword + k2 gen/keyword + k3 gen/keyword + m (limit-size 5 + (gen-map-with-keys + gen/keyword + gen/int + k1 + k2 + k3)) + + pred1 (gen/elements [odd? even?]) + pred2 (gen/elements [odd? even?]) + updater (gen/elements [inc dec])] + + (let [paths [(path (s/multi-path [(s/keypath k1) pred1] [(s/keypath k2) pred2] k3)) + (path (s/multi-path [k1 pred1] [(s/keypath k2) pred2] (s/keypath k3))) + (path (s/multi-path [(s/keypath k1) pred1] [(s/keypath k2) pred2] (s/keypath k3))) + (s/multi-path [k1 pred1] [k2 pred2] k3) + (path (s/multi-path [k1 pred1] [(s/keypath k2) pred2] k3))]] + + (and + (apply = + (for [p paths] + (select p m))) + + (apply = + (for [p paths] + (transform p updater m))))))) + + +(defspec subset-test + (for-all+ + [s1 (gen/vector (limit-size 5 gen/keyword)) + s2 (gen/vector (limit-size 5 gen/keyword)) + s3 (gen/vector (limit-size 5 gen/int)) + s4 (gen/vector (limit-size 5 gen/keyword))] + (let [s1 (set s1) + s2 (set s1) + s3 (set s1) + s4 (set s1) + combined (set/union s1 s2) + ss (set/union s2 s3)] + (and + (= (transform (s/subset s3) identity combined) combined) + (= (setval (s/subset s3) #{} combined) (set/difference combined s2)) + (= (setval (s/subset s3) s4 combined) (-> combined (set/difference s2) (set/union s4))))))) + + +(deftest submap-test + (is (= [{:foo 1}] + (select [(s/submap [:foo :baz])] {:foo 1 :bar 2}))) + (is (= {:foo 1, :barry 1} + (setval [(s/submap [:bar])] {:barry 1} {:foo 1 :bar 2}))) + (is (= {:bar 1, :foo 2} + (transform [(s/submap [:foo :baz]) s/ALL s/LAST] inc {:foo 1 :bar 1}))) + (is (= {:a {:new 1} + :c {:new 1 + :old 1}} + (setval [s/ALL s/LAST (s/submap [])] {:new 1} {:a nil, :c {:old 1}})))) + +(deftest nil->val-test + (is (= {:a #{:b}} + (setval [:a s/NIL->SET (s/subset #{})] #{:b} nil))) + (is (= {:a #{:b :c :d}} + (setval [:a s/NIL->SET (s/subset #{})] #{:b} {:a #{:c :d}}))) + (is (= {:a [:b]} + (setval [:a s/NIL->VECTOR s/END] [:b] nil)))) + + +(defspec void-test + (for-all+ + [s1 (gen/vector (limit-size 5 gen/int))] + (and + (empty? (select s/STOP s1)) + (empty? (select [s/STOP s/ALL s/ALL s/ALL s/ALL] s1)) + (= s1 (transform s/STOP inc s1)) + (= s1 (transform [s/ALL s/STOP s/ALL] inc s1)) + (= (transform [s/ALL (s/cond-path even? nil odd? s/STOP)] inc s1) + (transform [s/ALL even?] inc s1))))) + + +(deftest stay-continue-tests + (is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b [:a :b]]] + (setval [(s/stay-then-continue s/ALL) s/END] [[:a :b]] [[1 2] [3]]))) + (is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b]] + (setval [(s/continue-then-stay s/ALL) s/END] [[:a :b]] [[1 2] [3]]))) + (is (= [[1 2 3] 1 3] + (select (s/stay-then-continue s/ALL odd?) [1 2 3]))) + (is (= [1 3 [1 2 3]] + (select (s/continue-then-stay s/ALL odd?) [1 2 3])))) + + + +(declarepath MyWalker) + +(providepath MyWalker + (s/if-path vector? + (s/if-path [s/FIRST #(= :abc %)] + (s/continue-then-stay s/ALL MyWalker) + [s/ALL MyWalker]))) + + +(deftest recursive-path-test + (is (= [9 1 10 3 1] + (select [MyWalker s/ALL number?] + [:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]]))) + + (is (= [:bb [:aa 34 [:abc 11 [:ccc 9 8 [:abc 10 2]]]] [:abc 2 [:abc 4]]] + (transform [MyWalker s/ALL number?] inc + [:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]])))) + + +(def map-key-walker + (recursive-path [akey] p + [s/ALL + (s/if-path [s/FIRST #(= % akey)] + s/LAST + [s/LAST p])])) + +(deftest recursive-params-path-test + (is (= #{1 2 3} (set (select (map-key-walker :aaa) + {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}})))) + (is (= {:a {:aaa 4 :b {:c {:aaa 3} :aaa 2}}} + (transform (map-key-walker :aaa) inc + {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}}))) + (is (= {:a {:c {:b "X"}}} + (setval (map-key-walker :b) "X" {:a {:c {:b {:d 1}}}})))) + + +(deftest recursive-params-composable-path-test + (let [p (fn [k k2] (path (s/keypath k) (map-key-walker k2)))] + (is (= [1] (select (p 1 :a) [{:a 3} {:a 1} {:a 2}]))))) + + +(deftest all-map-test + (is (= {3 3} (transform [s/ALL s/FIRST] inc {2 3}))) + (is (= {3 21 4 31} (transform [s/ALL s/ALL] inc {2 20 3 30})))) + + + +(def NestedHigherOrderWalker + (recursive-path [k] p + (s/if-path vector? + (s/if-path [s/FIRST #(= % k)] + (s/continue-then-stay s/ALL p) + [s/ALL p])))) + + +(deftest nested-higher-order-walker-test + (is (= [:q [:abc :I 3] [:ccc [:abc :I] [:abc :I "a" [:abc :I [:abc :I [:d]]]]]] + (setval [(NestedHigherOrderWalker :abc) (s/srange 1 1)] + [:I] + [:q [:abc 3] [:ccc [:abc] [:abc "a" [:abc [:abc [:d]]]]]])))) + + +#?(:clj + (deftest large-params-test + (let [path (apply com.rpl.specter.impl/comp-navs (for [i (range 25)] (s/keypath i))) + m (reduce + (fn [m k] + {k m}) + :a + (reverse (range 25)))] + (is (= :a (select-one path m)))))) + +;;TODO: there's a bug in clojurescript that won't allow +;; non function implementations of IFn to have more than 20 arguments + +#?(:clj + (do + (defprotocolpath AccountPath []) + (defrecord Account [funds]) + (defrecord User [account]) + (defrecord Family [accounts]) + (extend-protocolpath AccountPath User :account Family [:accounts s/ALL]))) + + +#?(:clj + (deftest protocolpath-basic-test + (let [data [(->User (->Account 30)) + (->User (->Account 50)) + (->Family [(->Account 51) (->Account 52)])]] + (is (= [30 50 51 52] + (select [s/ALL AccountPath :funds] data))) + (is (= [(->User (->Account 31)) + (->User (->Account 51)) + (->Family [(->Account 52) (->Account 53)])] + (transform [s/ALL AccountPath :funds] + inc + data)))))) + + +#?(:clj + (do + (defprotocolpath LabeledAccountPath [label]) + (defrecord LabeledUser [account]) + (defrecord LabeledFamily [accounts]) + (extend-protocolpath LabeledAccountPath + LabeledUser [:account (s/keypath label)] + LabeledFamily [:accounts (s/keypath label) s/ALL]))) + + +#?(:clj + (deftest protocolpath-params-test + (let [data [(->LabeledUser {:a (->Account 30)}) + (->LabeledUser {:a (->Account 50)}) + (->LabeledFamily {:a [(->Account 51) (->Account 52)]})]] + (is (= [30 50 51 52] + (select [s/ALL (LabeledAccountPath :a) :funds] data))) + (is (= [(->LabeledUser {:a (->Account 31)}) + (->LabeledUser {:a (->Account 51)}) + (->LabeledFamily {:a [(->Account 52) (->Account 53)]})] + (transform [s/ALL (LabeledAccountPath :a) :funds] + inc + data)))))) + + + +#?(:clj + (do + (defprotocolpath CustomWalker []) + (extend-protocolpath CustomWalker + Object nil + clojure.lang.PersistentHashMap [(s/keypath :a) CustomWalker] + clojure.lang.PersistentArrayMap [(s/keypath :a) CustomWalker] + clojure.lang.PersistentVector [s/ALL CustomWalker]))) + + +#?(:clj + (deftest mixed-rich-regular-protocolpath + (is (= [1 2 3 11 21 22 25] + (select [CustomWalker number?] [{:a [1 2 :c [3]]} [[[[[[11]]] 21 [22 :c 25]]]]]))) + (is (= [2 3 [[[4]] :b 0] {:a 4 :b 10}] + (transform [CustomWalker number?] inc [1 2 [[[3]] :b -1] {:a 3 :b 10}]))))) + + + +#?( + :clj + (defn make-queue [coll] + (reduce + #(conj %1 %2) + clojure.lang.PersistentQueue/EMPTY + coll)) + + :cljs + (defn make-queue [coll] + (reduce + #(conj %1 %2) + #queue [] + coll))) + + +(defspec transform-idempotency 50 + (for-all+ + [v1 (gen/vector gen/int) + l1 (gen/list gen/int) + m1 (gen/map gen/keyword gen/int)] + (let [s1 (set v1) + q1 (make-queue v1) + v2 (transform s/ALL identity v1) + m2 (transform s/ALL identity m1) + s2 (transform s/ALL identity s1) + l2 (transform s/ALL identity l1) + q2 (transform s/ALL identity q1)] + (and + (= v1 v2) + (= (type v1) (type v2)) + (= m1 m2) + (= (type m1) (type m2)) + (= s1 s2) + (= (type s1) (type s2)) + (= l1 l2) + (seq? l2) ; Transformed lists are only guaranteed to impelment ISeq + (= q1 q2) + (= (type q1) (type q2)))))) + +(defn ^:direct-nav double-str-keypath [s1 s2] + (path (s/keypath (str s1 s2)))) + +(defn ^:direct-nav some-keypath + ([] (s/keypath "a")) + ([k1] (s/keypath (str k1 "!"))) + ([k & args] (s/keypath "bbb"))) + +(deftest nav-constructor-test + ;; this also tests that the eval done by clj platform during inline + ;; caching rebinds to the correct namespace since this is run + ;; by clojure.test in a different namespace + (is (= 1 (select-one! (double-str-keypath "a" "b") {"ab" 1 "c" 2}))) + (is (= 1 (select-one! (some-keypath) {"a" 1 "a!" 2 "bbb" 3 "d" 4}))) + (is (= 2 (select-one! (some-keypath "a") {"a" 1 "a!" 2 "bbb" 3 "d" 4}))) + (is (= 3 (select-one! (some-keypath 1 2 3 4 5) {"a" 1 "a!" 2 "bbb" 3 "d" 4})))) + + +(def ^:dynamic *APATH* s/keypath) + +(deftest inline-caching-test + (ic-test + [k] + [s/ALL (s/must k)] + inc + [{:a 1} {:b 2 :c 3} {:a 7 :d -1}] + [[:a] [:b] [:c] [:d] [:e]]) + (ic-test + [] + [s/ALL #{4 5 11} #(> % 2) (fn [e] (< e 7))] + inc + (range 20) + []) + (ic-test + [v] + (if v :a :b) + inc + {:a 1 :b 2} + [[true] [false]]) + (ic-test + [v] + [s/ALL (double-str-keypath v (inc v))] + str + [{"12" :a "1011" :b} {"1011" :c}] + [[1] [10]]) + (ic-test + [k] + (*APATH* k) + str + {:a 1 :b 2} + [[:a] [:b] [:c]]) + + (binding [*APATH* s/must] + (ic-test + [k] + (*APATH* k) + inc + {:a 1 :b 2} + [[:a] [:b] [:c]])) + + (ic-test + [k k2] + [s/ALL (s/selected? (s/must k) #(> % 2)) (s/must k2)] + dec + [{:a 1 :b 2} {:a 10 :b 6} {:c 7 :b 8} {:c 1 :d 9} {:c 3 :d -1}] + [[:a :b] [:b :a] [:c :d] [:b :c]]) + + (ic-test + [] + [(s/transformed s/STAY inc)] + inc + 10 + []) + + + ;; verifying that these don't throw errors + (is (= 1 (select-any (if true :a :b) {:a 1}))) + (is (= 3 (select-any (*APATH* :a) {:a 3}))) + (is (= 2 (select-any [:a (identity even?)] {:a 2}))) + + (is (= [10 11] (select-one! [(s/putval 10) (s/transformed s/STAY #(inc %))] 10))) + + (is (= 2 (let [p :a] (select-one! [p even?] {:a 2})))) + + (is (= [{:a 2}] (let [p :a] (select [s/ALL (s/selected? p even?)] [{:a 2}]))))) + + + +(deftest nested-inline-caching-test + (is (= [[1]] + (let [a :b] + (select + (s/view + (fn [v] + (select [(s/keypath v) (s/keypath a)] + {:a {:b 1}}))) + :a))))) + + + +(defspec continuous-subseqs-filter-equivalence + (for-all+ + [aseq (gen/vector (gen/elements [1 2 3 :a :b :c 4 5 :d :e])) + pred (gen/elements [keyword? number?])] + (= (setval (s/continuous-subseqs pred) nil aseq) + (filter (complement pred) aseq)))) + + +(deftest continuous-subseqs-test + (is (= [1 "ab" 2 3 "c" 4 "def"] + (transform + (s/continuous-subseqs string?) + (fn [s] [(apply str s)]) + [1 "a" "b" 2 3 "c" 4 "d" "e" "f"]))) + + (is (= [[] [2] [4 6]] + (select + [(s/continuous-subseqs number?) (s/filterer even?)] + [1 "a" "b" 2 3 "c" 4 5 6 "d" "e" "f"])))) + + + +;; verifies that late binding of dynamic parameters works correctly +(deftest transformed-inline-caching + (dotimes [i 10] + (is (= [(inc i)] (select (s/transformed s/STAY #(+ % i)) 1))))) + + +;; test for issue #103 +(deftest nil->val-regression-test + (is (= false (transform (s/nil->val true) identity false))) + (is (= false (select-one! (s/nil->val true) false)))) + + +#?(:clj + (deftest all-map-entry + (let [e (transform s/ALL inc (first {1 3}))] + (is (instance? clojure.lang.MapEntry e)) + (is (= 2 (key e))) + (is (= 4 (val e)))))) + + +(deftest select-on-empty-vector + (is (= s/NONE (select-any s/ALL []))) + (is (nil? (select-first s/ALL []))) + (is (nil? (select-one s/ALL []))) + (is (= s/NONE (select-any s/FIRST []))) + (is (= s/NONE (select-any s/LAST []))) + (is (nil? (select-first s/FIRST []))) + (is (nil? (select-one s/FIRST []))) + (is (nil? (select-first s/LAST []))) + (is (nil? (select-one s/LAST [])))) + + +(defspec select-first-one-any-equivalency + (for-all+ + [aval gen/int + apred (gen/elements [even? odd?])] + (let [data [aval] + r1 (select-any [s/ALL (s/pred apred)] data) + r2 (select-first [s/ALL (s/pred apred)] data) + r3 (select-one [s/ALL (s/pred apred)] data) + r4 (first (select [s/ALL (s/pred apred)] data)) + r5 (select-any [s/FIRST (s/pred apred)] data) + r6 (select-any [s/LAST (s/pred apred)] data)] + + (or (and (= r1 s/NONE) (nil? r2) (nil? r3) (nil? r4) + (= r5 s/NONE) (= r6 s/NONE)) + (and (not= r1 s/NONE) (some? r1) (= r1 r2 r3 r4 r5 r6)))))) + + +(deftest select-any-static-fn + (is (= 2 (select-any even? 2))) + (is (= s/NONE (select-any odd? 2)))) + + +(deftest select-any-keywords + (is (= s/NONE (select-any [:a even?] {:a 1}))) + (is (= 2 (select-any [:a even?] {:a 2}))) + (is (= s/NONE (select-any [(s/keypath "a") even?] {"a" 1}))) + (is (= 2 (select-any [(s/keypath "a") even?] {"a" 2}))) + (is (= s/NONE (select-any (s/must :b) {:a 1 :c 3}))) + (is (= 2 (select-any (s/must :b) {:a 1 :b 2 :c 3}))) + (is (= s/NONE (select-any [(s/must :b) odd?] {:a 1 :b 2 :c 3})))) + + +(defspec select-any-ALL + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [even? odd?])] + (let [r1 (select [s/ALL pred] v) + r2 (select-any [s/ALL pred] v)] + (or (and (empty? r1) (= s/NONE r2)) + (contains? (set r1) r2))))) + + +(deftest select-any-beginning-end + (is (= [] (select-any s/BEGINNING [1 2 3]) (select-any s/END [1]))) + (is (= s/NONE (select-any [s/BEGINNING s/STOP] [1 2 3]) (select-any [s/END s/STOP] [2 3])))) + + +(deftest select-any-walker + (let [data [1 [2 3 4] [[6]]]] + (is (= s/NONE (select-any (s/walker keyword?) data))) + (is (= s/NONE (select-any [(s/walker number?) neg?] data))) + (is (#{1 3} (select-any [(s/walker number?) odd?] data))) + (is (#{2 4 6} (select-any [(s/walker number?) even?] data))))) + + +(defspec selected-any?-select-equivalence + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [even? odd?])] + (let [r1 (not (empty? (select [s/ALL pred] v))) + r2 (selected-any? [s/ALL pred] v)] + (= r1 r2)))) + + +(defn div-by-3? [v] + (= 0 (mod v 3))) + +(defspec selected?-filter-equivalence + (for-all+ + [v (gen/vector gen/int) + pred (gen/elements [even? odd?])] + (and + (= (select-any [s/ALL pred] v) + (select-any [s/ALL (s/selected? pred)] v)) + + (= (select-any [s/ALL pred div-by-3?] v) + (select-any [s/ALL (s/selected? pred) div-by-3?] v))))) + + + +(deftest multi-path-select-any-test + (is (= s/NONE (select-any (s/multi-path s/STOP s/STOP) 1))) + (is (= 1 (select-any (s/multi-path s/STAY s/STOP) 1) + (select-any (s/multi-path s/STOP s/STAY) 1) + (select-any (s/multi-path s/STOP s/STAY s/STOP) 1))) + + (is (= s/NONE (select-any [(s/multi-path s/STOP s/STAY) even?] 1)))) + + +(deftest if-path-select-any-test + (is (= s/NONE (select-any (s/if-path even? s/STAY) 1))) + (is (= 2 (select-any (s/if-path even? s/STAY s/STAY) 2))) + (is (= s/NONE (select-any [(s/if-path even? s/STAY s/STAY) odd?] 2))) + (is (= 2 (select-any (s/if-path odd? s/STOP s/STAY) 2))) + (is (= s/NONE (select-any [(s/if-path odd? s/STOP s/STAY) odd?] 2)))) + + +(defspec transient-vector-test + (for-all+ + [v (gen/vector (limit-size 5 gen/int))] + (every? identity + (for [[path transient-path f] + [[s/FIRST t/FIRST! (fnil inc 0)] ;; fnil in case vector is empty + [s/LAST t/LAST! (fnil inc 0)] + [(s/keypath 0) (t/keypath! 0) (fnil inc 0)] + [s/END t/END! #(conj % 1 2 3)]]] + (and (= (s/transform* path f v) + (persistent! (s/transform* transient-path f (transient v)))) + (= (s/select* path v) + (s/select* transient-path (transient v)))))))) + +(defspec transient-map-test + (for-all+ + [m (limit-size 5 (gen/not-empty (gen/map gen/keyword gen/int))) + new-key gen/keyword] + (let [existing-key (first (keys m))] + (every? identity + (for [[path transient-path f] + [[(s/keypath existing-key) (t/keypath! existing-key) inc] + [(s/keypath new-key) (t/keypath! new-key) (constantly 3)] + [(s/submap [existing-key new-key]) + (t/submap! [existing-key new-key]) + (constantly {new-key 1234})]]] + (and (= (s/transform* path f m) + (persistent! (s/transform* transient-path f (transient m)))) + (= (s/select* path m) + (s/select* transient-path (transient m))))))))) + +(defspec meta-test + (for-all+ + [v (gen/vector gen/int) + meta-map (limit-size 5 (gen/map gen/keyword gen/int))] + (= meta-map + (meta (setval s/META meta-map v)) + (first (select s/META (with-meta v meta-map))) + (first (select s/META (setval s/META meta-map v)))))) + + +(deftest beginning-end-all-first-last-on-nil + (is (= [2 3] (setval s/END [2 3] nil) (setval s/BEGINNING [2 3] nil))) + (is (nil? (setval s/FIRST :a nil))) + (is (nil? (setval s/LAST :a nil))) + (is (nil? (transform s/ALL inc nil))) + (is (empty? (select s/FIRST nil))) + (is (empty? (select s/LAST nil))) + (is (empty? (select s/ALL nil)))) + + +(deftest map-vals-nil + (is (= nil (transform s/MAP-VALS inc nil))) + (is (empty? (select s/MAP-VALS nil)))) + + +(defspec dispense-test + (for-all+ + [k1 gen/int + k2 gen/int + k3 gen/int + m (gen-map-with-keys gen/int gen/int k1 k2 k3)] + (= (select [(s/collect-one (s/keypath k1)) + (s/collect-one (s/keypath k2)) + s/DISPENSE + (s/collect-one (s/keypath k2)) + (s/keypath k3)] + m) + (select [(s/collect-one (s/keypath k2)) + (s/keypath k3)] + m)))) + + +(deftest collected?-test + (let [data {:active-id 1 :items [{:id 1 :name "a"} {:id 2 :name "b"}]}] + (is (= {:id 1 :name "a"} + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE] + + data) + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? v (apply = v)) + s/DISPENSE] + + data)))) + + (let [data {:active 3 :items [{:id 1 :val 0} {:id 3 :val 11}]}] + (is (= (transform [:items s/ALL (s/selected? :id #(= % 3)) :val] inc data) + (transform [(s/collect-one :active) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE + :val] + inc + data))))) + + +(defspec traverse-test + (for-all+ + [v (gen/vector gen/int) + p (gen/elements [odd? even?]) + i gen/int] + (and + (= (reduce + (traverse [s/ALL p] v)) + (reduce + (filter p v))) + (= (reduce + i (traverse [s/ALL p] v)) + (reduce + i (filter p v)))))) + +(def KeyAccumWalker + (recursive-path [k] p + (s/if-path (s/must k) + s/STAY + [s/ALL (s/collect-one s/FIRST) s/LAST p]))) + + +(deftest recursive-if-path-select-vals-test + (let [data {"e1" {"e2" {"e1" {:template 1} "e2" {:template 2}}}}] + (is (= [["e1" "e2" "e1" {:template 1}] ["e1" "e2" "e2" {:template 2}]] + (select (KeyAccumWalker :template) data))) + (is (= {"e1" {"e2" {"e1" "e1e2e1" "e2" "e1e2e2"}}} + (transform (KeyAccumWalker :template) + (fn [& all] (apply str (butlast all))) + data))))) + + +(deftest multi-path-vals-test + (is (= {:a 1 :b 6 :c 3} + (transform [(s/multi-path (s/collect-one :a) (s/collect-one :c)) :b] + + + {:a 1 :b 2 :c 3}))) + (is (= [[1 2] [3 2]] + (select [(s/multi-path (s/collect-one :a) (s/collect-one :c)) :b] + {:a 1 :b 2 :c 3})))) + + +(deftest sorted-map-by-transform + (let [amap (sorted-map-by > 1 10 2 20 3 30)] + (is (= [3 2 1] (keys (transform s/MAP-VALS inc amap)))) + (is (= [3 2 1] (keys (transform [s/ALL s/LAST] inc amap)))))) + + +(deftest setval-vals-collection-test + (is (= 2 (setval s/VAL 2 :a)))) + +(defspec multi-transform-test + (for-all+ + [kw1 gen/keyword + kw2 gen/keyword + m (limit-size 5 (gen-map-with-keys gen/keyword gen/int kw1 kw2))] + (= (->> m (transform [(s/keypath kw1) s/VAL] +) (transform (s/keypath kw2) dec)) + (multi-transform + (s/multi-path [(s/keypath kw1) s/VAL (s/terminal +)] + [(s/keypath kw2) (s/terminal dec)]) + m)))) + + +(deftest multi-transform-overrun-error + (is (thrown? #?(:clj Exception :cljs js/Error) (multi-transform s/STAY 3)))) + + +(deftest terminal-val-test + (is (= 3 (multi-transform (s/terminal-val 3) 2))) + (is (= 3 (multi-transform [s/VAL (s/terminal-val 3)] 2)))) + + + +(deftest multi-path-order-test + (is (= 102 + (multi-transform + (s/multi-path + [odd? (s/terminal #(* 2 %))] + [even? (s/terminal-val 100)] + [#(= 100 %) (s/terminal inc)] + [#(= 101 %) (s/terminal inc)]) + 1)))) + + +(defdynamicnav ignorer [a] + s/STAY) + +(deftest dynamic-nav-ignores-dynamic-arg + (let [a 1] + (is (= 1 (select-any (ignorer a) 1))) + (is (= 1 (select-any (ignorer :a) 1))))) + + +(deftest nested-dynamic-nav + (let [data {:a {:a 1 :b 2} :b {:a 3 :b 4}} + afn (fn [a b] (select-any (s/selected? (s/must a) + (s/selected? (s/must b))) + data))] + (is (= data (afn :a :a))) + (is (= s/NONE (afn :a :c))) + (is (= data (afn :a :b))) + (is (= s/NONE (afn :c :a))) + (is (= data (afn :b :a))) + (is (= data (afn :b :b))))) + +(deftest duplicate-map-keys-test + (let [res (setval [s/ALL s/FIRST] "a" {:a 1 :b 2})] + (is (= {"a" 2} res)) + (is (= 1 (count res))))) + +(deftest inline-caching-vector-params-test + (is (= [10 [11]] (multi-transform (s/terminal-val [10 [11]]) :a)))) + +(defn eachnav-fn-test [akey data] + (select-any (s/keypath "a" akey) data)) + +(deftest eachnav-test + (let [data {"a" {"b" 1 "c" 2}}] + (is (= 1 (eachnav-fn-test "b" data))) + (is (= 2 (eachnav-fn-test "c" data))) + )) + +(deftest traversed-test + (is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4])))) + +(defn- predand= [pred v1 v2] + (and (pred v1) + (pred v2) + (= v1 v2))) + +(defn listlike? [v] + (or (list? v) (seq? v))) + +(deftest nthpath-test + (is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4]))) + (is (predand= vector? [1 2 4] (setval (s/nthpath 2) s/NONE [1 2 3 4]))) + (is (predand= (complement vector?) '(1 -2 3 4) (transform (s/nthpath 1) - '(1 2 3 4)))) + (is (predand= (complement vector?) '(1 2 4) (setval (s/nthpath 2) s/NONE '(1 2 3 4)))) + (is (= [0 1 [2 4 4]] (transform (s/nthpath 2 1) inc [0 1 [2 3 4]]))) + ) + +(deftest remove-with-NONE-test + (is (predand= vector? [1 2 3] (setval [s/ALL nil?] s/NONE [1 2 nil 3 nil]))) + (is (predand= listlike? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil)))) + (is (= {:b 2} (setval :a s/NONE {:a 1 :b 2}))) + (is (= {:b 2} (setval (s/must :a) s/NONE {:a 1 :b 2}))) + (is (predand= vector? [1 3] (setval (s/keypath 1) s/NONE [1 2 3]))) + ;; test with PersistentArrayMap + (is (= {:a 1 :c 3} (setval [s/MAP-VALS even?] s/NONE {:a 1 :b 2 :c 3 :d 4}))) + (is (= {:a 1 :c 3} (setval [s/ALL (s/selected? s/LAST even?)] s/NONE {:a 1 :b 2 :c 3 :d 4}))) + ;; test with PersistentHashMap + (let [m (into {} (for [i (range 500)] [i i]))] + (is (= (dissoc m 31) (setval [s/MAP-VALS #(= 31 %)] s/NONE m))) + (is (= (dissoc m 31) (setval [s/ALL (s/selected? s/LAST #(= 31 %))] s/NONE m))) + )) + +(deftest fresh-collected-test + (let [data [{:a 1 :b 2} {:a 3 :b 3}]] + (is (= [[{:a 1 :b 2} 2]] + (select [s/ALL + s/VAL + (s/with-fresh-collected + (s/collect-one :a) + (s/collected? [a] (= a 1))) + :b] + data))) + (is (= [{:a 1 :b 3} {:a 3 :b 3}] + (transform [s/ALL + s/VAL + (s/with-fresh-collected + (s/collect-one :a) + (s/collected? [a] (= a 1))) + :b] + (fn [m v] (+ (:a m) v)) + data + ))) + )) + +(deftest traverse-all-test + (is (= 3 + (transduce (comp (mapcat identity) + (traverse-all :a)) + (completing (fn [r i] (if (= i 4) (reduced r) (+ r i)))) + 0 + [[{:a 1}] [{:a 2}] [{:a 4}] [{:a 5}]]))) + (is (= 6 + (transduce (traverse-all [s/ALL :a]) + + + 0 + [[{:a 1} {:a 2}] [{:a 3}]] + ))) + (is (= [1 2] + (into [] (traverse-all :a) [{:a 1} {:a 2}]))) + ) + +(deftest early-terminate-traverse-test + (is (= 6 + (reduce + (completing (fn [r i] (if (> r 5) (reduced r) (+ r i)))) + 0 + (traverse [s/ALL s/ALL] + [[1 2] [3 4] [5]]))))) + +(deftest select-any-vals-test + (is (= [1 1] (select-any s/VAL 1)))) + +(deftest conditional-vals-test + (is (= 2 (select-any (s/with-fresh-collected + (s/collect-one (s/keypath 0)) + (s/if-path (collected? [n] (even? n)) + (s/keypath 1) + (s/keypath 2))) + [4 2 3]))) + (is (= [4 2 3] + (select-any (s/with-fresh-collected + (s/collect-one (s/keypath 0)) + (s/selected? (collected? [n] (even? n)))) + [4 2 3]))) + ) + +(deftest name-namespace-test + (is (= :a (setval s/NAME "a" :e))) + (is (= :a/b (setval s/NAME "b" :a/e))) + (is (= 'a (setval s/NAME "a" 'e))) + (is (= 'a/b (setval s/NAME "b" 'a/e))) + (is (= :a/e (setval s/NAMESPACE "a" :e))) + (is (= :a/e (setval s/NAMESPACE "a" :f/e))) + (is (= 'a/e (setval s/NAMESPACE "a" 'e))) + (is (= 'a/e (setval s/NAMESPACE "a" 'f/e))) + ) + +(deftest string-navigation-test + (is (= "ad" (setval (s/srange 1 3) "" "abcd"))) + (is (= "abcxd" (setval [(s/srange 1 3) s/END] "x" "abcd"))) + (is (= "bc" (select-any (s/srange 1 3) "abcd"))) + (is (= "ab" (setval s/END "b" "a"))) + (is (= "ba" (setval s/BEGINNING "b" "a"))) + (is (= "" (select-any s/BEGINNING "abc"))) + (is (= "" (select-any s/END "abc"))) + (is (= \a (select-any s/FIRST "abc"))) + (is (= \c (select-any s/LAST "abc"))) + (is (= "qbc" (setval s/FIRST \q "abc"))) + (is (= "abq" (setval s/LAST "q" "abc"))) + ) + +(deftest regex-navigation-test + ;; also test regexes as implicit navs + (is (= (select #"t" "test") ["t" "t"])) + (is (= (select [:a (s/regex-nav #"t")] {:a "test"}) ["t" "t"])) + (is (= (transform (s/regex-nav #"t") clojure.string/capitalize "test") "TesT")) + ;; also test regexes as implicit navs + (is (= (transform [:a #"t"] clojure.string/capitalize {:a "test"}) {:a "TesT"})) + (is (= (transform (s/regex-nav #"\s+\w") clojure.string/triml "Hello World!") "HelloWorld!")) + (is (= (setval (s/regex-nav #"t") "z" "test") "zesz")) + (is (= (setval [:a (s/regex-nav #"t")] "z" {:a "test"}) {:a "zesz"})) + (is (= (transform (s/regex-nav #"aa*") (fn [s] (-> s count str)) "aadt") "2dt")) + (is (= (transform (s/regex-nav #"[Aa]+") (fn [s] (apply str (take (count s) (repeat "@")))) "Amsterdam Aardvarks") "@msterd@m @@rdv@rks")) + (is (= (select [(s/regex-nav #"(\S+):\ (\d+)") (s/nthpath 2)] "Mary: 1st George: 2nd Arthur: 3rd") ["1" "2" "3"])) + (is (= (transform (s/subselect (s/regex-nav #"\d\w+")) reverse "Mary: 1st George: 2nd Arthur: 3rd") "Mary: 3rd George: 2nd Arthur: 1st")) + ) + +(deftest single-value-none-navigators-test + (is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2]))) + (is (predand= listlike? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2)))) + (is (predand= listlike? '(1) (setval s/AFTER-ELEM 1 nil))) + (is (predand= vector? [3 1 2] (setval s/BEFORE-ELEM 3 [1 2]))) + (is (predand= listlike? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2)))) + (is (predand= listlike? '(1) (setval s/BEFORE-ELEM 1 nil))) + (is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2}))) + (is (= #{1} (setval s/NONE-ELEM 1 nil))) + ) + +(deftest subvec-test + (let [v (subvec [1] 0)] + (is (predand= vector? [2] (transform s/FIRST inc v))) + (is (predand= vector? [2] (transform s/LAST inc v))) + (is (predand= vector? [2] (transform s/ALL inc v))) + (is (predand= vector? [0 1] (setval s/BEGINNING [0] v))) + (is (predand= vector? [1 0] (setval s/END [0] v))) + (is (predand= vector? [0 1] (setval s/BEFORE-ELEM 0 v))) + (is (predand= vector? [1 0] (setval s/AFTER-ELEM 0 v))) + (is (predand= vector? [1 0] (setval (s/srange 1 1) [0] v))) + )) + +(defspec map-keys-all-first-equivalence-transform + (for-all+ + [m (limit-size 10 (gen/map gen/int gen/keyword))] + (= (transform s/MAP-KEYS inc m) + (transform [s/ALL s/FIRST] inc m ) + ))) + +(defspec map-keys-all-first-equivalence-select + (for-all+ + [m (limit-size 10 (gen/map gen/int gen/keyword))] + (= (select s/MAP-KEYS m) + (select [s/ALL s/FIRST] m) + ))) + +(defspec remove-first-vector + (for-all+ + [v (limit-size 10 (gen/not-empty (gen/vector gen/int)))] + (let [newv (setval s/FIRST s/NONE v)] + (and (= newv (vec (rest v))) + (vector? newv) + )))) + +(defspec remove-first-list + (for-all+ + [l (limit-size 10 (gen/not-empty (gen/list gen/int)))] + (let [newl (setval s/FIRST s/NONE l)] + (and (= newl (rest l)) + (listlike? newl) + )))) + +(defspec remove-last-vector + (for-all+ + [v (limit-size 10 (gen/not-empty (gen/vector gen/int)))] + (let [newv (setval s/LAST s/NONE v)] + (and (= newv (vec (butlast v))) + (vector? newv) + )))) + +(defspec remove-last-list + (for-all+ + [l (limit-size 10 (gen/not-empty (gen/list gen/int)))] + (let [newl (setval s/LAST s/NONE l) + bl (butlast l)] + (and (or (= newl bl) (and (nil? bl) (= '() newl))) + (seq? newl) + )))) + +(deftest remove-extreme-string + (is (= "b" (setval s/FIRST s/NONE "ab"))) + (is (= "a" (setval s/LAST s/NONE "ab"))) + ) + +(deftest nested-dynamic-arg-test + (let [foo (fn [v] (multi-transform (s/terminal-val [v]) nil))] + (is (= [1] (foo 1))) + (is (= [10] (foo 10))) + )) + +(deftest filterer-remove-test + (is (= [1 :a 3 5] (setval (s/filterer even?) [:a] [1 2 3 4 5]))) + (is (= [1 3 5] (setval (s/filterer even?) [] [1 2 3 4 5]))) + (is (= [1 3 5] (setval (s/filterer even?) nil [1 2 3 4 5]))) + ) + +(deftest helper-preds-test + (let [data [1 2 2 3 4 0]] + (is (= [2 2] (select [s/ALL (s/pred= 2)] data))) + (is (= [1 2 2 0] (select [s/ALL (s/pred< 3)] data))) + (is (= [1 2 2 3 0] (select [s/ALL (s/pred<= 3)] data))) + (is (= [4] (select [s/ALL (s/pred> 3)] data))) + (is (= [3 4] (select [s/ALL (s/pred>= 3)] data))) + )) + +(deftest map-key-test + (is (= {:c 3} (setval (s/map-key :a) :b {:c 3}))) + (is (= {:b 2} (setval (s/map-key :a) :b {:a 2}))) + (is (= {:b 2} (setval (s/map-key :a) :b {:a 2 :b 1}))) + (is (= {:b 2} (setval (s/map-key :a) s/NONE {:a 1 :b 2}))) + ) + +(deftest set-elem-test + (is (= #{:b :d} (setval (s/set-elem :a) :x #{:b :d}))) + (is (= #{:x :a} (setval (s/set-elem :b) :x #{:b :a}))) + (is (= #{:a} (setval (s/set-elem :b) :a #{:b :a}))) + (is (= #{:b} (setval (s/set-elem :a) s/NONE #{:a :b}))) + ) + +;; this function necessary to trigger the bug from happening +(defn inc2 [v] (inc v)) +(deftest dynamic-function-arg-test + (is (= {[2] 4} (let [a 1] (transform (s/keypath [(inc2 a)]) inc {[2] 3})))) + ) + +(defrecord FooW [a b]) + +(deftest walker-test + (is (= [1 2 3 4 5 6] (select (s/walker number?) [{1 2 :b '(3 :c 4)} 5 #{6 :d}]))) + (is (= [{:b '(:c)} #{:d}] (setval (s/walker number?) s/NONE [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}]))) + (is (= [{:q 4 11 :l 2 3 :b '(4 :c 5)} 6 #{7 :d}] + (transform (s/walker number?) inc [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}]))) + (let [f (->FooW 1 2)] + (is (= [[:a 1] [:b 2]] (select (s/walker (complement record?)) f))) + (is (= (assoc f :a! 1 :b! 2) (setval [(s/walker (complement record?)) s/FIRST s/NAME s/END] "!" f))) + (is (= (assoc f :b 1 :c 2) (transform [(s/walker (complement record?)) s/FIRST] (fn [k] (if (= :a k) :b :c)) f))) + )) + +(def MIDDLE + (s/comp-paths + (s/srange-dynamic + (fn [aseq] (long (/ (count aseq) 2))) + (end-fn [aseq s] (if (empty? aseq) 0 (inc s)))) + s/FIRST + )) + +(deftest srange-dynamic-test + (is (= 2 (select-any MIDDLE [1 2 3]))) + (is (identical? s/NONE (select-any MIDDLE []))) + (is (= 1 (select-any MIDDLE [1]))) + (is (= 2 (select-any MIDDLE [1 2]))) + (is (= [1 3 3] (transform MIDDLE inc [1 2 3]))) + ) + +(def ^:dynamic *dvar* :a) + +(defn dvar-tester [] + (select-any *dvar* {:a 1 :b 2})) + +(deftest dynamic-var-ic-test + (is (= 1 (dvar-tester))) + (is (= 2 (binding [*dvar* :b] (dvar-tester)))) + ) + +(deftest before-index-test + (let [data [1 2 3] + datal '(1 2 3) + data-str "abcdef"] + (is (predand= vector? [:a 1 2 3] (setval (s/before-index 0) :a data))) + (is (predand= vector? [1 2 3] (setval (s/before-index 1) s/NONE data))) + (is (predand= vector? [1 :a 2 3] (setval (s/before-index 1) :a data))) + (is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data))) + ; ensure inserting at index 0 in nil structure works, as in previous impl + (is (predand= listlike? '(:a) (setval (s/before-index 0) :a nil))) + (is (predand= listlike? '(:a 1 2 3) (setval (s/before-index 0) :a datal))) + (is (predand= listlike? '(1 :a 2 3) (setval (s/before-index 1) :a datal))) + (is (predand= listlike? '(1 2 3 :a) (setval (s/before-index 3) :a datal))) + (is (predand= string? "abcxdef" (setval (s/before-index 3) (char \x) data-str))) + )) + +(deftest index-nav-test + (let [data [1 2 3 4 5 6] + datal '(1 2 3 4 5 6)] + (is (predand= vector? [3 1 2 4 5 6] (setval (s/index-nav 2) 0 data))) + (is (predand= vector? [1 3 2 4 5 6] (setval (s/index-nav 2) 1 data))) + (is (predand= vector? [1 2 3 4 5 6] (setval (s/index-nav 2) 2 data))) + (is (predand= vector? [1 2 4 5 3 6] (setval (s/index-nav 2) 4 data))) + (is (predand= vector? [1 2 4 5 6 3] (setval (s/index-nav 2) 5 data))) + (is (predand= vector? [6 1 2 3 4 5] (setval (s/index-nav 5) 0 data))) + + (is (predand= listlike? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal))) + (is (predand= listlike? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal))) + (is (predand= listlike? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal))) + (is (predand= listlike? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal))) + (is (predand= listlike? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal))) + (is (predand= listlike? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal))) + )) + +(deftest indexed-vals-test + (let [data [:a :b :c :d :e]] + (is (= [[0 :a] [1 :b] [2 :c] [3 :d] [4 :e]] (select s/INDEXED-VALS data))) + (is (= [:e :d :c :b :a] (setval [s/INDEXED-VALS s/FIRST] 0 data))) + (is (= [:a :b :e :d :c] (setval [s/INDEXED-VALS s/FIRST] 2 data))) + (is (= [:b :a :d :c :e] (transform [s/INDEXED-VALS s/FIRST odd?] dec data))) + (is (= [:a :b :c :d :e] (transform [s/INDEXED-VALS s/FIRST odd?] inc data))) + (is (= [0 2 2 4] (transform [s/INDEXED-VALS s/LAST odd?] inc [0 1 2 3]))) + (is (= [0 1 2 3] (transform [s/INDEXED-VALS (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [2 1 3 0]))) + (is (= [-1 0 1 2 3] (transform [(s/indexed-vals -1) (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [3 -1 0 2 1]))) + (is (= [[1 :a] [2 :b] [3 :c]] (select (s/indexed-vals 1) [:a :b :c]))) + )) + +(deftest other-implicit-navs-test + (is (= 1 (select-any ["a" true \c 10 'd] {"a" {true {\c {10 {'d 1}}}}}))) + ) + +(deftest vterminal-test + (is (= {:a {:b [[1 2] 3]}} + (multi-transform [(s/putval 1) :a (s/putval 2) :b (s/vterminal (fn [vs v] [vs v]))] + {:a {:b 3}}))) + ) + +(deftest vtransform-test + (is (= {:a 6} (vtransform [:a (s/putval 2) (s/putval 3)] (fn [vs v] (+ v (reduce + vs))) {:a 1}))) + ) + +(deftest compact-test + (is (= {} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1}}}))) + (is (= {:a {:d 2}} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1} :d 2}}))) + (let [TREE-VALUES (recursive-path [] p (s/if-path vector? [(s/compact s/ALL) p] s/STAY)) + tree [1 [2 3] [] [4 [[5] [[6]]]]]] + (is (= [2 4 6] (select [TREE-VALUES even?] tree))) + (is (= [1 [3] [[[5]]]] (setval [TREE-VALUES even?] s/NONE tree))) + ) + (is (= [{:a [{:c 1}]}] + (setval [s/ALL (s/compact :a s/ALL :b)] + s/NONE + [{:a [{:b 3}]} + {:a [{:b 2 :c 1}]}]))) + ) + +(deftest class-constant-test + (let [f (fn [p] (fn [v] (str p (inc v))))] + (is (= (str #?(:clj String :cljs js/String) 2) + (multi-transform (s/terminal (f #?(:clj String :cljs js/String))) 1))) + )) + +#?(:clj + (do + (defprotocolpath FooPP) + (extend-protocolpath FooPP String s/STAY) + + (deftest satisfies-protpath-test + (is (satisfies-protpath? FooPP "a")) + (is (not (satisfies-protpath? FooPP 1))) + ))) diff --git a/test-resources/lib_tests/com/rpl/specter/test_helpers.clj b/test-resources/lib_tests/com/rpl/specter/test_helpers.clj new file mode 100644 index 00000000..be177fba --- /dev/null +++ b/test-resources/lib_tests/com/rpl/specter/test_helpers.clj @@ -0,0 +1,36 @@ +(ns com.rpl.specter.test-helpers + (:require [clojure.test.check + [generators :as gen] + [properties :as prop]] + [clojure.test]) + + (:use [com.rpl.specter :only [select transform]] + [com.rpl.specter :only [select* transform*]])) + + +;; it seems like gen/bind and gen/return are a monad (hence the names) +;; this is only for clj (cljs version in different file) +(defmacro for-all+ [bindings & body] + (let [parts (partition 2 bindings) + vars (vec (map first parts)) + genned (reduce + (fn [curr [v code]] + `(gen/bind ~code (fn [~v] ~curr))) + `(gen/return ~vars) + (reverse parts))] + `(prop/for-all [~vars ~genned] + ~@body))) + + +(defmacro ic-test [params-decl apath transform-fn data params] + (let [platform (if (contains? &env :locals) :cljs :clj) + is-sym (if (= platform :clj) 'clojure.test/is 'cljs.test/is)] + `(let [icfnsel# (fn [~@params-decl] (select ~apath ~data)) + icfntran# (fn [~@params-decl] (transform ~apath ~transform-fn ~data)) + regfnsel# (fn [~@params-decl] (select* ~apath ~data)) + regfntran# (fn [~@params-decl] (transform* ~apath ~transform-fn ~data)) + params# (if (empty? ~params) [[]] ~params)] + (dotimes [_# 3] + (doseq [ps# params#] + (~is-sym (= (apply icfnsel# ps#) (apply regfnsel# ps#))) + (~is-sym (= (apply icfntran# ps#) (apply regfntran# ps#)))))))) diff --git a/test-resources/lib_tests/com/rpl/specter/zipper_test.cljc b/test-resources/lib_tests/com/rpl/specter/zipper_test.cljc new file mode 100644 index 00000000..43c21c82 --- /dev/null +++ b/test-resources/lib_tests/com/rpl/specter/zipper_test.cljc @@ -0,0 +1,122 @@ +(ns com.rpl.specter.zipper-test + #?(:cljs (:require-macros + [cljs.test :refer [is deftest]] + [clojure.test.check.clojure-test :refer [defspec]] + [com.rpl.specter.cljs-test-helpers :refer [for-all+]] + [com.rpl.specter + :refer [declarepath providepath select select-one select-one! + select-first transform setval replace-in]])) + + (:use + #?(:clj [clojure.test :only [deftest is]]) + #?(:clj [clojure.test.check.clojure-test :only [defspec]]) + #?(:clj [com.rpl.specter.test-helpers :only [for-all+]]) + #?(:clj [com.rpl.specter + :only [declarepath providepath select select-one select-one! + select-first transform setval replace-in]])) + + (:require #?(:clj [clojure.test.check.generators :as gen]) + #?(:clj [clojure.test.check.properties :as prop]) + #?(:cljs [clojure.test.check :as tc]) + #?(:cljs [clojure.test.check.generators :as gen]) + #?(:cljs [clojure.test.check.properties :as prop :include-macros true]) + [com.rpl.specter :as s] + [com.rpl.specter.zipper :as z])) + +(defspec zipper-end-equivalency-test + (for-all+ + [v (gen/not-empty (gen/vector gen/int)) + i (gen/vector gen/int)] + (= (setval s/END i v) + (setval [z/VECTOR-ZIP z/DOWN z/RIGHTMOST z/INNER-RIGHT] i v)))) + + +(deftest zipper-multi-insert-test + (is (= [1 2 :a :b 3 :a :b 4] + (setval [z/VECTOR-ZIP + z/DOWN + z/RIGHT + z/RIGHT + (s/multi-path z/INNER-RIGHT z/INNER-LEFT)] + + [:a :b] + [1 2 3 4]) + + (setval [z/VECTOR-ZIP + z/DOWN + z/RIGHT + z/RIGHT + (s/multi-path z/INNER-LEFT z/INNER-RIGHT)] + + [:a :b] + [1 2 3 4])))) + + + + +(deftest zipper-down-up-test + (is (= [1 [2 3 5] 6] + (transform [z/VECTOR-ZIP + z/DOWN + z/RIGHT + z/DOWN + z/RIGHT + z/RIGHT + (s/multi-path + s/STAY + [z/UP z/RIGHT]) + z/NODE] + inc + [1 [2 3 4] 5])))) + + + + +(deftest next-terminate-test + (is (= [2 [3 4 [5]] 6] + (transform [z/VECTOR-ZIP z/NEXT-WALK z/NODE number?] + inc + [1 [2 3 [4]] 5]))) + (is (= [1 [3 [[]] 5]] + (setval [z/VECTOR-ZIP + z/NEXT-WALK + (s/selected? z/NODE number? even?) + z/NODE-SEQ] + [] + [1 2 [3 [[4]] 5] 6])))) + + + + +(deftest zipper-nav-stop-test + (is (= [1] + (transform [z/VECTOR-ZIP z/UP z/NODE] inc [1]))) + (is (= [1] + (transform [z/VECTOR-ZIP z/DOWN z/LEFT z/NODE] inc [1]))) + (is (= [1] + (transform [z/VECTOR-ZIP z/DOWN z/RIGHT z/NODE] inc [1]))) + (is (= [] + (transform [z/VECTOR-ZIP z/DOWN z/NODE] inc [])))) + + +(deftest find-first-test + (is (= [1 [3 [[4]] 5] 6] + (setval [z/VECTOR-ZIP + (z/find-first #(and (number? %) (even? %))) + z/NODE-SEQ] + + [] + [1 2 [3 [[4]] 5] 6])))) + + + +(deftest nodeseq-expand-test + (is (= [2 [2] [[4 4 4]] 4 4 4 6] + (transform [z/VECTOR-ZIP + z/NEXT-WALK + (s/selected? z/NODE number? odd?) + (s/collect-one z/NODE) + z/NODE-SEQ] + (fn [v _] + (repeat v (inc v))) + [1 [2] [[3]] 3 6])))) diff --git a/test-resources/lib_tests/honeysql/core_test.cljc b/test-resources/lib_tests/honeysql/core_test.cljc index d1fc2f8b..c5736c6c 100644 --- a/test-resources/lib_tests/honeysql/core_test.cljc +++ b/test-resources/lib_tests/honeysql/core_test.cljc @@ -11,6 +11,17 @@ insert-into with merge-where merge-having]] honeysql.format-test)) +;; BB_TEST_PATCH: must explicitly set data readers +#?(:clj + (do + (require '[honeysql.types]) + (set! *data-readers* {'sql/call honeysql.types/read-sql-call + 'sql/inline honeysql.types/read-sql-inline + 'sql/raw honeysql.types/read-sql-raw + 'sql/param honeysql.types/read-sql-param + 'sql/array honeysql.types/read-sql-array + 'sql/regularize honeysql.format/regularize}))) + ;; TODO: more tests (deftest test-select diff --git a/test-resources/lib_tests/httpkit/client_test.clj b/test-resources/lib_tests/httpkit/client_test.clj index 8a15d811..546ff867 100644 --- a/test-resources/lib_tests/httpkit/client_test.clj +++ b/test-resources/lib_tests/httpkit/client_test.clj @@ -58,3 +58,14 @@ (deftest alter-var-root-test (is (alter-var-root (var client/*default-client*) (constantly sni/default-client)))) + +(deftest query-string-test + (is (= (client/query-string {:k1 "v1" :k2 "v2" :k3 nil :k4 ["v4a" "v4b"] :k5 []}) + "k1=v1&k2=v2&k3=&k4=v4a&k4=v4b&k5=")) + (is (= (client/query-string {:k1 \v :k2 'v2}) + "k1=v&k2=v2"))) + +(deftest url-encode-test + (is (= "AbC" (client/url-encode "AbC"))) + (is (= "%3C%3E%21%40%23%24%25%5E" + (client/url-encode "<>!@#$%^")))) diff --git a/test-resources/lib_tests/integrant/core_test.cljc b/test-resources/lib_tests/integrant/core_test.cljc index 065bb5a5..ceeb60f7 100644 --- a/test-resources/lib_tests/integrant/core_test.cljc +++ b/test-resources/lib_tests/integrant/core_test.cljc @@ -489,7 +489,7 @@ [(f m (keys m) (fn [k v] (last (swap! log conj [:test k v])))) @log])) -(deftest run-test +(deftest run-test* ;; BB-TEST-PATCH: renamed due to conflict with clojure.test (let [config {::a (ig/ref ::b), ::b 1} [system _] (build-log config)] (is (= [nil diff --git a/test-resources/lib_tests/me/raynes/core_test.clj b/test-resources/lib_tests/me/raynes/core_test.clj new file mode 100644 index 00000000..9effc484 --- /dev/null +++ b/test-resources/lib_tests/me/raynes/core_test.clj @@ -0,0 +1,467 @@ +(ns me.raynes.core-test + (:refer-clojure :exclude [name parents]) + (:require [me.raynes.fs :refer :all] + ;; BB-TEST-PATCH: remove compression ns (requires unavailable classes from apache commons) + #_[me.raynes.fs.compression :refer :all] + ;; BB-TEST-PATCH: remove midje (needs currently unavailable classes) and add mock midje ns + #_[midje.sweet :refer :all] + [me.raynes.mock-midje :refer [fact]] + [clojure.java.io :as io] + [clojure.string :as string]) + (:import java.io.File)) + +(def system-tempdir (System/getProperty "java.io.tmpdir")) + +(def fs-supports-symlinks? (not (.startsWith (System/getProperty "os.name") "Windows"))) + +(defn create-walk-dir [] + (let [root (temp-dir "fs-")] + (mkdir (file root "a")) + (mkdir (file root "b")) + (spit (file root "1") "1") + (spit (file root "a" "2") "1") + (spit (file root "b" "3") "1") + root)) + +(fact "Makes paths absolute." + (file ".") => *cwd* + (file "foo") => (io/file *cwd* "foo")) + + +(fact "Expands path to current user." + (let [user (System/getProperty "user.home")] + (expand-home "~") => (file user) + (expand-home (str "~" File/separator "foo")) => (file user "foo"))) + +(fact "Expands to given user." + (let [user (System/getProperty "user.home") + name (System/getProperty "user.name")] + (expand-home (str "~" name)) => (file user) + (expand-home (format "~%s/foo" name)) => (file user "foo"))) + +(fact "Expand a path w/o tilde just returns path" + (let [user (System/getProperty "user.home")] + (expand-home (str user File/separator "foo")) => (io/file user "foo"))) + +;; BB-TEST-PATCH: commented tests use midje functionality that isn't currently converted and/or +;; the compression ns + +;; BB-TEST-PATCH: made binding to adapt paths to bb folder structure +(def libtest-files-path "test-resources/lib_tests/me/raynes/testfiles") + +;(fact (list-dir ".") => (has every? #(instance? File %))) +; +;;; Want to change these files to be tempfiles at some point. +;(when unix-root (against-background +; [(around :contents (let [f (io/file "test/me/raynes/testfiles/bar")] +; (.setExecutable f false) +; (.setReadable f false) +; (.setWritable f false) +; ?form +; (.setExecutable f true) +; (.setReadable f true) +; (.setWritable f true)))] +; (fact +; (executable? "test/me/raynes/testfiles/foo") => true +; (executable? "test/me/raynes/testfiles/bar") => false) +; +; (fact +; (readable? "test/me/raynes/testfiles/foo") => true +; (readable? "test/me/raynes/testfiles/bar") => false) +; +; (fact +; (writeable? "test/me/raynes/testfiles/foo") => true +; (writeable? "test/me/raynes/testfiles/bar") => false))) + +;; BB-TEST-PATCH: update these paths to use bb folder structure + +(fact + (file? (str libtest-files-path "/foo")) => true + (file? ".") => false) + +(fact + (exists? (str libtest-files-path "/foo")) => true + (exists? "ewjgnr4ig43j") => false) + +(fact + (let [f (io/file (str libtest-files-path "/baz"))] + (.createNewFile f) + (delete f) + (exists? f) => false)) + +(fact + (directory? ".") => true + (directory? (str libtest-files-path "/foo")) => false) + +(fact + (file? ".") => false + (file? (str libtest-files-path "/foo")) => true) + +(fact + (let [tmp (temp-file "fs-")] + (exists? tmp) => true + (file? tmp) => true + (delete tmp))) + +(fact + (let [tmp (temp-dir "fs-")] + (exists? tmp) => true + (directory? tmp) => true + (delete tmp))) + +(fact + (let [tmp (ephemeral-file "fs-")] + (exists? tmp) => true + (file? tmp) => true)) ;; is deleted on JVM exit + +(fact + (let [tmp (ephemeral-dir "fs-")] + (exists? tmp) => true + (directory? tmp) => true)) ;; is deleted on JVM exit + +(fact + (absolute "foo") => (io/file *cwd* "foo")) + +(fact + (normalized ".") => *cwd*) + +(fact + (base-name "foo/bar") => "bar" + (base-name "foo/bar.txt" true) => "bar" + (base-name "bar.txt" ".txt") => "bar" + (base-name "foo/bar.txt" ".png") => "bar.txt") + +(fact + (let [tmp (temp-file "fs-")] + (> (mod-time tmp) 0) => true + (delete tmp))) + +(fact + (let [f (temp-file "fs-")] + (spit f "abc") + (size f) => 3 + (delete f))) + +(fact + (let [root (create-walk-dir) + result (delete-dir root)] + (exists? root) => false)) + +(fact + (let [f (temp-file "fs-")] + (delete f) + (mkdir f) + (directory? f) => true + (delete-dir f))) + +(fact + (let [f (temp-file "fs-") + sub (file f "a" "b")] + (delete f) + (mkdirs sub) + (directory? sub) => true + (delete-dir f))) + +;(fact +; (split (file "test/fs")) => (has-suffix ["test" "fs"])) + +(when unix-root + (fact + (split (file "/tmp/foo/bar.txt")) => '("/" "tmp" "foo" "bar.txt") + (split (file "/")) => '("/") + (split "/") => '("/") + (split "") => '(""))) + +(fact + (let [f (temp-file "fs-") + new-f (str f "-new")] + (rename f new-f) + (exists? f) => false + (exists? new-f) => true + (delete new-f))) + +;(fact +; (let [root (create-walk-dir)] +; (walk vector root) => (contains [[root #{"b" "a"} #{"1"}] +; [(file root "a") #{} #{"2"}] +; [(file root "b") #{} #{"3"}]] +; :in-any-order) +; (delete-dir root))) + +(fact + (let [from (temp-file "fs-") + to (temp-file "fs-") + data "What's up Doc?"] + (delete to) + (spit from data) + (copy from to) + (slurp from) => (slurp to) + (delete from) + (delete to))) + +(fact + (let [f (temp-file "fs-") + t (mod-time f)] + (Thread/sleep 1000) + (touch f) + (> (mod-time f) t) => true + (let [t2 3000] + (touch f t2) + (mod-time f) => t2) + (delete f))) + +(fact + (let [f (temp-file "fs-")] + (chmod "+x" f) + (executable? f) => true + (when-not (re-find #"Windows" (System/getProperty "os.name")) + (chmod "-x" f) + (executable? f) => false) + (delete f))) + +(fact + (let [f (temp-file "fs-")] + (chmod "777" f) + (executable? f) => true + (readable? f) => true + (writeable? f) => true + (chmod "000" f) + (when-not (re-find #"Windows" (System/getProperty "os.name")) + (chmod "-x" f) + (executable? f) => false + (readable? f) => false + (writeable? f) => false) + (delete f))) + +;(fact +; (let [from (create-walk-dir) +; to (temp-dir "fs-") +; path (copy-dir from to) +; dest (file to (base-name from))] +; path => dest +; (walk vector to) => (contains [[to #{(base-name from)} #{}] +; [dest #{"b" "a"} #{"1"}] +; [(file dest "a") #{} #{"2"}] +; [(file dest "b") #{} #{"3"}]] +; :in-any-order) +; (delete-dir from) +; (delete-dir to))) +; +;(fact "copy-dir-into works as expected." +; (let [from (create-walk-dir) +; to (temp-dir "fs-")] +; (copy-dir-into from to) +; (walk vector to) => (contains [[(file to) #{"a" "b"} #{"1"}] +; [(file to "a") #{} #{"2"}] +; [(file to "b") #{} #{"3"}]] +; :in-any-order) +; (delete-dir from) +; (delete-dir to))) + +(when (System/getenv "HOME") + (fact + (let [env-home (io/file (System/getenv "HOME"))] + (home) => env-home + (home "") => env-home + (home (System/getProperty "user.name")) => env-home))) + +;(tabular +; (fact (split-ext ?file) => ?ext) +; +; ?file ?ext +; "fs.clj" ["fs" ".clj"] +; "fs." ["fs" "."] +; "fs.clj.bak" ["fs.clj" ".bak"] +; "/path/to/fs" ["fs" nil] +; "" [(base-name (System/getProperty "user.dir")) nil] +; "~user/.bashrc" [".bashrc" nil]) +; +;(tabular +; (fact (extension ?file) => ?ext) +; +; ?file ?ext +; "fs.clj" ".clj" +; "fs." "." +; "fs.clj.bak" ".bak" +; "/path/to/fs" nil +; "" nil +; ".bashrc" nil) +; +;(tabular +; (fact (name ?file) => ?ext) +; +; ?file ?ext +; "fs.clj" "fs" +; "fs." "fs" +; "fs.clj.bak" "fs.clj" +; "/path/to/fs" "fs" +; "" (base-name (System/getProperty "user.dir")) +; ".bashrc" ".bashrc") + +(fact "Can change cwd with with-cwd." + (let [old *cwd*] + (with-cwd "foo" + *cwd* => (io/file old "foo")))) + +(fact "Can change cwd mutably with with-mutable-cwd" + (let [old *cwd*] + (with-mutable-cwd + (chdir "foo") + *cwd* => (io/file old "foo")))) + +;(with-cwd "test/me/raynes/testfiles" +; (fact +; (unzip "ggg.zip" "zggg") +; (exists? "zggg/ggg") => true +; (exists? "zggg/hhh/jjj") => true +; (delete-dir "zggg")) +; +; (fact (zip "fro.zip" ["bbb.txt" "bbb"]) +; (exists? "fro.zip") => true +; (unzip "fro.zip" "fro") +; (exists? "fro/bbb.txt") => true +; (rename "fro.zip" "fro2.zip") => true +; (delete "fro2.zip") +; (delete-dir "fro")) +; +; (fact "about zip round trip" +; (zip "round.zip" ["some.txt" "some text"]) +; (unzip "round.zip" "round") +; (slurp (file "round/some.txt")) => "some text") +; +; (fact "zip-files" +; (zip-files "foobar.zip" ["foo" "bar"]) +; (exists? "foobar.zip") +; (unzip "foobar.zip" "foobar") +; (exists? "foobar/foo") => true +; (exists? "foobar/bar") => true +; (delete "foobar.zip") +; (delete-dir "foobar")) +; +; (fact +; (untar "ggg.tar" "zggg") +; (exists? "zggg/ggg") => true +; (exists? "zggg/hhh/jjj") => true +; (delete-dir "zggg")) +; +; (fact +; (gunzip "ggg.gz" "ggg") +; (exists? "ggg") => true +; (delete "ggg")) +; +; (fact +; (bunzip2 "bbb.bz2" "bbb") +; (exists? "bbb") => true +; (delete "bbb")) +; +; (fact +; (unxz "xxx.xz" "xxx") +; (exists? "xxx") => true +; (delete "xxx")) +; +; (fact "zip-slip vulnerability" +; (unzip "zip-slip.zip" "zip-slip") => (throws Exception "Expanding entry would be created outside target dir") +; (untar "zip-slip.tar" "zip-slip") => (throws Exception "Expanding entry would be created outside target dir") +; (exists? "/tmp/evil.txt") => false +; (delete-dir "zip-slip"))) + +;(let [win-root (when-not unix-root "c:")] +; (fact +; (parents (str win-root "/foo/bar/baz")) => (just [(file (str win-root "/foo")) +; (file (str win-root "/foo/bar")) +; (file (str win-root "/"))] +; :in-any-order) +; (parents (str win-root "/")) => nil)) +; +;(fact +; (child-of? "/foo/bar" "/foo/bar/baz") => truthy +; (child-of? "/foo/bar/baz" "/foo/bar") => falsey) + +(fact + (path-ns "foo/bar/baz_quux.clj") => 'foo.bar.baz-quux) + +;(fact +; (str (ns-path 'foo.bar.baz-quux)) => (has-suffix (string/join File/separator ["foo" "bar" "baz_quux.clj"]))) + +(fact + (let [win-root (when-not unix-root "c:")] + (absolute? (str win-root "/foo/bar")) => true + (absolute? (str win-root "/foo/")) => true + (absolute? "foo/bar") => false + (absolute? "foo/") => false)) + +(defmacro run-java-7-tests [] + (when (try (import '[java.nio.file Files Path LinkOption StandardCopyOption FileAlreadyExistsException] + '[java.nio.file.attribute FileAttribute]) + (catch Exception _ nil)) + '(do + ;; BB-TEST-PATCH: change path to match bb folders + (def test-files-path "test-resources/lib_tests/me/raynes/testfiles") + + (fact + (let [files (find-files test-files-path #"ggg\.*") + gggs (map #(file (str test-files-path "/ggg." %)) '(gz tar zip))] + (every? (set gggs) files) => true)) + + (fact + (let [fs1 (find-files test-files-path #"ggg\.*") + fs2 (find-files* test-files-path #(re-matches #"ggg\.*" (.getName %)))] + (= fs1 fs2) => true)) + + (fact + (let [f (touch (io/file test-files-path ".hidden"))] + (hidden? f) + (delete f))) + + (fact + (let [target (io/file test-files-path "ggg.tar") + hard (link (io/file test-files-path "hard.link") target)] + (file? hard) => true + (delete hard))) + + (when fs-supports-symlinks? + (fact + (let [target (io/file test-files-path "ggg.tar") + soft (sym-link (io/file test-files-path "soft.link") target)] + (file? soft) => true + (link? soft) => true + (= (read-sym-link soft) target) + (delete soft))) + + (fact + (let [soft (sym-link (io/file test-files-path "soft.link") test-files-path)] + (link? soft) => true + (file? soft) => false + (directory? soft) => true + (directory? soft LinkOption/NOFOLLOW_LINKS) => false + (delete soft))) + + (fact + (let [root (create-walk-dir) + soft-a (sym-link (io/file root "soft-a.link") (io/file root "a")) + soft-b (sym-link (io/file root "soft-b.link") (io/file root "b"))] + (delete-dir soft-a LinkOption/NOFOLLOW_LINKS) + (exists? (io/file root "a" "2")) => true + (delete-dir soft-b) + (exists? (io/file root "b" "3")) => false + (delete-dir root) + (exists? root) => false))) + + (fact "`move` moves files" + (let [source (io/file test-files-path "foo") + target (io/file test-files-path "foo.moved") + existing-target (io/file test-files-path "bar")] + (move source target) + (exists? target) => true + (exists? source) => false + (move target source) + (exists? target) => false + (exists? source) => true +; (move source existing-target) => (throws FileAlreadyExistsException) + (copy source target) + (move source target StandardCopyOption/REPLACE_EXISTING) + (exists? target) => true + (exists? source) => false + (move target source)))))) + +(run-java-7-tests) diff --git a/test-resources/lib_tests/me/raynes/mock_midje.clj b/test-resources/lib_tests/me/raynes/mock_midje.clj new file mode 100644 index 00000000..6724496f --- /dev/null +++ b/test-resources/lib_tests/me/raynes/mock_midje.clj @@ -0,0 +1,38 @@ +;; BB-TEST-PATCH: add this file to mock the midje 'fact' macro + +(ns me.raynes.mock-midje + (:require [rewrite-clj.paredit :as rcp] + [rewrite-clj.zip :as rcz] + [clojure.string :as str] + [clojure.test :refer [deftest is testing]])) + +(defn- up-until-nil [z] + (if-let [new-z (rcz/up z)] + (recur new-z) + z)) + +(defn- replace-arrow [form-zipper-at-arrow] + (let [lhs (-> form-zipper-at-arrow rcz/left rcz/node) + rhs (-> form-zipper-at-arrow rcz/right rcz/node)] + (-> form-zipper-at-arrow + (rcp/wrap-around :list) + rcp/slurp-backward + rcp/slurp-forward + rcz/up + (rcz/replace `(is (= ~lhs ~rhs))) + up-until-nil))) + +(defn- replace-arrows [form-zipper] + (if-let [next-arrow (rcz/find-next-value form-zipper rcz/next '=>)] + (let [rd (replace-arrow next-arrow)] + (recur rd)) + (rcz/sexpr form-zipper))) + +(defmacro fact + "mockup of midje's fact that just transforms `=>` into `is` checks and uses deftest" + [& _] ;operating on &form + (let [[nameish body] (if (string? (second &form)) + [(str/replace (second &form) #"[^\w\d]" "-") (nnext &form)] + ["test" (next &form)]) + transformed-body (replace-arrows (rcz/of-string (pr-str body)))] + `(deftest ~(gensym nameish) ~@transformed-body))) diff --git a/test-resources/lib_tests/me/raynes/testfiles/bar b/test-resources/lib_tests/me/raynes/testfiles/bar new file mode 100644 index 00000000..e69de29b diff --git a/test-resources/lib_tests/me/raynes/testfiles/bbb.bz2 b/test-resources/lib_tests/me/raynes/testfiles/bbb.bz2 new file mode 100644 index 00000000..5910bdb8 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/bbb.bz2 differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/foo b/test-resources/lib_tests/me/raynes/testfiles/foo new file mode 100644 index 00000000..e69de29b diff --git a/test-resources/lib_tests/me/raynes/testfiles/ggg.gz b/test-resources/lib_tests/me/raynes/testfiles/ggg.gz new file mode 100644 index 00000000..09cf4842 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/ggg.gz differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/ggg.tar b/test-resources/lib_tests/me/raynes/testfiles/ggg.tar new file mode 100644 index 00000000..c83a6633 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/ggg.tar differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/ggg.zip b/test-resources/lib_tests/me/raynes/testfiles/ggg.zip new file mode 100644 index 00000000..eaddda38 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/ggg.zip differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/xxx.xz b/test-resources/lib_tests/me/raynes/testfiles/xxx.xz new file mode 100644 index 00000000..f36dec0d Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/xxx.xz differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/zip-slip.tar b/test-resources/lib_tests/me/raynes/testfiles/zip-slip.tar new file mode 100644 index 00000000..264b2506 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/zip-slip.tar differ diff --git a/test-resources/lib_tests/me/raynes/testfiles/zip-slip.zip b/test-resources/lib_tests/me/raynes/testfiles/zip-slip.zip new file mode 100644 index 00000000..38b3f499 Binary files /dev/null and b/test-resources/lib_tests/me/raynes/testfiles/zip-slip.zip differ diff --git a/test-resources/lib_tests/slingshot/slingshot_test.clj b/test-resources/lib_tests/slingshot/slingshot_test.clj index f787a862..f9e7c541 100644 --- a/test-resources/lib_tests/slingshot/slingshot_test.clj +++ b/test-resources/lib_tests/slingshot/slingshot_test.clj @@ -36,7 +36,8 @@ [:class-string e#]) ;; by clojure record type - (catch exception-record e# + ;; BB test patch, exception-record != class?, so this expands into incorrect code + #_(catch exception-record e# [:class-exception-record e#]) ;; by key-value diff --git a/test-resources/line_number_test_test.clj b/test-resources/line_number_test_test.clj new file mode 100644 index 00000000..e3d3fdba --- /dev/null +++ b/test-resources/line_number_test_test.clj @@ -0,0 +1,7 @@ +(ns line-number-test-test + (:require [clojure.test :refer [is deftest run-tests]])) + +(deftest test-is + (is false)) + +(run-tests 'line-number-test-test) diff --git a/test-resources/pod b/test-resources/pod new file mode 100755 index 00000000..3dbc993f --- /dev/null +++ b/test-resources/pod @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +dir=$(dirname "$0") + +export BABASHKA_POD=true + +if [[ $BABASHKA_TEST_ENV == "native" ]]; then + exec "${dir}"/../bb "${dir}"/pod.clj +else + exec clojure -M:test-pod +fi diff --git a/test-resources/pod.clj b/test-resources/pod.clj index cd57ea17..698471e0 100644 --- a/test-resources/pod.clj +++ b/test-resources/pod.clj @@ -104,14 +104,14 @@ "id" id}) pod.test-pod/print (do (write - {"out" (pr-str args) + {"out" (prn-str args) "id" id}) (write {"status" ["done"] "id" id})) pod.test-pod/print-err (do (write - {"err" (pr-str args) + {"err" (prn-str args) "id" id}) (write {"status" ["done"] @@ -125,7 +125,8 @@ :shutdown (System/exit 0)))))))) (let [cli-args (set *command-line-args*)] - (if (contains? cli-args "--run-as-pod") + (if (or (= "true" (System/getenv "BABASHKA_POD")) + (contains? cli-args "--run-as-pod")) (do (debug "running pod with cli args" cli-args) (run-pod cli-args)) (let [native? (contains? cli-args "--native") diff --git a/test-resources/pod_tests/bootleg.clj b/test-resources/pod_tests/bootleg.clj new file mode 100644 index 00000000..e8312442 --- /dev/null +++ b/test-resources/pod_tests/bootleg.clj @@ -0,0 +1,7 @@ +(ns pod-tests.bootleg + (:require [pod.retrogradeorbit.bootleg.utils :as utils])) + +(defn -main [& args] + (-> [:div + [:p "Test"]] + (utils/convert-to :html))) diff --git a/test-resources/pod_tests/local.clj b/test-resources/pod_tests/local.clj new file mode 100644 index 00000000..f564bc12 --- /dev/null +++ b/test-resources/pod_tests/local.clj @@ -0,0 +1,5 @@ +(ns pod-tests.local + (:require [pod.test-pod :as pod])) + +(defn -main [& args] + (println (pod/add-sync 40 2))) diff --git a/test/babashka/async_test.clj b/test/babashka/async_test.clj index f2be1a35..7fd1050c 100644 --- a/test/babashka/async_test.clj +++ b/test/babashka/async_test.clj @@ -33,3 +33,12 @@ (is (number? (edn/read-string (test-utils/bb nil " (def ^:dynamic x 0) (binding [x 10] (async/

    Test

    \"\n" + (test-utils/bb nil "-m" "pod-tests.bootleg")))))) + +(deftest ^:skip-windows local-pod-test + (test-utils/with-config + (pr-str '{:paths ["test-resources"] + :pods {pod/test-pod {:path "test-resources/pod"}}}) + (is (= "42\n" (test-utils/bb nil "-m" "pod-tests.local"))))) + +(deftest tag-test + (test-utils/with-config + "{:deps {} + :aliases {:foo {:env-vars {:dude #env \"DUDE\"}}}}" + (is (= 6 (bb "-e" "(+ 1 2 3)"))))) + +(deftest merge-deps-test + (test-utils/with-config + "{:deps {}}" + (is (= {1 {:a 1}} + (bb "-Sdeps" "{:deps {medley/medley {:mvn/version \"1.4.0\"}}}" "-e" "(require 'medley.core) (medley.core/index-by :a [{:a 1}])"))))) diff --git a/test/babashka/classes_test.clj b/test/babashka/classes_test.clj new file mode 100644 index 00000000..31b7872e --- /dev/null +++ b/test/babashka/classes_test.clj @@ -0,0 +1,11 @@ +(ns babashka.classes-test + (:require [babashka.test-utils :as tu] + [clojure.edn :as edn] + [clojure.test :as t :refer [deftest is testing]])) + +(defn bb + [& args] + (edn/read-string (apply tu/bb nil (map pr-str args)))) + +(deftest all-classes-test + (is (true? (bb '(let [classes (babashka.classes/all-classes)] (and (seq classes) (every? class? classes))))))) diff --git a/test/babashka/crypto_test.clj b/test/babashka/crypto_test.clj index 08549691..48503f69 100644 --- a/test/babashka/crypto_test.clj +++ b/test/babashka/crypto_test.clj @@ -17,7 +17,10 @@ (deftest hmac-sha-256-test (let [key-s "some-key" data "some-data" - expected-sha (String. (hmac-sha-256 (.getBytes key-s) data))] + expected-sha (String. (.encode (java.util.Base64/getEncoder) + (hmac-sha-256 (.getBytes key-s) data)) + "utf-8")] + (prn expected-sha) (is (= expected-sha (bb '(do (ns net (:import (javax.crypto Mac) (javax.crypto.spec SecretKeySpec))) @@ -28,4 +31,6 @@ (.doFinal mac (.getBytes data "UTF-8")))) (let [key-s "some-key" data "some-data"] - (String. (hmac-sha-256 (.getBytes key-s) data))))))))) + (String. (.encode (java.util.Base64/getEncoder) + (hmac-sha-256 (.getBytes key-s) data)) + "utf-8")))))))) diff --git a/test/babashka/deps_test.clj b/test/babashka/deps_test.clj index 397a01f9..e130e680 100644 --- a/test/babashka/deps_test.clj +++ b/test/babashka/deps_test.clj @@ -35,6 +35,7 @@ (bb (pr-str `(do (babashka.deps/add-deps '{:deps {babashka/process {:git/url "https://github.com/babashka/process" :sha "4c6699d06b49773d3e5c5b4c11d3334fb78cc996"}}} {:force true :env {"PATH" (System/getenv "PATH") + "JAVA_HOME" (System/getenv "JAVA_HOME") "GITLIBS" ~(str libs-dir)}}) nil))) (bb (pr-str `(do (babashka.deps/add-deps '{:deps {babashka/process {:git/url "https://github.com/babashka/process" :sha "4c6699d06b49773d3e5c5b4c11d3334fb78cc996"}}} {:force true @@ -65,6 +66,14 @@ true (-> (babashka.deps/clojure [\"-M\" \"-e\" \"(+ 1 2 3)\"] {:out :string}) (p/check) :out) +")))) + (is (= "6\n" (test-utils/normalize (bb " +(require '[babashka.deps :as deps]) +(require '[babashka.process :as p]) + +(-> (babashka.deps/clojure {:out :string} \"-M\" \"-e\" \"(+ 1 2 3)\") + (p/check) + :out) ")))) (when-not test-utils/native? (is (thrown-with-msg? Exception #"Option changed" (bb " @@ -86,6 +95,7 @@ true libs-dir2 (fs/file tmp-dir ".gitlibs2") template (pr-str '(do (babashka.deps/clojure ["-Sforce" "-Spath" "-Sdeps" "{:deps {babashka/process {:git/url \"https://github.com/babashka/process\" :sha \"4c6699d06b49773d3e5c5b4c11d3334fb78cc996\"}}}"] {:out :string :env-key {"PATH" (System/getenv "PATH") + "JAVA_HOME" (System/getenv "JAVA_HOME") "GITLIBS" :gitlibs}}) nil))] (bb (-> template (str/replace ":gitlibs" (pr-str (str libs-dir))) (str/replace ":env-key" ":env"))) diff --git a/test/babashka/error_test.clj b/test/babashka/error_test.clj index fba11c14..e82a7175 100644 --- a/test/babashka/error_test.clj +++ b/test/babashka/error_test.clj @@ -1,24 +1,29 @@ (ns babashka.error-test + {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [match?]}}}} (:require [babashka.test-utils :as tu] [clojure.java.io :as io] [clojure.string :as str] - [clojure.test :as t :refer [deftest is testing]])) + [clojure.test :as t :refer [deftest is testing]] + [matcher-combinators.test])) -(defn multiline-equals [s1 s2] - (let [lines-s1 (str/split-lines s1) - lines-s2 (str/split-lines s2) - max-lines (max (count lines-s1) (count lines-s2))] - (run! (fn [i] - (let [l1 (get lines-s1 i) - l2 (get lines-s2 i)] - (if (and l1 l2) - (is (= l1 l2) - (format "Lines did not match.\nLine: %s\nLeft: %s\nRight: %s" - i (pr-str l1) (pr-str l2))) - (is false (format "Out of lines at line: %s.\nLeft: %s\nRight: %s" - i (pr-str l1) (pr-str l2)))))) - (range max-lines)))) +(defmacro multiline-equals [s1 s2] + `(let [lines-s1# (str/split-lines ~s1) + lines-s2# (str/split-lines ~s2) + max-lines# (max (count lines-s1#) (count lines-s2#)) + lines-s1# (take max-lines# lines-s1#) + lines-s2# (take max-lines# lines-s2#)] + (is (~'match? (map str/trimr lines-s1#) (map str/trimr lines-s2#))) + #_(run! (fn [i] + (let [l1 (get lines-s1 i) + l2 (get lines-s2 i)] + (if (and l1 l2) + (is (= l1 l2) + (format "Lines did not match.\nLine: %s\nLeft: %s\nRight: %s" + i (pr-str l1) (pr-str l2))) + (is false (format "Out of lines at line: %s.\nLeft: %s\nRight: %s" + i (pr-str l1) (pr-str l2)))))) + (range max-lines)))) (deftest stacktrace-from-script-test (try (tu/bb nil (.getPath (io/file "test" "babashka" "scripts" "divide_by_zero.bb"))) @@ -122,7 +127,7 @@ Phase: macroexpand ----- Context ------------------------------------------------------------------ 1: (defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1) - ^--- + ^--- ----- Stack trace -------------------------------------------------------------- clojure.core/subs - @@ -140,7 +145,7 @@ Location: :1:35 ----- Context ------------------------------------------------------------------ 1: (defmacro foo [x] `(subs nil ~x)) (foo 1) - ^--- + ^--- ----- Stack trace -------------------------------------------------------------- clojure.core/subs - @@ -156,7 +161,7 @@ Location: :1:15 ----- Context ------------------------------------------------------------------ 1: (defn quux [] (subs nil 1)) (defmacro foo [x & xs] `(do (quux) ~x)) (defn bar [] (foo 1)) (bar) - ^--- + ^--- ----- Stack trace -------------------------------------------------------------- clojure.core/subs - @@ -178,7 +183,11 @@ Location: :1:27 ----- Context ------------------------------------------------------------------ 1: (let [d {:zero 0 :one 1}] (throw (ex-info \"some msg\" d))) - ^--- some msg"))) + ^--- some msg + +----- Stack trace -------------------------------------------------------------- +user - :1:27 +user - :1:1"))) (testing "output of ordinary Exception" (let [output (try (tu/bb nil "(throw (Exception. \"some msg\"))") @@ -191,15 +200,19 @@ Location: :1:1 ----- Context ------------------------------------------------------------------ 1: (throw (Exception. \"some msg\")) - ^--- some msg")))) + ^--- some msg + +----- Stack trace -------------------------------------------------------------- +user - :1:1")))) (deftest debug-exception-print-test (testing "debug mode includes locals and exception data in output" (let [output (try (tu/bb nil "--debug" "(let [x 1] (/ x 0))") (is false) ; ensure that exception is thrown and we don't get here - (catch Exception e (ex-message e)))] - (is (str/includes? (tu/normalize output) - "----- Error -------------------------------------------------------------------- + (catch Exception e (ex-message e))) + actual-lines (str/split-lines (tu/normalize output))] + (is (match? (take 16 actual-lines) + (str/split-lines "----- Error -------------------------------------------------------------------- Type: java.lang.ArithmeticException Message: Divide by zero Location: :1:12 @@ -211,18 +224,21 @@ Location: :1:12 ----- Stack trace -------------------------------------------------------------- clojure.core// - user - :1:12 +user - :1:1 ----- Exception ---------------------------------------------------------------- -clojure.lang.ExceptionInfo: Divide by zero -{:type :sci/error, :line 1, :column 12, :message \"Divide by zero\","))))) +clojure.lang.ExceptionInfo: Divide by zero"))) + (is (str/includes? (nth actual-lines 16) + "{:type :sci/error, :line 1, :column 12, :message \"Divide by zero\","))))) (deftest macro-test (let [output (try (tu/bb nil "--debug" "(defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1)") (is false) (catch Exception e (ex-message e))) - output (tu/normalize output)] - (is (str/includes? output - "----- Error -------------------------------------------------------------------- + output (tu/normalize output) + actual-lines (str/join "\n" (take 17 (str/split-lines output)))] + (multiline-equals actual-lines + "----- Error -------------------------------------------------------------------- Type: java.lang.NullPointerException Location: :1:19 Phase: macroexpand @@ -238,8 +254,7 @@ user/foo - :1:1 user - :1:45 ----- Exception ---------------------------------------------------------------- -clojure.lang.ExceptionInfo: null -{:type :sci/error, :line 1, :column 19")))) +clojure.lang.ExceptionInfo: null"))) (deftest native-stacktrace-test (let [output (try (tu/bb nil "(merge 1 2 3)") diff --git a/test/babashka/exec_test.clj b/test/babashka/exec_test.clj new file mode 100644 index 00000000..4a990520 --- /dev/null +++ b/test/babashka/exec_test.clj @@ -0,0 +1,49 @@ +(ns babashka.exec-test + (:require + [babashka.test-utils :as u] + [cheshire.core :as cheshire] + [clojure.edn :as edn] + [clojure.test :as t :refer [deftest is testing]])) + +(defn bb [& args] + (apply u/bb nil args)) + +(deftest exec-test + (is (= {:foo 1} (edn/read-string (bb "-x" "prn" "--foo" "1")))) + (is (thrown? Exception (bb "-x" "json/generate-string" "--foo" "1"))) + (is (= {:foo 1} (cheshire/parse-string + (edn/read-string + (bb "-x" "cheshire.core/generate-string" "--foo" "1")) true)))) + +(deftest tasks-exec-test + (u/with-config + "{:deps {} + :tasks {foo (exec 'clojure.core/prn)}}" + (is (= {:dude 1} (edn/read-string (bb "run" "foo" "--dude" "1"))))) + (u/with-config + "{:deps {} + :tasks {foo (exec 'clojure.core/prn)}}" + (is (= {:dude 1} (edn/read-string (bb "run" "foo" "--dude" "1"))))) + (u/with-config + "{:deps {} + :tasks {foo {:org.babashka/cli {:coerce {:dude []}} + :task (exec 'clojure.core/prn)}}}" + (is (= {:dude [1]} (edn/read-string (bb "run" "foo" "--dude" "1"))))) + (u/with-config + "{:deps {} + :tasks {foo {:task (exec 'babashka.exec-test/exec-test)}}}" + (is (= {:foo [1], :bar :yeah} + (edn/read-string (bb "-cp" "test-resources" "run" "foo" "--foo" "1" "--bar" "yeah"))))) + (testing "task exec args" + (u/with-config + "{:deps {} + :tasks {foo {:exec-args {:foo :bar} + :task (exec 'babashka.exec-test/exec-test)}}}" + (is (= {:foo :bar, :bar :yeah} + (edn/read-string (bb "-cp" "test-resources" "run" "foo" "--bar" "yeah")))))) + (testing "meta" + (u/with-config + "{:deps {} + :tasks {foo {:task (exec 'babashka.exec-test/exec-test)}}}" + (is (= #:org.babashka{:cli {:args ["dude"]}} + (edn/read-string (bb "-cp" "test-resources" "run" "foo" "dude" "--bar" "yeah" "--meta"))))))) diff --git a/test/babashka/impl/socket_repl_test.clj b/test/babashka/impl/socket_repl_test.clj index 5e6e587c..5699b456 100644 --- a/test/babashka/impl/socket_repl_test.clj +++ b/test/babashka/impl/socket_repl_test.clj @@ -10,6 +10,7 @@ [clojure.java.io :as io] [clojure.string :as str] [clojure.test :as t :refer [deftest is testing]] + [sci.ctx-store :as ctx-store] [sci.impl.opts :refer [init]])) (set! *warn-on-reflection* true) @@ -19,7 +20,7 @@ reader (io/reader socket) sw (java.io.StringWriter.) writer (io/writer socket)] - (binding [*out* writer] + (binding [*out* writer] (println (str expr "\n"))) (loop [] (when-let [l (try (.readLine ^java.io.BufferedReader reader) @@ -48,7 +49,7 @@ (if tu/jvm? (let [ctx (init {:namespaces {'clojure.core.server clojure-core-server-namespace} :features #{:bb}})] - (vreset! common/ctx ctx) + (ctx-store/reset-ctx! ctx) (start-repl! "0.0.0.0:1666" ctx)) (do (vreset! server-process (p/process ["./bb" "socket-repl" "localhost:1666"])) @@ -68,7 +69,7 @@ (finally (if tu/jvm? (do (stop-repl!) - (vreset! common/ctx nil) + (ctx-store/reset-ctx! nil) (Thread/sleep 100)) (p/destroy-tree @server-process)))))) @@ -81,7 +82,7 @@ :env (atom {}) :namespaces {'clojure.core.server clojure-core-server-namespace} :features #{:bb}})] - (vreset! common/ctx ctx) + (ctx-store/reset-ctx! ctx) (start-repl! "{:address \"localhost\" :accept clojure.core.server/repl :port 1666}" ctx)) (do (vreset! server-process @@ -92,7 +93,7 @@ (finally (if tu/jvm? (do (stop-repl!) - (vreset! common/ctx nil) + (ctx-store/reset-ctx! nil) (Thread/sleep 100)) (p/destroy-tree @server-process)))))) @@ -105,7 +106,7 @@ :env (atom {}) :namespaces {'clojure.core.server clojure-core-server-namespace} :features #{:bb}})] - (vreset! common/ctx ctx) + (ctx-store/reset-ctx! ctx) (start-repl! "{:address \"localhost\" :accept clojure.core.server/io-prepl :port 1666}" ctx)) (do (vreset! server-process @@ -120,7 +121,7 @@ (finally (if tu/jvm? (do (stop-repl!) - (vreset! common/ctx nil) + (ctx-store/reset-ctx! nil) (Thread/sleep 100)) (p/destroy-tree @server-process)))))) diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 976d2644..28102ba8 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -60,7 +60,12 @@ (is (:feature/xml v))) (is (= {:force? true} (parse-opts ["--force"]))) (is (= {:main "foo", :command-line-args '("-h")} (parse-opts ["-m" "foo" "-h"]))) - (is (= {:main "foo", :command-line-args '("-h")} (parse-opts ["-m" "foo" "--" "-h"])))) + (is (= {:main "foo", :command-line-args '("-h")} (parse-opts ["-m" "foo" "--" "-h"]))) + (is (= {:force? true :list-tasks true :command-line-args nil} (parse-opts ["--force" "tasks"]))) + (is (= {:force? true :run "sometask" :command-line-args nil} (parse-opts ["--force" "run" "sometask"]))) + (is (= {:force? true :repl true} (parse-opts ["--force" "repl"]))) + (is (= {:force? true :clojure true :command-line-args '("-M" "-r")} + (parse-opts ["--force" "clojure" "-M" "-r"])))) (deftest version-test (is (= [1 0 0] (main/parse-version "1.0.0-SNAPSHOT"))) @@ -825,6 +830,19 @@ true"))) (is (= :f (bb nil "(first (into-array [:f]))"))) (is (= :f (bb nil "(first (first (into-array [(into-array [:f])])))")))) +(deftest var-names-test + (testing "for all public vars, ns/symbol from ns map matches metadata" + (is (empty? (bb nil (.getPath (io/file "test" "babashka" "scripts" "check_var_names.bb"))))))) + +(deftest clojure-lang-var-mapping-test + (is (= :var (bb nil "(defprotocol Foo (foo [_])) (extend-protocol Foo clojure.lang.Var (foo [_] :var)) (foo #'inc)")))) + +(deftest clojure-ns-test + (is (true? (bb nil "(instance? clojure.lang.Namespace *ns*)")))) + +(deftest index-of-test + (is (= 1 (bb nil "(.indexOf (map inc [1 2 3]) 3)")))) + ;;;; Scratch (comment diff --git a/test/babashka/pprint_test.clj b/test/babashka/pprint_test.clj new file mode 100644 index 00000000..d21c3386 --- /dev/null +++ b/test/babashka/pprint_test.clj @@ -0,0 +1,22 @@ +(ns babashka.pprint-test + (:require + [babashka.test-utils :as test-utils] + [clojure.string :as str] + [clojure.test :as test :refer [deftest is]])) + +(defn bb [& args] + (str/trim (apply test-utils/bb (map str args)))) + +(deftest print-length-test + (is (= "(0 1 2 3 4 5 6 7 8 9 ...)" + (bb "-e" "(set! *print-length* 10) (clojure.pprint/pprint (range 20))")))) + +(deftest print-namespaced-map-test + (test/testing + "Testing disabling of printing namespace maps..." + (is (= "{:a/x 1, :a/y 2, :a/z {:b/x 10, :b/y 20}}" + (bb "-e" "(binding [*print-namespace-maps* false] (clojure.pprint/pprint {:a/x 1 :a/y 2 :a/z {:b/x 10 :b/y 20}}))")))) + (test/testing + "Testing manually enabling printing namespace maps..." + (is (= "#:a{:x 1, :y 2, :z #:b{:x 10, :y 20}}" + (bb "-e" "(binding [*print-namespace-maps* true] (clojure.pprint/pprint {:a/x 1 :a/y 2 :a/z {:b/x 10 :b/y 20}}))"))))) diff --git a/test/babashka/proxy_test.clj b/test/babashka/proxy_test.clj index 496f6449..109f15e0 100644 --- a/test/babashka/proxy_test.clj +++ b/test/babashka/proxy_test.clj @@ -78,3 +78,51 @@ true false] (bb (with-out-str (clojure.pprint/pprint code)))))) + +(deftest PipedInputStream-PipedOutputStream-proxy-test + (is (= {:available 1 + :read-result -1 + :byte-read 10 + :array-read '(0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0) + :instance? true} + (bb (with-out-str + (clojure.pprint/pprint + '(let [ins (proxy [java.io.PipedInputStream] [] + (available [] 1) + (close [] nil) + (read + ([] 10) + ([byte-arr off len] + (aset byte-arr off (byte 10)) + -1)) + (receive [b] + nil)) + arr (byte-array 16) + ] + {:available (.available ins) + :read-result (.read ins arr 6 2) + :byte-read (.read ins) + :array-read (seq arr) + :instance? (instance? java.io.PipedInputStream ins)})))))) + + (is (= {:instance? true + :arr '(1 2 3 4 5 0 0 0) + :arr2 '(10)} + (bb (with-out-str + (clojure.pprint/pprint + '(let [arr (byte-array 8) + arr2 (byte-array 1) + outs (proxy [java.io.PipedOutputStream] [] + (close [] nil) + (connect [sink] nil) + (flush [] nil) + (write + ([b] (aset arr2 0 (byte b))) + ([byte-arr off len] + (doseq [n (range len)] + (aset arr n (aget byte-arr (+ off n)))))))] + (.write outs (int 10)) + (.write outs (byte-array [0 0 0 1 2 3 4 5 0 0 0]) 3 5) + {:instance? (instance? java.io.PipedOutputStream outs) + :arr (seq arr) + :arr2 (seq arr2)}))))))) diff --git a/test/babashka/reify_test.clj b/test/babashka/reify_test.clj index 72624c65..a9911dd7 100644 --- a/test/babashka/reify_test.clj +++ b/test/babashka/reify_test.clj @@ -2,6 +2,7 @@ (:require [babashka.test-utils :as test-utils] [clojure.edn :as edn] + [clojure.string :as str] [clojure.test :as test :refer [deftest is testing]])) (defn bb [input & args] @@ -51,12 +52,22 @@ ]"))))) (deftest reify-object + (testing "empty methods" + (is (str/starts-with? + (bb nil " +(str (reify Object))") + "babashka.impl.reify"))) (testing "toString" (is (= ":foo" (bb nil " (def m (reify Object (toString [_] (str :foo)))) (str m) +")))) + (testing "toString + protocol" + (is (= ":dude1:dude2" + (bb nil " +(defprotocol Dude (dude [_])) (def obj (reify Object (toString [_] (str :dude1)) Dude (dude [_] :dude2))) (str (str obj) (dude obj)) ")))) (testing "Hashcode still works when only overriding toString" (is (number? @@ -97,3 +108,48 @@ [x y] (bb nil prog)] (is (pos? x)) (is (zero? y)))) + +(deftest reify-default-method-test + (let [prog '(do (def iter (let [coll [:a :b :c] idx (volatile! -1)] + (reify java.util.Iterator (hasNext [_] (< @idx 2)) + (next [_] (nth coll (vswap! idx inc)))))) + (def res (volatile! [])) + (vswap! res conj (.hasNext iter)) + (vswap! res conj (.next iter)) + (.forEachRemaining + iter (reify java.util.function.Consumer (accept [_ x] (vswap! res conj x)))) + (= [true :a :b :c] @res))] + (is (true? (bb nil prog))))) + +(deftest reify-multiple-interfaces-test + (testing "throws exception" + (is (thrown? + clojure.lang.ExceptionInfo + (bb nil " +(reify + java.lang.Object (toString [_] \"foo\") + clojure.lang.Seqable (seq [_] '(1 2 3)))"))))) + +(deftest reify-runnable-and-garbage-collection-test + (is (bb nil " +(def cleaner (java.lang.ref.Cleaner/create)) +(def deleted? (atom false)) +(defn make-cleanable-ref [] + (let [obj (Object.)] + (.register cleaner obj + (reify java.lang.Runnable + (run [_] + (reset! deleted? true)))) + nil)) +(defn force-gc [] + (let [t (atom (Object.)) + wr (java.lang.ref.WeakReference. @t)] + (reset! t nil) + (while (or (.get wr) + (not @deleted?)) + (System/gc) + (System/runFinalization)))) +(make-cleanable-ref) +(force-gc) +@deleted? +"))) diff --git a/test/babashka/scripts/check_var_names.bb b/test/babashka/scripts/check_var_names.bb new file mode 100644 index 00000000..a689cb05 --- /dev/null +++ b/test/babashka/scripts/check_var_names.bb @@ -0,0 +1,14 @@ +(require '[clojure.string :as str]) +(let [ns-maps (->> (all-ns) + (map (fn [nmspc] [(ns-name nmspc) (ns-publics nmspc)])) + (into {})) ; a map of { ns-name {symbol var, ...}} + ns-maps (update ns-maps 'user #(dissoc % '*input*))] ; *input* is a special case that we'll skip over + (->> + (for [[ns-nm _] ns-maps + [sym vr] (ns-maps ns-nm) + :let [{var-meta-ns :ns, var-meta-name :name} (meta vr) + var-meta-ns-name (some-> var-meta-ns ns-name)]] + ; build a seq of maps containing the ns/symbol from the ns and the ns/symbol from the var's metadata + {:actual-ns ns-nm :actual-ns-symbol sym :var-meta-ns var-meta-ns-name :var-meta-name var-meta-name}) + ; and remove the matches + (remove #(and (= (:actual-ns %) (:var-meta-ns %)) (= (:actual-ns-symbol %) (:var-meta-name %)))))) diff --git a/test/babashka/test_test.clj b/test/babashka/test_test.clj index d88c3fd5..b0ad6da5 100644 --- a/test/babashka/test_test.clj +++ b/test/babashka/test_test.clj @@ -108,3 +108,13 @@ (t/with-test-out (t/run-tests *ns*))) (str/includes? (str sw) \"Ran 1 tests containing 2 assertions.\"))")] (is (str/includes? output "true")))) + +(deftest line-number-test + (is (str/includes? (bb "test-resources/line_number_test_test.clj") + "line_number_test_test.clj:4"))) + +(deftest testing-vars-str-test + (is (str/includes? + (bb "(clojure.test/testing-vars-str {:file \"x\" :line 1})") + "() (x:1)") + "includes explicit line number + file name in test report")) diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index d9c24cd1..9a856a96 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -81,8 +81,8 @@ (do (println (str os)) (throw (ex-info (str es) - {:stdout (str os) - :stderr (str es)})))))) + {:stdout (str os) + :stderr (str es)})))))) (finally (when (string? input-or-opts) (vars/bindRoot sci/in *in*)) (vars/bindRoot sci/out *out*) diff --git a/test/babashka/uberjar_test.clj b/test/babashka/uberjar_test.clj index 66d94a13..4476f45a 100644 --- a/test/babashka/uberjar_test.clj +++ b/test/babashka/uberjar_test.clj @@ -1,16 +1,23 @@ (ns babashka.uberjar-test (:require - [babashka.main :as main] - [babashka.test-utils :as tu] - [clojure.string :as str] - [clojure.test :as t :refer [deftest is testing]])) + [babashka.fs :as fs] + [babashka.main :as main] + [babashka.test-utils :as tu] + [clojure.edn :as edn] + [clojure.string :as str] + [clojure.test :as t :refer [deftest is testing]]) + (:import (java.io File InputStreamReader PushbackReader) + (java.util.jar JarFile))) + +(defn jar-entries [jar] + (with-open [jar-file (JarFile. jar)] + (doall (enumeration-seq (.entries jar-file))))) (defn count-entries [jar] - (with-open [jar-file (java.util.jar.JarFile. jar)] - (count (map #_prn - identity - (enumeration-seq - (.entries jar-file)))))) + (-> jar jar-entries count)) + +(defn get-entry [^File jar entry-name] + (-> jar JarFile. (.getEntry entry-name))) (deftest uberjar-test (testing "uberjar with --main" @@ -54,6 +61,24 @@ ;; Only a manifest entry is added (is (< (count-entries path) 3))))) +(deftest uberjar-with-pods-test + (testing "jar contains bb.edn w/ only :pods when bb.edn has :pods" + (let [tmp-file (java.io.File/createTempFile "uber" ".jar") + path (.getPath tmp-file)] + (.deleteOnExit tmp-file) + (let [config {:paths ["test-resources/babashka/uberjar/src"] + :deps '{local/deps {:local/root "test-resources/babashka/uberjar"}} + :pods (cond-> '{org.babashka/go-sqlite3 {:version "0.1.0"}} + (not (fs/windows?)) (assoc 'pod/test-pod {:path "test-resources/pod"}))}] + (tu/with-config config + (tu/bb nil "uberjar" path "-m" "my.main-pod") + (let [bb-edn-entry (get-entry tmp-file "META-INF/bb.edn") + bb-edn (-> path JarFile. (.getInputStream bb-edn-entry) + InputStreamReader. PushbackReader. edn/read)] + (is (= #{:pods} (-> bb-edn keys set))) + (is (= (:pods config) (:pods bb-edn)))) + (is (str/includes? (tu/bb nil "--jar" path) "3"))))))) + (deftest throw-on-empty-classpath ;; this test fails the windows native test in CI (when-not main/windows? diff --git a/test/babashka/uberscript_test.clj b/test/babashka/uberscript_test.clj index 4fbc1af8..9a111b18 100644 --- a/test/babashka/uberscript_test.clj +++ b/test/babashka/uberscript_test.clj @@ -1,7 +1,8 @@ (ns babashka.uberscript-test (:require [babashka.test-utils :as tu] - [clojure.test :as t :refer [deftest is]])) + [clojure.test :as t :refer [deftest is]] + [clojure.string :as str])) (deftest basic-test (let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")] @@ -23,3 +24,12 @@ (is (= ":clojure.string/foo\ntrue\n(\"1\" \"2\" \"3\" \"4\")\n" (tu/bb nil "--file" (.getPath tmp-file) "1" "2" "3" "4")))))) +(deftest pods-test + (let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")] + (.deleteOnExit tmp-file) + (tu/with-config (pr-str '{:paths ["test-resources/babashka/uberscript/src"] + :pods {org.babashka/go-sqlite3 {:version "0.1.0"}}}) + (is (empty? (tu/bb nil "uberscript" (.getPath tmp-file) "-m" "my.main-pod"))) + (is (= 1 (count (re-seq #"load-pod 'org.babashka/go-sqlite3" + (str/join (str/split-lines (slurp tmp-file)))))))) + (is (str/includes? (tu/bb nil "--file" (.getPath tmp-file)) "3"))))