├── snoop.edn ├── .lsp └── config.edn ├── src └── crypticbutter │ ├── snoop │ ├── impl │ │ ├── cljs.clj │ │ └── cljs.cljs │ ├── stubs │ │ └── cljs │ │ │ └── env.cljc │ └── config.cljc │ └── snoop.cljc ├── bin ├── kaocha ├── ci-cljs-test.sh └── install-bb.sh ├── .clj-kondo ├── babashka │ └── fs │ │ └── config.edn ├── config.edn └── rewrite-clj │ └── rewrite-clj │ └── config.edn ├── pom.properties ├── test ├── cljs_main.cljs ├── test_runner │ └── hooks.clj └── crypticbutter │ └── snoop_test.cljc ├── resources └── clj-kondo.exports │ └── com.crypticbutter │ └── snoop │ ├── config.edn │ └── clj_kondo │ └── crypticbutter │ └── snoop.clj ├── package.json ├── docs ├── testing.org └── development.adoc ├── tests.edn ├── .gitignore ├── bb.edn ├── CHANGELOG.org ├── shadow-cljs.edn ├── .github └── workflows │ └── ci.yml ├── pom.xml ├── deps.edn ├── README.md ├── LICENSE └── yarn.lock /snoop.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.lsp/config.edn: -------------------------------------------------------------------------------- 1 | {:source-paths #{"src" "test" "src-dev"}} 2 | -------------------------------------------------------------------------------- /src/crypticbutter/snoop/impl/cljs.clj: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop.impl.cljs) 2 | -------------------------------------------------------------------------------- /bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | clojure -M:snoop:test-runner -m kaocha.runner "$@" 3 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /src/crypticbutter/snoop/stubs/cljs/env.cljc: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop.stubs.cljs.env) 2 | 3 | (def ^:dynamic *compiler* nil) 4 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:config-paths ["../resources/clj-kondo.exports/com.crypticbutter/snoop" 2 | "rewrite-clj/rewrite-clj"]} 3 | -------------------------------------------------------------------------------- /pom.properties: -------------------------------------------------------------------------------- 1 | # Generated by org.clojure/tools.build 2 | # Sun Dec 19 14:50:21 GMT 2021 3 | version=21-353-alpha 4 | groupId=com.crypticbutter 5 | artifactId=snoop -------------------------------------------------------------------------------- /test/cljs_main.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs-main 2 | (:require 3 | [crypticbutter.snoop :refer [>defn]])) 4 | 5 | (>defn x [] 6 | [:=> [:cat int?] int?]) 7 | 8 | (defn -main []) 9 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/com.crypticbutter/snoop/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:analyze-call {crypticbutter.snoop/>defn clj-kondo.crypticbutter.snoop/>defn 2 | crypticbutter.snoop/>defn- clj-kondo.crypticbutter.snoop/>defn}}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "Eclipse Public License 2.0", 3 | "devDependencies": { 4 | "platform": "1.3.5", 5 | "react": "16.13.0", 6 | "react-dom": "16.13.0", 7 | "shadow-cljs": "2.15.2", 8 | "stack-trace": "0.0.10", 9 | "stacktrace-js": "^2.0.2", 10 | "ws": "7.3.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/testing.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Testing 2 | 3 | - If testing ClojureScript, install and run [[https://github.com/lambdaisland/funnel][Funnel]]. 4 | - Run =bb test= (executes =./bin/kaocha= — see the [[https://cljdoc.org/d/lambdaisland/kaocha/1.0.732/doc/4-running-kaocha-cli][Kaocha docs]]) 5 | - or individual tests: =bb test clj=; =bb test cljs= 6 | - Test specifications are in =tests.edn= 7 | -------------------------------------------------------------------------------- /docs/development.adoc: -------------------------------------------------------------------------------- 1 | = Developing Snoop 2 | 3 | The `:cljs` alias may be useful, and `:snoop` enables instrumentation features via the JVM option. 4 | 5 | Example for `.dir-locals.el` when using CIDER: 6 | 7 | [source,elisp] 8 | ---- 9 | ((clojure-mode . ((cider-clojure-cli-aliases . "snoop:test:cljs")))) 10 | ---- 11 | 12 | After making changes, ensure the link:./testing.adoc[tests pass]. 13 | -------------------------------------------------------------------------------- /test/test_runner/hooks.clj: -------------------------------------------------------------------------------- 1 | (ns test-runner.hooks 2 | (:require 3 | [clojure.java.browse :as browse] 4 | [shadow.cljs.devtools.api :as shadow-api] 5 | [shadow.cljs.devtools.server :as shadow-server])) 6 | 7 | (defn compile-and-launch [suite _] 8 | (shadow-server/start!) 9 | (shadow-api/compile! (:shadow/build-id suite) {}) 10 | (some-> (:browse-url suite) browse/browse-url) 11 | suite) 12 | -------------------------------------------------------------------------------- /src/crypticbutter/snoop/impl/cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop.impl.cljs) 2 | 3 | (defn meta-fn 4 | ;; Taken from https://clojure.atlassian.net/browse/CLJS-3018 5 | ;; Because the current MetaFn implementation can cause quirky errors in CLJS 6 | [f m] 7 | (let [new-f (goog/bind f #js{})] 8 | (goog/mixin new-f f) 9 | (specify! new-f IMeta #_:clj-kondo/ignore (-meta [_] m)) 10 | new-f)) 11 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:plugins [:hooks] 3 | :tests [{:id :clj 4 | :type :kaocha.type/clojure.test} 5 | {:id :cljs 6 | :type :kaocha.type/cljs2 7 | :shadow/build-id :test-runner 8 | :browse-url "http://localhost:1818" 9 | ;; See https://cljdoc.org/d/lambdaisland/kaocha/1.0.861/doc/10-hooks 10 | :kaocha.hooks/pre-load-test [test-runner.hooks/compile-and-launch]}]} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | pom.xml.asc 4 | settings.xml 5 | *.jar 6 | *.class 7 | /lib/ 8 | /classes/ 9 | /target/ 10 | /checkouts/ 11 | .lein-deps-sum 12 | .lein-repl-history 13 | .lein-plugins/ 14 | .lein-failures 15 | .nrepl-port 16 | .cpcache/ 17 | 18 | node_modules/* 19 | yarn-error.log 20 | .shadow-cljs/* 21 | .cljs_node_repl/* 22 | 23 | .dir-locals.el 24 | .projectile 25 | .#* 26 | .clj-kondo/.cache/* 27 | .clj-kondo/com.crypticbutter/snoop 28 | .lsp/.cache 29 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | ;; 2 | {:tasks {:requires [[babashka.fs :as fs]] 3 | 4 | chui (shell "./node_modules/.bin/shadow-cljs -A:lib/cider-nrepl watch test-runner") 5 | 6 | test (apply shell "./bin/kaocha" *command-line-args*) 7 | 8 | -jar-location "target/build/snoop.jar" 9 | 10 | pom (clojure "-T:build" "pom") 11 | 12 | jar (clojure "-T:build" "jar") 13 | 14 | deploy (clojure "-T:build" "deploy") 15 | 16 | ;; To deploy with username & password 17 | jardeploy (clojure "-T:build" "jardeploy") 18 | 19 | ;; 20 | }} 21 | -------------------------------------------------------------------------------- /CHANGELOG.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Changelog 2 | 3 | * 21-353-alpha 4 | ** Fixes: 5 | - [[https://github.com/CrypticButter/snoop/pull/6][#6]]: Possible Null Pointer Exception 6 | 7 | * 21-228-alpha 8 | ** Features: 9 | - *[BREAKING CHANGE]* Better support for keyword argument functions (see README). The keyword args are treated as an additional map argument rather than variadic input. 10 | 11 | * 21-212-alpha 12 | ** Fixes: 13 | - No longer throws an error when compiling for cljs production (even when disabled in the config) (#4) (issue: #3) 14 | 15 | * 21-207-alpha2 16 | ** Changes: 17 | - -strument errors now show the full symbol (with namespace) of the violated function (#2) 18 | 19 | * 21-201-alpha 20 | ** Features: 21 | - You can now specify a multi-arity function schema via =malli.core/=>= (that looks like =[:function [:=> ...]]= 22 | - Added ability to swap out the logging functions (see =:log-error-fn= and =:log-fn-sym=) 23 | -------------------------------------------------------------------------------- /bin/ci-cljs-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ $GITHUB_ACTIONS == "" ]]; then 6 | echo "This script is supposed to run on CI, not in your dev machine." 7 | exit 1 8 | fi 9 | 10 | do_cleanup() { 11 | patterns=(xvfb chromium java) 12 | for pattern in ${patterns[*]}; do 13 | pkill -f $pattern || true 14 | done 15 | } 16 | 17 | trap do_cleanup EXIT 18 | 19 | # Start funnel for kaocha-cljs2 20 | nohup clojure -Sdeps '{:deps {lambdaisland/funnel {:mvn/version "0.1.42"}}}' -m lambdaisland.funnel -v & 21 | 22 | # Setup chromium flags to use in CI environment 23 | export CHROMIUM_USER_FLAGS="--no-first-run --no-default-browser-check" 24 | if [[ $whoami == "root" ]]; then 25 | export CHROMIUM_USER_FLAGS="$CHROMIUM_USER_FLAGS --no-sandbox" 26 | fi 27 | 28 | nohup xvfb-run -e /dev/stdout --server-args=":99.0 -screen 0 1360x1020x24 -ac +extension RANDR" chromium-browser & 29 | export DISPLAY=:99.0 30 | bb test cljs 31 | -------------------------------------------------------------------------------- /bin/install-bb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Improved based upon 4 | # https://github.com/lambdaisland/open-source/blob/bdeb15d185e4f82ef9a07676076b23be11c0e0a1/bin/install_babashka 5 | 6 | set -e 7 | 8 | # https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c 9 | gh_latest_release() { 10 | curl -L --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub api 11 | grep '"tag_name":' | # Get tag line 12 | sed -E 's/.*"v([^"]+)".*/\1/' # Pluck JSON value 13 | } 14 | 15 | current_platform() { 16 | case "$(uname -s)" in 17 | Linux*) echo linux;; 18 | Darwin*) echo macos;; 19 | esac 20 | } 21 | 22 | BABASHKA_VERSION=$(gh_latest_release borkdude/babashka) 23 | PLATFORM=$(current_platform) 24 | DOWNLOAD_URL="https://github.com/borkdude/babashka/releases/download/v${BABASHKA_VERSION}/babashka-${BABASHKA_VERSION}-${PLATFORM}-amd64.tar.gz" 25 | INSTALL_DIR="/usr/local/bin" 26 | 27 | mkdir -p "${INSTALL_DIR}" 28 | curl -o "/tmp/bb.tar.gz" -sL "${DOWNLOAD_URL}" 29 | tar xf "/tmp/bb.tar.gz" -C "${INSTALL_DIR}" 30 | rm "/tmp/bb.tar.gz" 31 | chmod +x "${INSTALL_DIR}/bb" 32 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | ;; 2 | {:deps {:aliases [:cljs :test :test-runner :dev]} 3 | 4 | :builds 5 | {:dev {:target :node-script 6 | :main main/-main 7 | :dev {:compiler-options {:external-config {:crypticbutter.snoop {}}}} 8 | :output-to "target/dev.js"} 9 | 10 | :test-disabled {:target :node-script 11 | :main cljs-main/-main 12 | :compiler-options {:external-config {;; 13 | ;; :crypticbutter.snoop {} 14 | ;; :crypticbutter.snoop {:enabled? false} 15 | ;; 16 | }} 17 | :output-to "target/prod.js"} 18 | 19 | :test-runner {:target :browser-test 20 | :runner-ns kaocha.cljs2.shadow-runner 21 | :test-dir "target/kaocha-test" 22 | :ns-regexp ".*-test$" 23 | :dev {:compiler-options {:external-config {:crypticbutter.snoop {}}}} 24 | :devtools {:http-port 1818 25 | :http-root "target/kaocha-test" 26 | :preloads [lambdaisland.chui.remote]}}}} 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci-test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | clj-test: 7 | # ubuntu 18.04 comes with lein + java8 installed 8 | runs-on: ubuntu-18.04 9 | steps: 10 | - name: Git checkout 11 | uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 1 14 | 15 | - name: Install bb 16 | run: 17 | ./bin/install-bb.sh 18 | 19 | - name: Install clojure tools 20 | uses: DeLaGuardo/setup-clojure@3.5 21 | with: 22 | cli: 1.10.3.943 23 | 24 | - name: Run CLJ Tests 25 | run: | 26 | bb test clj 27 | 28 | cljs-test: 29 | runs-on: ubuntu-18.04 30 | steps: 31 | - name: Git checkout 32 | uses: actions/checkout@v2 33 | with: 34 | fetch-depth: 1 35 | 36 | - name: Install bb 37 | run: 38 | ./bin/install-bb.sh 39 | 40 | - name: Install clojure tools 41 | uses: DeLaGuardo/setup-clojure@3.5 42 | with: 43 | cli: 1.10.3.943 44 | 45 | - name: Install Chrome 46 | run: | 47 | whoami 48 | sudo apt-get update -qq 49 | sudo apt-get install -q -y xvfb libgbm1 libxss1 chromium-browser 50 | 51 | - name: Ensure chrome installed 52 | run: chromium-browser --version 53 | 54 | - name: Install cljs deps 55 | run: yarn install 56 | 57 | - name: Run CLJS tests 58 | run: ./bin/ci-cljs-test.sh 59 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jar 5 | com.crypticbutter 6 | snoop 7 | 21-353-alpha 8 | snoop 9 | Function instrumentation using malli schemas. 10 | 11 | 12 | Eclipse Public License 2.0 13 | http://www.eclipse.org/legal/epl-v20.html 14 | 15 | 16 | 17 | 18 | org.clojure 19 | clojure 20 | 1.10.3 21 | 22 | 23 | com.taoensso 24 | encore 25 | 3.20.0 26 | 27 | 28 | net.cgrand 29 | macrovich 30 | 0.2.1 31 | 32 | 33 | 34 | src 35 | 36 | 37 | 38 | clojars 39 | https://repo.clojars.org/ 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | 3 | :deps {net.cgrand/macrovich {:mvn/version "0.2.1"} 4 | com.taoensso/encore {:mvn/version "3.20.0"} 5 | metosin/malli {:mvn/version "0.7.5"}} 6 | 7 | :aliases {:cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.16.8"}}} 8 | :dev {:extra-paths ["src-dev"] 9 | :extra-deps {cider/cider-nrepl {:mvn/version "0.27.4"} 10 | nrepl/nrepl {:mvn/version "0.9.0"}}} 11 | :test {:extra-paths ["test"]} 12 | :test-runner {:extra-paths ["test"] 13 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.60.972"} 14 | lambdaisland/kaocha-cljs2 {:mvn/version "0.0.35" 15 | :exclusions [lambdaisland/glogi]} 16 | ;; you get errors when using glogi versions earlier than 1.0.100 17 | lambdaisland/glogi {:mvn/version "1.0.106"} 18 | thheller/shadow-cljs {:mvn/version "2.16.8"}}} 19 | 20 | :snoop {:jvm-opts ["-Dsnoop.enabled"]} 21 | 22 | :build {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.7.2" :git/sha "0361dde"} 23 | io.github.juxt/pack.alpha {:git/sha "9fd4a63ddeec3bedcbc819422de5a40ccb9eb8c9"} 24 | slipset/deps-deploy {:mvn/version "0.2.0"}} 25 | :ns-default build} 26 | ;; 27 | }} 28 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/com.crypticbutter/snoop/clj_kondo/crypticbutter/snoop.clj: -------------------------------------------------------------------------------- 1 | (ns clj-kondo.crypticbutter.snoop 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn- read-param-decl [params-node] 5 | (reduce (fn [acc param-decl] 6 | (let [list-children (when (api/list-node? param-decl) 7 | (:children param-decl)) 8 | schema-element (second list-children)] 9 | (-> acc 10 | (update 0 conj (or (first list-children) param-decl)) 11 | (cond-> schema-element 12 | (update 1 conj schema-element))))) 13 | [[] ;; params 14 | []] ;; schemas 15 | (:children params-node))) 16 | 17 | (defn >defn 18 | [{:keys [node]}] 19 | (let [[macro-sym & body] (:children node) 20 | defn-node (api/token-node (case (name (api/sexpr macro-sym)) 21 | ">defn" 'defn ">defn-" 'defn-)) 22 | output-node (api/list-node 23 | (loop [body' body 24 | acc []] 25 | (if-some [item (first body')] 26 | (if (api/vector-node? item) 27 | (let [[params schemas] (read-param-decl item)] 28 | (list (api/token-node 'do) (api/vector-node schemas) 29 | (api/list-node 30 | (list* defn-node 31 | (into (conj acc (api/vector-node params)) 32 | (rest body')))))) 33 | (recur (next body') (conj acc item))) 34 | (list* defn-node acc))))] 35 | {:node output-node})) 36 | -------------------------------------------------------------------------------- /test/crypticbutter/snoop_test.cljc: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop-test 2 | {:clj-kondo/config #_:clj-kondo/ignore 3 | '{:linters 4 | {:inline-def 5 | {:level :off}}}} 6 | (:require 7 | [clojure.test :refer [deftest is testing]] 8 | [taoensso.encore :as enc] 9 | [malli.core :as m] 10 | [crypticbutter.snoop :as snoop :refer [>defn =>]])) 11 | 12 | (defn throws? [f & args] 13 | (enc/catching (do (apply f args) 14 | false) 15 | _ true)) 16 | 17 | (deftest >defn-test 18 | (testing "no schema" 19 | (>defn f [_ _] 20 | []) 21 | (is (= [] (f nil nil)))) 22 | 23 | (testing "instrument - malli style schema" 24 | (>defn add [_ _] 25 | [:=> [:cat int? int?] string?] 26 | "0") 27 | (is (= "0" (add 1 2))) 28 | (is (true? (throws? add "" "")))) 29 | 30 | (testing "instrument - ghostwheel style schema" 31 | (>defn g-var-arrow [_] 32 | [int? => string?] 33 | "0") 34 | (is (= "0" (g-var-arrow 5))) 35 | (is (true? (throws? g-var-arrow ""))) 36 | 37 | (>defn g-sym [_] 38 | [int? '=> string?] 39 | "0") 40 | (is (= "0" (g-sym 5))) 41 | (is (true? (throws? g-sym ""))) 42 | 43 | (>defn g-kw [_] 44 | [int? :=> string?] 45 | "0") 46 | (is (= "0" (g-kw 5))) 47 | (is (true? (throws? g-kw ""))) 48 | 49 | (>defn g-kw-ret [_] 50 | [int? :ret string?] 51 | "0") 52 | (is (= "0" (g-kw-ret 5))) 53 | (is (true? (throws? g-kw-ret "")))) 54 | 55 | (testing "instrument - inline style schema" 56 | (>defn simple-inline-instrument 57 | [(_ int?) _ (_)] 58 | [=> string?] 59 | "0") 60 | (is (= "0" (simple-inline-instrument 5 4 3))) 61 | (is (true? (throws? simple-inline-instrument "")))) 62 | 63 | (testing "outstrument - 0-parameter functions - malli style" 64 | (>defn f0-m-good [] 65 | [:cat int?] 66 | 5) 67 | (is (= 5 (f0-m-good))) 68 | 69 | (>defn f0-m-bad [] 70 | [:cat string?] 71 | 5) 72 | (is (true? (throws? f0-m-bad)))) 73 | 74 | (testing "outstrument - 0-parameter functions - ghostwheel style" 75 | (>defn f0-g-good [] 76 | [=> int?] 77 | 5) 78 | (is (= 5 (f0-g-good))) 79 | 80 | (>defn f0-g-bad [] 81 | [=> string?] 82 | 5) 83 | (is (true? (throws? f0-g-bad)))) 84 | 85 | (testing "schema via prepost map" 86 | (>defn prepost 87 | [x _] 88 | {:=> [:any int? => int?]} 89 | x) 90 | (is (= 5 (prepost 5 4))) 91 | (is (true? (throws? prepost "x" 4))) 92 | (is (true? (throws? prepost 5 "y")))) 93 | 94 | (testing "schema via m/=>" 95 | (>defn malli-sch [x _y] 96 | x) 97 | (m/=> malli-sch [:=> [:cat :any int?] int?]) 98 | (is (= 5 (malli-sch 5 4))) 99 | (is (true? (throws? malli-sch "x" 4))) 100 | (is (true? (throws? malli-sch 5 "y")))) 101 | 102 | (testing "multi-arity and variable-arity" 103 | (is (ifn? (>defn _varargs [& _]))) 104 | (>defn multi-arity 105 | ([] 106 | [=> int?] 107 | 5) 108 | ([x] 109 | [int? => int?] 110 | x) 111 | ([x & _] 112 | [[:+ int?] int?] 113 | x)) 114 | (is (= 5 (multi-arity))) 115 | (is (= 4 (multi-arity 4))) 116 | (is (true? (throws? multi-arity "x"))) 117 | (is (= 3 (multi-arity 3 4 4))) 118 | (is (true? (throws? multi-arity 3 "x" "y")))) 119 | 120 | (testing "ghostwheel style with variable arity" 121 | (>defn g-var [_ & _more] 122 | [int? [:* string?] => :any] 123 | true) 124 | (is (true? (g-var 3))) 125 | (is (true? (g-var 3 "a"))) 126 | (is (true? (g-var 3 "a" "b"))) 127 | (is (throws? g-var "a")) 128 | (is (throws? g-var 3 3)) 129 | (is (throws? g-var 3 "a" 3))) 130 | 131 | (testing "inline style with variable arity" 132 | (>defn inline-variadic [(_ int?) & (_more [:* string?])] 133 | true) 134 | (is (true? (inline-variadic 3))) 135 | (is (true? (inline-variadic 3 "a"))) 136 | (is (true? (inline-variadic 3 "a" "b"))) 137 | (is (throws? inline-variadic "a")) 138 | (is (throws? inline-variadic 3 3)) 139 | (is (throws? inline-variadic 3 "a" 3))) 140 | 141 | (testing "variable arity + map destructuring" 142 | (>defn inline-map-variadic [(i [:maybe int?]) & ({:keys [x]} [:map {:closed true} [:x {:optional true} string?]])] 143 | [:=> some?] 144 | (boolean (or i x))) 145 | (is (true? (inline-map-variadic 3))) 146 | (is (true? (inline-map-variadic nil :x "a"))) 147 | (is (throws? inline-map-variadic "a")) 148 | (is (throws? inline-map-variadic 3 :x 3)) 149 | (is (throws? inline-map-variadic 3 :y "a"))) 150 | 151 | (testing "disable via meta and attr-map" 152 | (>defn ^{::snoop/macro-config {:enabled? false}} 153 | d-m [] 154 | [int? => :nil] 155 | 5) 156 | (is (= 5 (d-m))) 157 | (>defn d-a1 158 | {::snoop/macro-config 159 | ;; Putting def here as it ensures the var is bound in both the cljs and clj 160 | ;; unit tests, apparently. 161 | (do (def macro-config-with-disable {:enabled? false}) 162 | (assoc macro-config-with-disable 4 20))} 163 | [] 164 | [int? => :nil] 165 | 5) 166 | (is (= 5 (d-a1))) 167 | (>defn d-a2 168 | ([] 169 | [int? => :nil] 170 | 5) 171 | #_:clj-kondo/ignore 172 | {::snoop/macro-config {:enabled? false}}) 173 | (is (= 5 (d-a2))) 174 | (is (true? (empty? (select-keys (into {} (map meta) 175 | [(var d-m) (var d-a1) (var d-a2)]) 176 | snoop/-defn-option-keys))))) 177 | 178 | (testing "disable function with inline style schema specification" 179 | (>defn disabled-inline 180 | {::snoop/macro-config {:enabled? false}} 181 | [(_x int?) (_y int?)] 182 | [=> string?] 183 | :melon) 184 | (is (= :melon (disabled-inline "a" "b")))) 185 | 186 | (testing "custom runtime atom passes through" 187 | (>defn passthrough 188 | {::snoop/config-atom (atom (merge @snoop/*config 189 | {:melons true}))} 190 | [] 191 | [=> string?] 192 | "melon") 193 | (is (true? (contains? (some-> (var passthrough) #?(:cljs deref) 194 | meta ::snoop/config-atom deref) 195 | :melons)))) 196 | 197 | (testing "override runtime atom" 198 | ;; important to keep around whitelisting settings 199 | (>defn custom-atom 200 | {::snoop/config-atom (atom (merge @snoop/*config 201 | {:outstrument? false}))} 202 | [] 203 | [=> int?] 204 | "melon") 205 | (is (false? (throws? custom-atom))) 206 | (>defn custom-atom-out 207 | {::snoop/config-atom (atom (merge @snoop/*config 208 | {:outstrument? true}))} 209 | [] 210 | [=> int?] 211 | "melon") 212 | (is (true? (throws? custom-atom-out)))) 213 | 214 | (testing "m/=> :function schema notation - multi arity" 215 | (m/=> function-schema-multi [:function 216 | [:=> [:cat int?] int?] 217 | [:=> [:cat map? :any] int?] 218 | [:=> [:cat string? :any [:+ string?]] int?]]) 219 | (>defn function-schema-multi 220 | ([_x] 221 | 5) 222 | ([_x y] 223 | y) 224 | ([_x y & _zs] 225 | y)) 226 | (is (false? (throws? function-schema-multi 5))) 227 | (is (true? (throws? function-schema-multi ""))) 228 | 229 | (is (false? (throws? function-schema-multi {} 5))) 230 | (is (true? (throws? function-schema-multi "" 5))) 231 | (is (true? (throws? function-schema-multi {} ""))) 232 | 233 | (is (false? (throws? function-schema-multi "" 5 "" ""))) 234 | (is (true? (throws? function-schema-multi "" 5 5 ""))))) 235 | 236 | (comment 237 | 238 | 239 | ;; 240 | ) 241 | -------------------------------------------------------------------------------- /src/crypticbutter/snoop/config.cljc: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop.config 2 | {:clj-kondo/config #_:clj-kondo/ignore 3 | '{:linters 4 | {:unresolved-namespace 5 | {:exclude [cljs.env]}}}} 6 | #?(:cljs (:require-macros 7 | [crypticbutter.snoop.config :refer [notify-enabled-state]] 8 | [net.cgrand.macrovich :as macrovich :refer [deftime usetime]])) 9 | (:require 10 | #?@(:clj [[net.cgrand.macrovich :as macrovich :refer [deftime usetime]] 11 | [clojure.edn :as edn]] 12 | :cljs [[cljs.env :as cljs.env]]) 13 | [taoensso.encore :as enc] 14 | [malli.error :as me])) 15 | 16 | #?(:clj (try 17 | (require 'cljs.env) 18 | (catch Exception _ 19 | (require '[crypticbutter.snoop.stubs.cljs.env :as cljs.env])))) 20 | 21 | (usetime 22 | (declare throw-validation-error) 23 | (def *config 24 | "The global runtime configuration atom for snoop's instrumented functions. 25 | 26 | Also accessible from `snoop.core/*config` 27 | 28 | Refer to the project README for option details." 29 | (atom {:on-instrument-fail #(throw-validation-error % :input) 30 | :on-outstrument-fail #(throw-validation-error % :output) 31 | :instrument? true 32 | :outstrument? true 33 | :malli-opts {} 34 | :whitelist-by-default? true 35 | :whitelist-fn {} 36 | :blacklist-fn {} 37 | :blacklist-ns #{} 38 | :whitelist-ns #{} 39 | :log-error-fn #?(:clj println :cljs js/console.error)}))) 40 | 41 | (usetime 42 | (defn throw-validation-error 43 | "Default function used to throw errors when in/outstrumentation fails." 44 | [{:keys [explainer-error] :as data} boundary] 45 | (let [boundary-name (case boundary 46 | :input "Instrument" 47 | :output "Outstrument") 48 | log-error (:log-error-fn @*config) 49 | data-str #?(:clj pr-str :cljs identity)] 50 | (log-error (str boundary-name " error for:") (symbol (str (:ns data)) 51 | (str (:name data)))) 52 | (enc/catching (let [hm-errors (me/humanize explainer-error)] 53 | (case boundary 54 | :input (let [params (:params data) 55 | fixed-args-count (- (count params) 2) 56 | varargs? (= '& (get params fixed-args-count))] 57 | (loop [idx 0] 58 | (when (< idx (count hm-errors)) 59 | (let [err (nth hm-errors idx) 60 | in-vararg? (and varargs? (<= fixed-args-count idx))] 61 | (when err 62 | (log-error (cond-> "For param:" in-vararg? (str " &")) 63 | (nth params (if in-vararg? (inc fixed-args-count) idx)) 64 | "\nGot:" (data-str (get-in explainer-error [:value idx])) 65 | "\nError:" (data-str err))) 66 | (recur (inc idx)))))) 67 | :output (log-error "Got:" (data-str (:value explainer-error)) 68 | "\nError:" (data-str hm-errors)))) 69 | _ (log-error "Humanize failed" 70 | "\nGot:" (data-str (:value explainer-error)) 71 | "\nErrors:" (data-str (:errors explainer-error)))) 72 | (throw (ex-info (str boundary-name " failed. See message printed above.") data))))) 73 | 74 | (deftime 75 | (def ^:private production-cljs-compiler? 76 | (when cljs.env/*compiler* 77 | (not= :none (get-in @cljs.env/*compiler* [:options :optimizations] :none))))) 78 | 79 | (deftime 80 | (defn- get-system-propery [#_:clj-kondo/ignore prop] 81 | #?(:clj (System/getProperty prop) :cljs nil))) 82 | 83 | (deftime 84 | (defn- read-config-file [] 85 | #?(:clj (try 86 | (edn/read-string (slurp "snoop.edn")) 87 | (catch Exception _ nil)) 88 | :cljs nil))) 89 | 90 | (deftime 91 | (defn- get-cljs-compiler-config [] 92 | (when cljs.env/*compiler* 93 | (get-in @cljs.env/*compiler* [:options :external-config :crypticbutter.snoop])))) 94 | 95 | (deftime 96 | (def *compiletime-config-cache (atom {:by-id {} 97 | :register (enc/queue)}))) 98 | 99 | (deftime 100 | (def compiletime-config-defaults {:defn-sym 'clojure.core/defn 101 | :log-fn-sym 'clojure.core/println})) 102 | 103 | (deftime 104 | (defn- get-compiletime-config* [] 105 | (let [file-config (when (get-system-propery "snoop.enabled") 106 | (or (read-config-file) {})) 107 | supplied-config (enc/merge file-config (get-cljs-compiler-config)) 108 | complete-config (enc/merge compiletime-config-defaults 109 | (cond-> supplied-config 110 | (and (some? supplied-config) 111 | (not (contains? supplied-config :enabled?))) 112 | (assoc :enabled? true)))] 113 | (when (and production-cljs-compiler? (:enabled? complete-config)) 114 | (throw (ex-info "🚨 Snoop enabled with production compiler options 🚨" {}))) 115 | complete-config))) 116 | 117 | (deftime 118 | (defn get-compiletime-config [] 119 | (let [id (when cljs.env/*compiler* 120 | (hash (get-in @cljs.env/*compiler* [:options :closure-defines]))) 121 | now #?(:clj (System/currentTimeMillis) :cljs (js/Date.now)) 122 | age (- now (get-in @*compiletime-config-cache [:by-id id :timestamp] 0))] 123 | (get-in (if (< age 3000) 124 | @*compiletime-config-cache 125 | (swap! *compiletime-config-cache 126 | (fn [m] 127 | (let [fresh-config (get-compiletime-config*) 128 | oldest-id (-> m :register peek) 129 | oldest-timestamp (get-in m [:by-id oldest-id :timestamp])] 130 | (-> m 131 | (cond-> #__ 132 | (and id (-> m :register count (> 15)) 133 | oldest-timestamp 134 | (< (* 1000 60 10) (- now oldest-timestamp))) 135 | (-> (update :by-id dissoc oldest-id) 136 | (update :register pop))) 137 | (cond-> id (update :register conj id)) 138 | (assoc-in [:by-id id] {:value fresh-config 139 | :timestamp now})))))) 140 | [:by-id id :value])))) 141 | 142 | (deftime 143 | (defmacro ^:private notify-enabled-state [] 144 | (let [config (get-compiletime-config) 145 | log (resolve (:log-fn-sym config))] 146 | (when (and (get-system-propery "snoop.enabled") 147 | (not (map? (read-config-file)))) 148 | (log "\u001B[31mWARNING: snoop.enabled is set but we could not find a map in a snoop.edn file.\u001B[m")) 149 | (if (:enabled? config) 150 | (log "\u001B[33mBeware: Snoop is snooping and performance may be affected.\u001B[m") 151 | (log "\u001B[32mSnoop is disabled\u001B[m"))))) 152 | 153 | (usetime 154 | (defonce ^:private *notified? (atom false)) 155 | 156 | (when-not @*notified? 157 | (reset! *notified? true) 158 | (notify-enabled-state))) 159 | 160 | (comment 161 | (reset! *compiletime-config-cache {:by-id {} 162 | :register (enc/queue)}) 163 | @*compiletime-config-cache 164 | 165 | ;; 166 | ) 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snoop (Alpha) 2 | 5 | 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/com.crypticbutter/snoop.svg)](https://clojars.org/com.crypticbutter/snoop) 8 | 9 | Function instrumention for Clojure(Script) using [malli](https://github.com/metosin/malli/) 10 | schemas and a custom defn wrapper. 11 | 12 | Inspired by [Guardrails](https://github.com/fulcrologic/guardrails) and 13 | [malli-instrument](https://github.com/setzer22/malli-instrument). 14 | 15 | ## Rationale 16 | 17 | I wanted a way to use malli schemas to check the validity of the inputs and outputs 18 | of functions. Instrumentation is a conventient way to spot errors using real-world 19 | data and it does not require writing tests upfront. malli-instrument and [aave](https://github.com/teknql/aave) 20 | had limitations that made them unsuitable for my needs. 21 | 22 | I attempted to modify malli-instrument to be ClojureScript-compatible. However, 23 | I found that `clojure.spec`-like instrumentation (which works on regular `defn`s) 24 | can be inconvenient with hot code reloading and evaluating functions on the fly. 25 | Thus, I took the approach of using a `>defn` macro, which has the following benefits: 26 | 27 | * Makes it more convenient to specify the schema 28 | * You do not have to define the function symbol twice (once for the function, 29 | again for the schema using `m/=>`) 30 | * In multi-arity functions, schemas can be colocated with each arity 31 | * Easy to quickly disable instrumentation on individual functions 32 | * No special linter required (can be linted as `defn`) 33 | 34 | ![example snoop](https://user-images.githubusercontent.com/41270840/121600548-88637500-ca3c-11eb-918c-7464a6db0887.png) 35 | 36 | ## Installation 37 | 38 | [![Clojars Project](https://img.shields.io/clojars/v/com.crypticbutter/snoop.svg)](https://clojars.org/com.crypticbutter/snoop) 39 | 40 | deps.edn: 41 | 42 | ```clojure 43 | com.crypticbutter/snoop {:mvn/version "21-353-alpha"} 44 | metosin/malli {:mvn/version "LATEST"} 45 | ``` 46 | 47 | (Also see [the changelog](./CHANGELOG.org)) 48 | 49 | Then either: 50 | - Create a `snoop.edn` file with a map in it. Specify the `-Dsnoop.enabled` JVM option 51 | when launching a REPL. E.g `clj -J-Dsnoop.enabled` or `:jvm-opts ["-Dsnoop.enabled"]` 52 | in deps.edn 53 | - [Only available with ClojureScript] Provide the compiler options: `{:external-config {:crypticbutter.snoop {}}}` 54 | - Example for a shadow-cljs build: `:your-build {:dev {:compiler-options {:external-config {:crypticbutter.snoop {}}}} ...}` 55 | 56 | Snoop is disabled by default and will throw an exception if enabled in a CLJS 57 | production build. 58 | 59 | If there are any problems installing & using, please let me know. 60 | 61 | ## Using the `>defn` macro 62 | 63 | | **Prerequisite:** | [understand malli's function schemas](https://github.com/metosin/malli#function-schemas) 64 | | --- | --- 65 | 66 | ```clojure 67 | (require '[crypticbutter.snoop :refer [>defn]]) 68 | ``` 69 | 70 | The `>defn` macro is optionally backwards compatible with `defn` (you can swap out one 71 | symbol with the other without breaking any code). This makes it more feasible to 72 | combine multiple defn wrappers (also see the [:defn-sym option](#Compile-time-config)). 73 | 74 | Enclosed, you will find a clj-kondo config export, for your linting convenience. 75 | See ([exporting and importing clj-kondo configs](https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#exporting-and-importing-configuration)). 76 | 77 | There are multiple ways of specifying your schema(s). 78 | 79 | ### Using malli's function schema registry: 80 | 81 | ```clojure 82 | (require '[malli.core :as m]) 83 | 84 | (m/=> add [:=> [:cat int? int?] int?]) 85 | (>defn add [x y] ...) 86 | ``` 87 | 88 | The schema specified with `m/=>` will be ignored if any schema is specified within the function body. 89 | 90 | ### Inside the function body: 91 | ```clojure 92 | (>defn add [x [y z]] ;; You can still use destructuring 93 | [:=> [:cat int? [:tuple int? int?]] int?] 94 | ...) 95 | ``` 96 | 97 | ### More convenient notations that work when using `>defn`: 98 | ```clojure 99 | ;; Require `=>` solely to prevent unresolved symbol linting errors 100 | (require '[crypticbutter.snoop :refer [>defn =>]]) 101 | 102 | (>defn add [x y] 103 | ;; Either: 104 | [[:cat int? int?] int?] 105 | ;; Or: 106 | [int? int? => int?] 107 | ...) 108 | ``` 109 | The second schema above uses a similar notation to [ghostwheel](https://github.com/gnl/ghostwheel). 110 | The `=>` can be substituted with `:=>`, `'=>` or `:ret` 111 | 112 | To outstrument a 0-parameter function, you could use `[=> int?]` — this means 113 | there will be no input validation. 114 | 115 | ### Inside the prepost map: 116 | ```clojure 117 | (>defn add [x y] 118 | {:=> [[:cat int? int?] int?]} 119 | ...) 120 | ``` 121 | 122 | The main motivation for this option is that it could make combining defn wrappers 123 | easier by allowing you to forward the schema via the prepost map. Requires that you 124 | are able to set the `defn` symbol used by the top-level macro. 125 | 126 | ### Multiple arities and variadic functions 127 | 128 | You can mix and match notations. 129 | 130 | ```clojure 131 | (>defn add 132 | ([x] 133 | [int? => int?] 134 | ...) 135 | ([x y] 136 | {:=> [:=> [:cat int? int?] int?]} 137 | ...) 138 | ([x y & zs] 139 | ;; Either 140 | [int? int? [:+ int?] => int?] 141 | ;; Or 142 | [[:cat int? int? [:+ int?]] int?] 143 | ...)) 144 | ``` 145 | 146 | You could also use `m/=>`. 147 | 148 | ```clojure 149 | (m/=> add [:function 150 | [:=> [:cat int?] int?] 151 | [:=> [:cat int? int?] int?] 152 | [:=> [:cat int? int? [:+ int?]] int?]]) 153 | (>defn add 154 | ([x] ...) 155 | ([x y] ...) 156 | ([x y & zs] ...)) 157 | ``` 158 | 159 | ### No schema 160 | 161 | Schemas are optional. `>defn` works fine without the schema (acts as a regular 162 | `defn` without the instrumentation): 163 | ```clojure 164 | (>defn add [x y] 165 | ;; advanced maths 166 | ...) 167 | ``` 168 | 169 | ### Inline Schema 170 | 171 | You can choose to depart from the standard `defn` pattern and specify your schemas 172 | right alongside your function parameters. You will need a custom linter, so you may 173 | find the included clj-kondo config export useful ([exporting and importing clj-kondo configs](https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#exporting-and-importing-configuration)). 174 | 175 | ```clojure 176 | (>defn wow 177 | [(mickey string?) 178 | (mouse) ;; this argument is not validated 179 | ({:keys [fun]} MySchema) ;; Destructuring still available 180 | house ;; list brackets are optional for schemaless params 181 | & (melon [:* int?])] 182 | ...) 183 | ``` 184 | 185 | The disadvantage of this syntax is that you are limited to considering each argument 186 | individually; You cannot validate the relationship between arguments. 187 | 188 | Note that the inline schemas are used for validation *in addition* to any schema specified 189 | before the function body (or any schema defined with `m/=>`). For example, this will always throw an error: 190 | 191 | ```clojure 192 | (>defn doom 193 | [(x int?)] 194 | [string? => :any] 195 | ;; x cannot be int and string at the same time 196 | ...) 197 | ``` 198 | 199 | However, this also means that you can use an additional schema vector to specify 200 | the return schema: 201 | 202 | ```clojure 203 | (>defn melon [(x int?) (y int?) (z melon?)] 204 | [=> string?] 205 | ...) 206 | ``` 207 | 208 | As with the other methods, this works with multiple arities: 209 | 210 | ```clojure 211 | (>defn add 212 | ([(x int?)] 213 | [=> int?] 214 | ...) 215 | ([(x int?) (y int?)] 216 | {:=> [=> int?]} ;; return value is int, specified in prepost map 217 | ...) 218 | ([(x int?) (y int?) & (zs [:* int?])] 219 | ;; with no output schema 220 | ...)) 221 | ``` 222 | 223 | ### Support for Keyword Argument Functions 224 | 225 | Treat the keyword arguments as a single map argument (as if it were a fixed-arity function). 226 | If no keyword arguments are passed, an empty map (instead of nil) will be used for validation 227 | (so you do not have to wrap `:map` with `:maybe`). 228 | 229 | ```clojure 230 | (>defn f [a & {:keys [b c]}] 231 | [int? [:map 232 | [:b int?] 233 | [:c {:optional true} int?]] 234 | => int?] 235 | ...) 236 | ``` 237 | 238 | ## Configuration 239 | 240 | There are two main global configurations and they can be overrided for individual functions: 241 | 242 | ### Runtime config 243 | 244 | At runtime, you are able to modify the `crypticbutter.snoop/*config` atom, 245 | which affects the behaviour of instrumented functions. 246 | 247 | | Key | Default | Description | 248 | | --- | --- | --- | 249 | | `:on-instrument-fail` | | Function to call when the input is not valid. Receives single argument. | 250 | | `:on-outstrument-fail` | | Function to call when the output is not valid. Receives single argument. | 251 | | `:log-error-fn` | #?(:clj println :cljs js/console.error) | Used to log errors at runtime. Must be variadic. | 252 | | `:malli-opts` | {} | Given to `m/explain` which is used for validation. | 253 | | `:instrument?` | true | Whether to enable validation on a function's arguments. | 254 | | `:outstrument?` | true | Whether to enable validation on a function's return value. | 255 | | `:whitelist-by-default` | true | Determines whether validation is allowed on functions by default. If set to false, functions must be whitelisted in order for validation to occur. | 256 | | `:blacklist-ns` | #{} | Set of namespace symbols for which in/outstrumentation should be disallowed. | 257 | | `:whitelist-ns` | #{} | Similar to above but allows validation in the namespaces. Only useful if `:whitelist-by-default` is false. | 258 | | `:whitelist-fn` | {} | Maps namespace symbols to sets of function symbols whose validation should be allowed. Overrides the namespace rules above. | 259 | | `:blacklist-fn` | {} | Similar to above but disallows validation. | 260 | 261 | ### Compile-time config 262 | 263 | You can also modify the config used by the macros. This can be done in `snoop.edn` 264 | or via the CLJS compiler options (see [Installation](#Installation)). 265 | 266 | | Key | Default | Description | 267 | | --- | --- | --- | 268 | | :enabled? | `true` (only if config provided) | Whether to augment the function body with instrumentation features. This is the master switch, and should not be true in a production build. | 269 | | :defn-sym | `clojure.core/defn` | The *symbol* to use for `defn`. This allows you to combine `defn` wrappers as long as their structures are compatible with the core `defn` macro (you can forward data via metadata or prepost maps). | 270 | | :log-fn-sym | `clojure.core/println` | The *symbol* used to resolve the function used for logging messages during compile-time. | 271 | 272 | ### Per-function config 273 | 274 | You can provide config overrides as metadata (including via an `attr-map`). 275 | 276 | - `::snoop/macro-config` gets merged on top of the compile-time config. Whatever you 277 | provide here, it must be possible to `eval` it as compile-time (so all the appropriate 278 | vars must be bound and you cannot pass in locals). 279 | 280 | - `::snoop/config-atom` will be used within the function instead of `snoop/*config`. In 281 | ClojureScript, this will be attached to the metadata of the function object because 282 | [var metadata does not get evaluated](https://clojurescript.org/about/differences#_special_forms). 283 | 284 | ```clojure 285 | (require '[crypticbutter.snoop :as snoop :refer [>defn]]) 286 | 287 | (def special-compiletime-config {:enabled? true 288 | :defn-sym 'some.magic/>defn}) 289 | 290 | (def special-runtime-config (atom {:malli-opts {...} :on-instrument-fail ...})) 291 | 292 | (>defn fun-function 293 | {::snoop/macro-config special-compiletime-config 294 | ::snoop/config-atom special-runtime-config} 295 | [] 296 | ['=> string?] 297 | "🍉") 298 | ``` 299 | 300 | ## Improvements to be made 301 | 302 | - [ ] In `>defn`, combine schemas for each arity into a single schema and call `m/=>`. Would be useful for malli.dev static schema checking facilities. 303 | at runtime to register a schema passed via the prepost map or body. 304 | - [ ] Provide facilities to allow valiation to be done in a different thread in CLJS. 305 | - [ ] Option for asynchronous checking in Clojure JVM 306 | 307 | I will probably only work on new features as I need them. That said, please report any 308 | issues you run into whilst using this library. 309 | 310 | --- 311 | 312 | 313 | 314 | # Contributing 315 | 316 | See [development](./docs/development.adoc) 317 | 318 | And ensure the tests pass: [testing](./docs/testing.org) 319 | 320 | I'll publish more details in the future. 321 | 322 | # License 323 | 324 | Copyright © 2021 Luis Thiam-Nye and contributors. 325 | 326 | Distributed under Eclipse Public License 2.0, see [LICENSE](./LICENSE). 327 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /src/crypticbutter/snoop.cljc: -------------------------------------------------------------------------------- 1 | (ns crypticbutter.snoop 2 | #?(:cljs (:require-macros 3 | [crypticbutter.snoop :refer [current-ns]] 4 | [net.cgrand.macrovich :as macrovich :refer [deftime usetime]])) 5 | (:require 6 | #?@(:clj [[net.cgrand.macrovich :as macrovich :refer [deftime usetime]]]) 7 | [clojure.pprint :as pp] 8 | [crypticbutter.snoop.impl.cljs :as patched-cljs] 9 | [crypticbutter.snoop.config :as cfg] 10 | [taoensso.encore :as enc] 11 | [malli.core :as m])) 12 | 13 | (def => '=>) 14 | 15 | (defn arity-of-params [params] 16 | (let [arg-count (count params) 17 | variadic? (= '& (get params (- arg-count 2)))] 18 | (if variadic? :varargs arg-count))) 19 | 20 | (def FnSchemaDecl 21 | [:and vector? 22 | [:or 23 | [:catn 24 | [:input-unwrapped [:* :any]] 25 | [:ret-divider [:enum :=> `=> '=> (quote '=>) :ret]] 26 | [:output :any]] 27 | [:catn 28 | [:prefix [:? [:= :=>]]] 29 | [:input :any] 30 | [:output :any]]]]) 31 | 32 | (def InstrumentedParamsVec 33 | [:and vector? 34 | [:* 35 | [:altn 36 | [:instrumented [:and list? 37 | [:catn 38 | [:param :any] 39 | [:schema [:? :any]]]]] 40 | [:param :any]]]]) 41 | 42 | (def ArityDecl 43 | [:alt 44 | [:catn 45 | [:params-decl InstrumentedParamsVec] 46 | [:prepost-map [:? map?]] 47 | [:schema [:? FnSchemaDecl]] 48 | [:body [:+ :any]]] 49 | [:catn 50 | [:params-decl InstrumentedParamsVec] 51 | [:prepost-map [:? map?]] 52 | [:schema [:? FnSchemaDecl]] 53 | [:body [:* :any]]]]) 54 | 55 | (def InstrumentedDefnArgs 56 | [:catn 57 | [:docstring [:? string?]] 58 | [:attr-map [:? map?]] 59 | [:code 60 | [:altn 61 | [:multi-arity 62 | [:catn 63 | [:defs [:+ [:and list? ArityDecl]]] 64 | [:attr-map [:? map?]]]] 65 | [:single-arity ArityDecl]]]]) 66 | 67 | (usetime 68 | (def *config 69 | "The global runtime configuration atom for snoop's instrumented functions. 70 | 71 | Refer to the project README for option details." 72 | cfg/*config) 73 | 74 | (defn get-snoop-config [fn-var] 75 | @(or (-> fn-var #?(:cljs deref) meta ::config-atom) 76 | cfg/*config))) 77 | 78 | (usetime 79 | (defn validation-allowed? 80 | "Returns whether the `boundary` of the function is allowed to be validated 81 | with the active config." 82 | ([boundary fn-ns fn-name cfg] 83 | (let [enabled? ((case boundary 84 | :input :instrument? 85 | :output :outstrument?) cfg) 86 | ns-blacklisted? (contains? (:blacklist-ns cfg) fn-ns) 87 | ns-whitelisted? (contains? (:whitelist-ns cfg) fn-ns) 88 | fn-blacklisted? (contains? (get-in cfg [:blacklist-fn fn-ns]) fn-name) 89 | fn-whitelisted? (contains? (get-in cfg [:whitelist-fn fn-ns]) fn-name)] 90 | (->> (:whitelist-by-default? cfg) 91 | (and (not ns-blacklisted?)) 92 | (or ns-whitelisted?) 93 | (and (not fn-blacklisted?)) 94 | (or fn-whitelisted?) 95 | (and enabled?)))) 96 | ([boundary fn-var] 97 | (let [fn-name (-> fn-var meta :name) 98 | fn-ns (-> fn-var meta :ns str symbol)] 99 | (validation-allowed? boundary fn-ns fn-name (get-snoop-config fn-var)))))) 100 | 101 | (usetime 102 | (defn validate 103 | "Calls the corresponding error fn when the input/output fails schema validation" 104 | [boundary schema args {:keys [fn-sym fn-params config]}] 105 | (let [cfg (or config @cfg/*config) 106 | fn-name (symbol (name fn-sym)) 107 | fn-ns (symbol (namespace fn-sym)) 108 | {:keys [on-fail]} (case boundary 109 | :input {:on-fail :on-instrument-fail} 110 | :output {:on-fail :on-outstrument-fail}) 111 | err (and (validation-allowed? boundary fn-ns fn-name cfg) 112 | (m/explain schema args (:malli-opts cfg)))] 113 | (when err 114 | ((on-fail cfg) {:explainer-error err 115 | :ns fn-ns 116 | :name fn-name 117 | :params fn-params}))))) 118 | 119 | (deftime 120 | (defn- current-ns [] 121 | (symbol (str *ns*)))) 122 | 123 | (deftime 124 | (defn- read-param-decl [parsed-arity] 125 | {:pre [(:params-decl parsed-arity)]} 126 | (let [{:keys [params schema schema-used?]} 127 | (reduce (fn [acc [alt decl]] 128 | (let [[param schema] (case alt 129 | :instrumented 130 | [(:param decl) (:schema decl)] 131 | 132 | :param 133 | [decl nil])] 134 | (-> acc 135 | (update :params conj param) 136 | (cond-> schema 137 | (-> (update :schema conj schema) 138 | (assoc :schema-used? true))) 139 | (cond-> (not (or schema (= '& decl))) 140 | (update :schema conj :any))))) 141 | {:params [] 142 | :schema [:cat] 143 | :schema-used? false} 144 | (:params-decl parsed-arity))] 145 | {:params params 146 | :schema (when schema-used? schema)}))) 147 | 148 | (usetime 149 | (defn get-arity-schema [arityn form config] 150 | (let [schema+ (m/schema form)] 151 | (case (m/type schema+) 152 | :=> (select-keys (m/-function-info schema+) 153 | #{:input :output}) 154 | :function 155 | (let [singles (m/children schema+) 156 | log-error (:log-error-fn config)] 157 | (loop [idx (dec (count singles)) 158 | match nil] 159 | (if (neg? idx) 160 | (if (nil? match) 161 | (log-error "Snoop Error: Could not find matching arity of" arityn "in" schema+) 162 | match) 163 | (let [{:keys [arity] :as info} (m/-function-info (nth singles idx))] 164 | (recur (dec idx) 165 | (if (= arity arityn) 166 | (select-keys info #{:input :output}) 167 | match)))))))))) 168 | 169 | (deftime 170 | (defn- gen-param-syms [n] 171 | (vec (repeatedly n #(gensym "param")))) 172 | 173 | (defn- modify-arity-rf 174 | "Reducing function that processes each arity declared by `>defn`" 175 | [acc {:keys [fn-name schema prepost-map body arityn max-fixed-arity params param-schema]}] 176 | (let [map-splice? (and (= :varargs arityn) 177 | (let [last-param (peek params)] 178 | (or (map? last-param) 179 | (and (list? last-param) (map? (first last-param)))))) 180 | new-params (if map-splice? (into [] (remove #{'&}) params) params) 181 | {:keys [params-proxy 182 | arg-capture-expr]} (if (= :varargs arityn) 183 | (let [fixed-syms (gen-param-syms max-fixed-arity) 184 | rest-sym (gensym "rest")] 185 | {:params-proxy (into fixed-syms (if map-splice? 186 | ['& {:as rest-sym}] 187 | ['& rest-sym])) 188 | :arg-capture-expr (if map-splice? 189 | (conj fixed-syms rest-sym) 190 | `(into ~fixed-syms ~rest-sym))}) 191 | (let [v (gen-param-syms arityn)] 192 | {:params-proxy v :arg-capture-expr v})) 193 | given-schema (or schema 194 | (some->> (:=> prepost-map) 195 | (m/parse FnSchemaDecl))) 196 | given-input-schema (or (:input given-schema) 197 | (when (and (contains? given-schema :input-unwrapped) 198 | (seq (:input-unwrapped given-schema))) 199 | (into [:cat] (:input-unwrapped given-schema)))) 200 | given-output-schema (:output given-schema) 201 | cfg-sym (gensym "cfg") 202 | output-sym (gensym "output") 203 | validation-ctx-sym (gensym "validation-ctx") 204 | args-sym (gensym "args") 205 | validation-args-sym (gensym "validation-args") 206 | modified-body `(let [fn-var# (var ~fn-name) 207 | ~cfg-sym (get-snoop-config fn-var#) 208 | schemas# ~(if given-schema 209 | {:input given-input-schema 210 | :output given-output-schema} 211 | `(some-> (get-in (m/function-schemas) 212 | ['~(current-ns) '~fn-name :schema]) 213 | (m/form) 214 | (as-> form# 215 | (get-arity-schema ~arityn form# ~cfg-sym)))) 216 | input-schema# (:input schemas#) 217 | output-schema# (:output schemas#) 218 | ~validation-ctx-sym {:fn-sym '~(symbol (str *ns*) (str fn-name)) 219 | :fn-params '~params 220 | :config ~cfg-sym} 221 | ~args-sym ~arg-capture-expr 222 | ~validation-args-sym ~(if map-splice? 223 | `(update ~args-sym ~max-fixed-arity #(or % {})) 224 | args-sym)] 225 | ~(when param-schema 226 | `(validate :input ~param-schema ~validation-args-sym ~validation-ctx-sym)) 227 | (when input-schema# 228 | (validate :input input-schema# ~validation-args-sym ~validation-ctx-sym)) 229 | (let [~new-params ~args-sym 230 | ~output-sym (do ~@body)] 231 | (when output-schema# 232 | (validate :output output-schema# ~output-sym ~validation-ctx-sym)) 233 | ~output-sym))] 234 | (-> acc 235 | (cond-> given-schema 236 | (assoc-in [:arities arityn] {:input given-input-schema 237 | :output given-output-schema})) 238 | (update :raw-parts conj (list params-proxy prepost-map modified-body)))))) 239 | 240 | (deftime 241 | (defn- eval-macro-config [macro-config base-config] 242 | (when (some nil? (map resolve ['clojure.core/def 'clojure.core/assoc 'clojure.core/merge])) 243 | (refer-clojure)) 244 | (enc/catching (enc/merge base-config 245 | (eval macro-config)) 246 | e 247 | (let [log (resolve (:log-fn-sym base-config))] 248 | (log "ERROR EXPANDING >defn: failed to eval the provided ::snoop/macro-config at compile-time." 249 | "\nMake sure:" 250 | "\n• You have not passed any locals." 251 | "\n• All symbols have been bound at compile-time (eg with 'def')" 252 | "\n" 253 | "\nYou provided:") 254 | (log (with-out-str (pp/pprint macro-config))) 255 | (log "\nSurfacing error below:\n") 256 | (throw e))))) 257 | 258 | (def -defn-option-keys #{::config-atom ::macro-config}) 259 | 260 | (deftime 261 | (defn >defn* 262 | "Generates the output code for `>defn` from the declaration in `args`." 263 | [&env fn-name args] 264 | (let [{:keys [docstring] 265 | [arity-type 266 | code] :code 267 | :as parse-result} (m/parse InstrumentedDefnArgs args) 268 | input-attr-map (enc/merge (:attr-map parse-result) (:attr-map code)) 269 | opts (select-keys (enc/merge input-attr-map (meta fn-name)) 270 | -defn-option-keys) 271 | parsed-arities (into [] 272 | (map (fn [a] 273 | (let [{:keys [params] 274 | param-schema :schema} (read-param-decl a)] 275 | (assoc a 276 | :arityn (arity-of-params params) 277 | :param-schema param-schema 278 | :params params)))) 279 | (case arity-type 280 | :single-arity (vector code) 281 | :multi-arity (:defs code))) 282 | max-fixed-arity (->> parsed-arities 283 | (mapv (fn [{:keys [arityn params]}] 284 | (if (int? arityn) 285 | arityn 286 | (- (count params) 2)))) 287 | (apply max 0)) 288 | {:keys [enabled?] 289 | :as macro-cfg} (eval-macro-config (::macro-config opts) 290 | (cfg/get-compiletime-config)) 291 | sym-for-defn (:defn-sym macro-cfg) 292 | attr-map (cond->> input-attr-map (not enabled?) 293 | (enc/remove-keys -defn-option-keys)) 294 | {:keys [raw-parts]} (reduce (fn [acc {:keys [prepost-map body] 295 | :as parsed-arity}] 296 | (if enabled? 297 | (modify-arity-rf acc (assoc parsed-arity 298 | :fn-name fn-name 299 | :max-fixed-arity max-fixed-arity)) 300 | (update acc :raw-parts conj 301 | (apply list (:params (read-param-decl parsed-arity)) 302 | prepost-map body)))) 303 | {:raw-parts [] :arities {}} 304 | parsed-arities) 305 | args-for-defn (into (enc/conj-some [] docstring attr-map) 306 | raw-parts)] 307 | (apply list 308 | `do 309 | (apply list sym-for-defn 310 | (if enabled? 311 | (vary-meta fn-name assoc ::instrumented? true) 312 | (vary-meta fn-name #(enc/remove-keys -defn-option-keys %))) 313 | args-for-defn) 314 | (when (and (:ns &env) enabled?) 315 | [`(set! ~fn-name (patched-cljs/meta-fn ~fn-name (enc/assoc-some {} ::config-atom ~(::config-atom opts))))]))))) 316 | 317 | (deftime 318 | (defmacro >defn 319 | "Wraps `defn` with instrumentation features if enabled. 320 | 321 | A malli schema can be specified in these places: 322 | - The first thing in the body 323 | - The `:=>` key of the prepost map 324 | - malli's function schema registry (using `m/=>`) 325 | 326 | Additional options can be provided via metadata or an attr-map: 327 | - `::snoop/macro-config` - a map used to override the global compile-time configuration for this function. 328 | - `::snoop/config-atom` - a runtime config atom to use in place of the default `*config` atom. 329 | " 330 | {:style/indent :defn} 331 | [sym & decls] 332 | (>defn* &env sym decls)) 333 | 334 | (defmacro >defn- 335 | "Same as `>defn` but creates a privately scoped var." 336 | {:style/indent :defn} 337 | [sym & decls] 338 | (>defn* &env (vary-meta sym assoc :private true) decls))) 339 | 340 | (comment 341 | 342 | (m/=> print [:function 343 | [:=> [:cat string?] nil?] 344 | [:=> [:cat string? string?] nil?]]) 345 | 346 | (macroexpand-1 347 | (quote 348 | (>defn print 349 | ([first] (println first)) 350 | ([first second] (println first second))))) 351 | 352 | (>defn print 353 | ([first] 354 | 355 | (println first)) 356 | ([first second] 357 | [:=> [:cat string? string?] nil?] 358 | (println first second))) 359 | 360 | (print "Hello!") 361 | 362 | (m/explain nil? nil) 363 | 364 | (clojure.core/some-> 365 | (clojure.core/get-in 366 | (malli.core/function-schemas) 367 | [(quote user) (quote print) :schema]) 368 | (malli.core/form) 369 | (clojure.core/as-> 370 | form__15071__auto__ 371 | {:output (clojure.core/nth form__15071__auto__ 2), 372 | :input (clojure.core/nth form__15071__auto__ 1)})) 373 | 374 | (m/validate [:=> {:registry {::small-int [:int {:min -100, :max 100}]}} 375 | [:cat ::small-int] :int] (fn [])) 376 | 377 | (-> (m/function-schemas) 378 | (get-in ['user 'print]) 379 | :schema 380 | m/form 381 | m/schema 382 | m/-function-info) 383 | 384 | (-> (m/function-schemas) 385 | (get-in ['user 'print]) 386 | :schema 387 | m/form 388 | m/schema 389 | m/type) 390 | 391 | (-> (m/function-schemas) 392 | (get-in ['user 'print]) 393 | :schema 394 | m/form 395 | m/schema 396 | m/type) 397 | (m/=> lol [:=> [:cat int?] int?]) 398 | 399 | (-> (m/function-schemas) 400 | (get-in ['user 'lol]) 401 | :schema 402 | m/form 403 | m/schema 404 | m/-function-info) 405 | 406 | (-> (m/function-schemas) 407 | (get-in ['user 'lol]) 408 | :schema 409 | m/form 410 | m/schema 411 | m/-function-info) 412 | 413 | (m/children (m/schema [:function {} [:=> [:cat int?] int?]])) 414 | 415 | (m/=> x [:function 416 | [:=> [:cat int?] :any] 417 | [:=> [:cat int?] :any]]) 418 | (m/properties [:function {:closed true} [:=> [:cat int?] :any]]) 419 | 420 | (require '[malli.util :as mu]) 421 | 422 | (m/children [:function {:registry {::small-int [:int {:min -100, :max 100}]}} 423 | [:=> [:cat ::small-int] :int]]) 424 | 425 | (m/-function-info (m/schema [:=> [:cat :int [:* int?]] :int])) 426 | 427 | (m/=> function-schema-multi [:function 428 | [:=> [:cat int?] int?] 429 | [:=> [:cat map? :any] int?] 430 | [:=> [:cat string? :any [:+ string?]] int?]]) 431 | (>defn function-schema-multi 432 | ([_x] 433 | 5) 434 | ([_x y] 435 | y) 436 | ([_x y & _zs] 437 | y)) 438 | 439 | (-> (get-in (m/function-schemas) ['user 'function-schema-multi :schema]) 440 | (m/form) 441 | (as-> f (s/get-arity-schema 1 f @s/*config)) 442 | (as-> m 443 | (:output m))) 444 | 445 | ;; 446 | ) 447 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asn1.js@^5.2.0: 6 | version "5.4.1" 7 | resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" 8 | integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== 9 | dependencies: 10 | bn.js "^4.0.0" 11 | inherits "^2.0.1" 12 | minimalistic-assert "^1.0.0" 13 | safer-buffer "^2.1.0" 14 | 15 | assert@^1.1.1: 16 | version "1.5.0" 17 | resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" 18 | integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== 19 | dependencies: 20 | object-assign "^4.1.1" 21 | util "0.10.3" 22 | 23 | base64-js@^1.0.2: 24 | version "1.5.1" 25 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 26 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 27 | 28 | bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: 29 | version "4.12.0" 30 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" 31 | integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== 32 | 33 | bn.js@^5.0.0, bn.js@^5.1.1: 34 | version "5.2.0" 35 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" 36 | integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== 37 | 38 | brorand@^1.0.1, brorand@^1.1.0: 39 | version "1.1.0" 40 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" 41 | integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= 42 | 43 | browserify-aes@^1.0.0, browserify-aes@^1.0.4: 44 | version "1.2.0" 45 | resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" 46 | integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== 47 | dependencies: 48 | buffer-xor "^1.0.3" 49 | cipher-base "^1.0.0" 50 | create-hash "^1.1.0" 51 | evp_bytestokey "^1.0.3" 52 | inherits "^2.0.1" 53 | safe-buffer "^5.0.1" 54 | 55 | browserify-cipher@^1.0.0: 56 | version "1.0.1" 57 | resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" 58 | integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== 59 | dependencies: 60 | browserify-aes "^1.0.4" 61 | browserify-des "^1.0.0" 62 | evp_bytestokey "^1.0.0" 63 | 64 | browserify-des@^1.0.0: 65 | version "1.0.2" 66 | resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" 67 | integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== 68 | dependencies: 69 | cipher-base "^1.0.1" 70 | des.js "^1.0.0" 71 | inherits "^2.0.1" 72 | safe-buffer "^5.1.2" 73 | 74 | browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: 75 | version "4.1.0" 76 | resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" 77 | integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== 78 | dependencies: 79 | bn.js "^5.0.0" 80 | randombytes "^2.0.1" 81 | 82 | browserify-sign@^4.0.0: 83 | version "4.2.1" 84 | resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" 85 | integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== 86 | dependencies: 87 | bn.js "^5.1.1" 88 | browserify-rsa "^4.0.1" 89 | create-hash "^1.2.0" 90 | create-hmac "^1.1.7" 91 | elliptic "^6.5.3" 92 | inherits "^2.0.4" 93 | parse-asn1 "^5.1.5" 94 | readable-stream "^3.6.0" 95 | safe-buffer "^5.2.0" 96 | 97 | browserify-zlib@^0.2.0: 98 | version "0.2.0" 99 | resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" 100 | integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== 101 | dependencies: 102 | pako "~1.0.5" 103 | 104 | buffer-xor@^1.0.3: 105 | version "1.0.3" 106 | resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 107 | integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= 108 | 109 | buffer@^4.3.0: 110 | version "4.9.2" 111 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" 112 | integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== 113 | dependencies: 114 | base64-js "^1.0.2" 115 | ieee754 "^1.1.4" 116 | isarray "^1.0.0" 117 | 118 | builtin-status-codes@^3.0.0: 119 | version "3.0.0" 120 | resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" 121 | integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= 122 | 123 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: 124 | version "1.0.4" 125 | resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" 126 | integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== 127 | dependencies: 128 | inherits "^2.0.1" 129 | safe-buffer "^5.0.1" 130 | 131 | console-browserify@^1.1.0: 132 | version "1.2.0" 133 | resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" 134 | integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== 135 | 136 | constants-browserify@^1.0.0: 137 | version "1.0.0" 138 | resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" 139 | integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= 140 | 141 | core-util-is@~1.0.0: 142 | version "1.0.2" 143 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 144 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 145 | 146 | create-ecdh@^4.0.0: 147 | version "4.0.4" 148 | resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" 149 | integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== 150 | dependencies: 151 | bn.js "^4.1.0" 152 | elliptic "^6.5.3" 153 | 154 | create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: 155 | version "1.2.0" 156 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" 157 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== 158 | dependencies: 159 | cipher-base "^1.0.1" 160 | inherits "^2.0.1" 161 | md5.js "^1.3.4" 162 | ripemd160 "^2.0.1" 163 | sha.js "^2.4.0" 164 | 165 | create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: 166 | version "1.1.7" 167 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" 168 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== 169 | dependencies: 170 | cipher-base "^1.0.3" 171 | create-hash "^1.1.0" 172 | inherits "^2.0.1" 173 | ripemd160 "^2.0.0" 174 | safe-buffer "^5.0.1" 175 | sha.js "^2.4.8" 176 | 177 | crypto-browserify@^3.11.0: 178 | version "3.12.0" 179 | resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" 180 | integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== 181 | dependencies: 182 | browserify-cipher "^1.0.0" 183 | browserify-sign "^4.0.0" 184 | create-ecdh "^4.0.0" 185 | create-hash "^1.1.0" 186 | create-hmac "^1.1.0" 187 | diffie-hellman "^5.0.0" 188 | inherits "^2.0.1" 189 | pbkdf2 "^3.0.3" 190 | public-encrypt "^4.0.0" 191 | randombytes "^2.0.0" 192 | randomfill "^1.0.3" 193 | 194 | des.js@^1.0.0: 195 | version "1.0.1" 196 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" 197 | integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== 198 | dependencies: 199 | inherits "^2.0.1" 200 | minimalistic-assert "^1.0.0" 201 | 202 | diffie-hellman@^5.0.0: 203 | version "5.0.3" 204 | resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" 205 | integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== 206 | dependencies: 207 | bn.js "^4.1.0" 208 | miller-rabin "^4.0.0" 209 | randombytes "^2.0.0" 210 | 211 | domain-browser@^1.1.1: 212 | version "1.2.0" 213 | resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" 214 | integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== 215 | 216 | elliptic@^6.5.3: 217 | version "6.5.4" 218 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" 219 | integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== 220 | dependencies: 221 | bn.js "^4.11.9" 222 | brorand "^1.1.0" 223 | hash.js "^1.0.0" 224 | hmac-drbg "^1.0.1" 225 | inherits "^2.0.4" 226 | minimalistic-assert "^1.0.1" 227 | minimalistic-crypto-utils "^1.0.1" 228 | 229 | error-stack-parser@^2.0.6: 230 | version "2.0.6" 231 | resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" 232 | integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== 233 | dependencies: 234 | stackframe "^1.1.1" 235 | 236 | events@^3.0.0: 237 | version "3.3.0" 238 | resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 239 | integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 240 | 241 | evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: 242 | version "1.0.3" 243 | resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" 244 | integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== 245 | dependencies: 246 | md5.js "^1.3.4" 247 | safe-buffer "^5.1.1" 248 | 249 | hash-base@^3.0.0: 250 | version "3.1.0" 251 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" 252 | integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== 253 | dependencies: 254 | inherits "^2.0.4" 255 | readable-stream "^3.6.0" 256 | safe-buffer "^5.2.0" 257 | 258 | hash.js@^1.0.0, hash.js@^1.0.3: 259 | version "1.1.7" 260 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" 261 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== 262 | dependencies: 263 | inherits "^2.0.3" 264 | minimalistic-assert "^1.0.1" 265 | 266 | hmac-drbg@^1.0.1: 267 | version "1.0.1" 268 | resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 269 | integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= 270 | dependencies: 271 | hash.js "^1.0.3" 272 | minimalistic-assert "^1.0.0" 273 | minimalistic-crypto-utils "^1.0.1" 274 | 275 | https-browserify@^1.0.0: 276 | version "1.0.0" 277 | resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" 278 | integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= 279 | 280 | ieee754@^1.1.4: 281 | version "1.2.1" 282 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 283 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 284 | 285 | inherits@2.0.1: 286 | version "2.0.1" 287 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 288 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= 289 | 290 | inherits@2.0.3: 291 | version "2.0.3" 292 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 293 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 294 | 295 | inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: 296 | version "2.0.4" 297 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 298 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 299 | 300 | isarray@^1.0.0, isarray@~1.0.0: 301 | version "1.0.0" 302 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 303 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 304 | 305 | isexe@^2.0.0: 306 | version "2.0.0" 307 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 308 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 309 | 310 | "js-tokens@^3.0.0 || ^4.0.0": 311 | version "4.0.0" 312 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 313 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 314 | 315 | loose-envify@^1.1.0, loose-envify@^1.4.0: 316 | version "1.4.0" 317 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 318 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 319 | dependencies: 320 | js-tokens "^3.0.0 || ^4.0.0" 321 | 322 | md5.js@^1.3.4: 323 | version "1.3.5" 324 | resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" 325 | integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== 326 | dependencies: 327 | hash-base "^3.0.0" 328 | inherits "^2.0.1" 329 | safe-buffer "^5.1.2" 330 | 331 | miller-rabin@^4.0.0: 332 | version "4.0.1" 333 | resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" 334 | integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== 335 | dependencies: 336 | bn.js "^4.0.0" 337 | brorand "^1.0.1" 338 | 339 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: 340 | version "1.0.1" 341 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" 342 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== 343 | 344 | minimalistic-crypto-utils@^1.0.1: 345 | version "1.0.1" 346 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 347 | integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= 348 | 349 | node-libs-browser@^2.2.1: 350 | version "2.2.1" 351 | resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" 352 | integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== 353 | dependencies: 354 | assert "^1.1.1" 355 | browserify-zlib "^0.2.0" 356 | buffer "^4.3.0" 357 | console-browserify "^1.1.0" 358 | constants-browserify "^1.0.0" 359 | crypto-browserify "^3.11.0" 360 | domain-browser "^1.1.1" 361 | events "^3.0.0" 362 | https-browserify "^1.0.0" 363 | os-browserify "^0.3.0" 364 | path-browserify "0.0.1" 365 | process "^0.11.10" 366 | punycode "^1.2.4" 367 | querystring-es3 "^0.2.0" 368 | readable-stream "^2.3.3" 369 | stream-browserify "^2.0.1" 370 | stream-http "^2.7.2" 371 | string_decoder "^1.0.0" 372 | timers-browserify "^2.0.4" 373 | tty-browserify "0.0.0" 374 | url "^0.11.0" 375 | util "^0.11.0" 376 | vm-browserify "^1.0.1" 377 | 378 | object-assign@^4.1.1: 379 | version "4.1.1" 380 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 381 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 382 | 383 | os-browserify@^0.3.0: 384 | version "0.3.0" 385 | resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" 386 | integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= 387 | 388 | pako@~1.0.5: 389 | version "1.0.11" 390 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" 391 | integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== 392 | 393 | parse-asn1@^5.0.0, parse-asn1@^5.1.5: 394 | version "5.1.6" 395 | resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" 396 | integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== 397 | dependencies: 398 | asn1.js "^5.2.0" 399 | browserify-aes "^1.0.0" 400 | evp_bytestokey "^1.0.0" 401 | pbkdf2 "^3.0.3" 402 | safe-buffer "^5.1.1" 403 | 404 | path-browserify@0.0.1: 405 | version "0.0.1" 406 | resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" 407 | integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== 408 | 409 | pbkdf2@^3.0.3: 410 | version "3.1.2" 411 | resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" 412 | integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== 413 | dependencies: 414 | create-hash "^1.1.2" 415 | create-hmac "^1.1.4" 416 | ripemd160 "^2.0.1" 417 | safe-buffer "^5.0.1" 418 | sha.js "^2.4.8" 419 | 420 | platform@1.3.5: 421 | version "1.3.5" 422 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" 423 | integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== 424 | 425 | process-nextick-args@~2.0.0: 426 | version "2.0.1" 427 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 428 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 429 | 430 | process@^0.11.10: 431 | version "0.11.10" 432 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 433 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 434 | 435 | prop-types@^15.6.2: 436 | version "15.7.2" 437 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" 438 | integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== 439 | dependencies: 440 | loose-envify "^1.4.0" 441 | object-assign "^4.1.1" 442 | react-is "^16.8.1" 443 | 444 | public-encrypt@^4.0.0: 445 | version "4.0.3" 446 | resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" 447 | integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== 448 | dependencies: 449 | bn.js "^4.1.0" 450 | browserify-rsa "^4.0.0" 451 | create-hash "^1.1.0" 452 | parse-asn1 "^5.0.0" 453 | randombytes "^2.0.1" 454 | safe-buffer "^5.1.2" 455 | 456 | punycode@1.3.2: 457 | version "1.3.2" 458 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 459 | integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= 460 | 461 | punycode@^1.2.4: 462 | version "1.4.1" 463 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 464 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= 465 | 466 | querystring-es3@^0.2.0: 467 | version "0.2.1" 468 | resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" 469 | integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= 470 | 471 | querystring@0.2.0: 472 | version "0.2.0" 473 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 474 | integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= 475 | 476 | randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: 477 | version "2.1.0" 478 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 479 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 480 | dependencies: 481 | safe-buffer "^5.1.0" 482 | 483 | randomfill@^1.0.3: 484 | version "1.0.4" 485 | resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" 486 | integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== 487 | dependencies: 488 | randombytes "^2.0.5" 489 | safe-buffer "^5.1.0" 490 | 491 | react-dom@16.13.0: 492 | version "16.13.0" 493 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.0.tgz#cdde54b48eb9e8a0ca1b3dc9943d9bb409b81866" 494 | integrity sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg== 495 | dependencies: 496 | loose-envify "^1.1.0" 497 | object-assign "^4.1.1" 498 | prop-types "^15.6.2" 499 | scheduler "^0.19.0" 500 | 501 | react-is@^16.8.1: 502 | version "16.13.1" 503 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 504 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 505 | 506 | react@16.13.0: 507 | version "16.13.0" 508 | resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7" 509 | integrity sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ== 510 | dependencies: 511 | loose-envify "^1.1.0" 512 | object-assign "^4.1.1" 513 | prop-types "^15.6.2" 514 | 515 | readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: 516 | version "2.3.7" 517 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 518 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 519 | dependencies: 520 | core-util-is "~1.0.0" 521 | inherits "~2.0.3" 522 | isarray "~1.0.0" 523 | process-nextick-args "~2.0.0" 524 | safe-buffer "~5.1.1" 525 | string_decoder "~1.1.1" 526 | util-deprecate "~1.0.1" 527 | 528 | readable-stream@^3.6.0: 529 | version "3.6.0" 530 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 531 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 532 | dependencies: 533 | inherits "^2.0.3" 534 | string_decoder "^1.1.1" 535 | util-deprecate "^1.0.1" 536 | 537 | readline-sync@^1.4.7: 538 | version "1.4.10" 539 | resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" 540 | integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== 541 | 542 | ripemd160@^2.0.0, ripemd160@^2.0.1: 543 | version "2.0.2" 544 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" 545 | integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== 546 | dependencies: 547 | hash-base "^3.0.0" 548 | inherits "^2.0.1" 549 | 550 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: 551 | version "5.2.1" 552 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 553 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 554 | 555 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 556 | version "5.1.2" 557 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 558 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 559 | 560 | safer-buffer@^2.1.0: 561 | version "2.1.2" 562 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 563 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 564 | 565 | scheduler@^0.19.0: 566 | version "0.19.1" 567 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" 568 | integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== 569 | dependencies: 570 | loose-envify "^1.1.0" 571 | object-assign "^4.1.1" 572 | 573 | setimmediate@^1.0.4: 574 | version "1.0.5" 575 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 576 | integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= 577 | 578 | sha.js@^2.4.0, sha.js@^2.4.8: 579 | version "2.4.11" 580 | resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" 581 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 582 | dependencies: 583 | inherits "^2.0.1" 584 | safe-buffer "^5.0.1" 585 | 586 | shadow-cljs-jar@1.3.2: 587 | version "1.3.2" 588 | resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" 589 | integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== 590 | 591 | shadow-cljs@2.15.2: 592 | version "2.15.2" 593 | resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.2.tgz#195ad2cc45d3334920e629721f06c6d63802b1ac" 594 | integrity sha512-WPlSMkGgbU5b2nrt+Y1A1TsPs5Rip/JvCxGG2t2Pvzo+pLJ+RcpkZgAxjNQNNA7VYWEh5Pqwyvq5KzQ+0LMsxw== 595 | dependencies: 596 | node-libs-browser "^2.2.1" 597 | readline-sync "^1.4.7" 598 | shadow-cljs-jar "1.3.2" 599 | source-map-support "^0.4.15" 600 | which "^1.3.1" 601 | ws "^7.4.6" 602 | 603 | source-map-support@^0.4.15: 604 | version "0.4.18" 605 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 606 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== 607 | dependencies: 608 | source-map "^0.5.6" 609 | 610 | source-map@0.5.6: 611 | version "0.5.6" 612 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 613 | integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= 614 | 615 | source-map@^0.5.6: 616 | version "0.5.7" 617 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 618 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 619 | 620 | stack-generator@^2.0.5: 621 | version "2.0.5" 622 | resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" 623 | integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q== 624 | dependencies: 625 | stackframe "^1.1.1" 626 | 627 | stack-trace@0.0.10: 628 | version "0.0.10" 629 | resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 630 | integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= 631 | 632 | stackframe@^1.1.1: 633 | version "1.2.0" 634 | resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" 635 | integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== 636 | 637 | stacktrace-gps@^3.0.4: 638 | version "3.0.4" 639 | resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a" 640 | integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg== 641 | dependencies: 642 | source-map "0.5.6" 643 | stackframe "^1.1.1" 644 | 645 | stacktrace-js@^2.0.2: 646 | version "2.0.2" 647 | resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" 648 | integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== 649 | dependencies: 650 | error-stack-parser "^2.0.6" 651 | stack-generator "^2.0.5" 652 | stacktrace-gps "^3.0.4" 653 | 654 | stream-browserify@^2.0.1: 655 | version "2.0.2" 656 | resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" 657 | integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== 658 | dependencies: 659 | inherits "~2.0.1" 660 | readable-stream "^2.0.2" 661 | 662 | stream-http@^2.7.2: 663 | version "2.8.3" 664 | resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" 665 | integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== 666 | dependencies: 667 | builtin-status-codes "^3.0.0" 668 | inherits "^2.0.1" 669 | readable-stream "^2.3.6" 670 | to-arraybuffer "^1.0.0" 671 | xtend "^4.0.0" 672 | 673 | string_decoder@^1.0.0, string_decoder@^1.1.1: 674 | version "1.3.0" 675 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 676 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 677 | dependencies: 678 | safe-buffer "~5.2.0" 679 | 680 | string_decoder@~1.1.1: 681 | version "1.1.1" 682 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 683 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 684 | dependencies: 685 | safe-buffer "~5.1.0" 686 | 687 | timers-browserify@^2.0.4: 688 | version "2.0.12" 689 | resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" 690 | integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== 691 | dependencies: 692 | setimmediate "^1.0.4" 693 | 694 | to-arraybuffer@^1.0.0: 695 | version "1.0.1" 696 | resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" 697 | integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= 698 | 699 | tty-browserify@0.0.0: 700 | version "0.0.0" 701 | resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" 702 | integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= 703 | 704 | url@^0.11.0: 705 | version "0.11.0" 706 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" 707 | integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= 708 | dependencies: 709 | punycode "1.3.2" 710 | querystring "0.2.0" 711 | 712 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 713 | version "1.0.2" 714 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 715 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 716 | 717 | util@0.10.3: 718 | version "0.10.3" 719 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 720 | integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= 721 | dependencies: 722 | inherits "2.0.1" 723 | 724 | util@^0.11.0: 725 | version "0.11.1" 726 | resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" 727 | integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== 728 | dependencies: 729 | inherits "2.0.3" 730 | 731 | vm-browserify@^1.0.1: 732 | version "1.1.2" 733 | resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" 734 | integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== 735 | 736 | which@^1.3.1: 737 | version "1.3.1" 738 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 739 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 740 | dependencies: 741 | isexe "^2.0.0" 742 | 743 | ws@7.3.1: 744 | version "7.3.1" 745 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" 746 | integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== 747 | 748 | ws@^7.4.6: 749 | version "7.5.3" 750 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" 751 | integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== 752 | 753 | xtend@^4.0.0: 754 | version "4.0.2" 755 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 756 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 757 | --------------------------------------------------------------------------------