├── 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 | [](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 | 
35 |
36 | ## Installation
37 |
38 | [](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 |
--------------------------------------------------------------------------------