|
1 | | -(ns clojure+.util) |
| 1 | +(ns clojure+.util |
| 2 | + (:require |
| 3 | + [clojure.string :as str])) |
| 4 | + |
| 5 | +(def runtime-version |
| 6 | + (let [v (System/getProperty "java.version")] |
| 7 | + (if (str/starts-with? v "1.") |
| 8 | + (-> (str/split v #"\.") second Long/parseLong) |
| 9 | + (-> (str/split v #"\.") first Long/parseLong)))) |
| 10 | + |
| 11 | +(defmacro if-java-version-gte |
| 12 | + ([version if-branch] |
| 13 | + (when (<= version runtime-version) |
| 14 | + if-branch)) |
| 15 | + ([version if-branch else-branch] |
| 16 | + (if (<= version runtime-version) |
| 17 | + if-branch |
| 18 | + else-branch))) |
| 19 | + |
| 20 | +(defmacro if-clojure-version-gte |
| 21 | + ([version if-branch] |
| 22 | + `(if-clojure-version-gte ~version ~if-branch nil)) |
| 23 | + ([version if-branch else-branch] |
| 24 | + (if (>= (compare |
| 25 | + [(:major *clojure-version*) (:minor *clojure-version*) (:incremental *clojure-version* 0)] |
| 26 | + (->> (str/split version #"\.") (mapv #(Long/parseLong %)))) |
| 27 | + 0) |
| 28 | + if-branch |
| 29 | + else-branch))) |
| 30 | + |
| 31 | +(def bb? |
| 32 | + (System/getProperty "babashka.version")) |
| 33 | + |
| 34 | +(defmacro if-not-bb |
| 35 | + ([if-branch] |
| 36 | + (when-not bb? |
| 37 | + if-branch)) |
| 38 | + ([if-branch else-branch] |
| 39 | + (if-not bb? |
| 40 | + if-branch |
| 41 | + else-branch))) |
| 42 | + |
| 43 | +(defn rebind-dynamic-impl |
| 44 | + "Clojure creates a lot of layers of dynamic bindings for *data-readers* |
| 45 | + (e.g. clojure.main, require, Compiler/load etc). Some of them are not |
| 46 | + directly nested, but instead siblings. This causes issues even in simplest |
| 47 | + cases: e.g. loading namespace vs executing a function from that namespace, |
| 48 | + latter won't see changes made by former. |
| 49 | +
|
| 50 | + This might lead to very confusing behaviors, like: |
| 51 | +
|
| 52 | + (print/install!) |
| 53 | +
|
| 54 | + (defn -main [& args] |
| 55 | + (println *data-readers*)) |
| 56 | + ;; => {} |
| 57 | +
|
| 58 | + Modifying root binding is not enough, as the changes don't automatically |
| 59 | + propagate down the stack. Here we are abusing pop/pushBindings to add |
| 60 | + desired readers to every frame in dynamic vars stack." |
| 61 | + [var f args] |
| 62 | + (let [bindings (clojure.lang.Var/getThreadBindings)] |
| 63 | + (try |
| 64 | + (clojure.lang.Var/popThreadBindings) |
| 65 | + (try |
| 66 | + (rebind-dynamic-impl var f args) |
| 67 | + (finally |
| 68 | + (clojure.lang.Var/pushThreadBindings |
| 69 | + (apply update bindings var f args)))) |
| 70 | + (catch IllegalStateException _ |
| 71 | + nil)))) |
| 72 | + |
| 73 | +(defmacro rebind-dynamic [var f & args] |
| 74 | + `(do |
| 75 | + (alter-var-root (var ~var) ~f ~@args) |
| 76 | + ~(if true #_bb? |
| 77 | + `(when (thread-bound? (var ~var)) |
| 78 | + (set! ~var (~f ~var ~@args))) |
| 79 | + `(rebind-dynamic-impl (var ~var) ~f ~(vec args))))) |
2 | 80 |
|
3 | 81 | (defn color? [] |
4 | 82 | (cond |
|
0 commit comments