Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/mate/js_obj.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#?(:cljs
(ns mate.js-obj
{:doc "Macros for destructuring JavaScript objects."}))

#?(:cljs
(defn- binding-sym
[k]
(cond
(symbol? k) k
(keyword? k) (symbol (name k))
(string? k) (symbol k)
:else (throw (ex-info "Invalid destructuring key" {:key k})))))

#?(:cljs
(defn- lookup-key
[k]
(cond
(keyword? k) (name k)
(string? k) k
(symbol? k) (name k)
:else (str k))))

#?(:cljs
(defn- normalized-or
[or]
(into {}
(map (fn [[k v]]
[(lookup-key k) v])
or)))
Comment on lines +23 to +29

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will stop my review at this function. This is definitely not the product of a human thought.


#?(:cljs
(defmacro destructure-js-obj
"Destructure a JavaScript object expression.

Accepts the same map-style destructuring as `let`, plus `:rest`:

(destructure-js-obj
[{:keys [a b]
:as obj
:or {a 0}
:rest rest}]
js-obj-expr
body...)

`:rest` binds a map containing every entry of the object that was not
explicitly destructured."
[bindings expr & body]
(if (symbol? bindings)
`(let [~bindings ~expr]
~@body)
(do
(when-not (map? bindings)
(throw (ex-info "destructure-js-obj expects a binding map or symbol" {})))
(let [or (normalized-or (or (get bindings :or) {}))
keys (get bindings :keys [])
strs (get bindings :strs [])
as (get bindings :as)
rest-sym (get bindings :rest)
bound-keys (into #{}
(concat (map lookup-key keys)
(map lookup-key strs)))
obj (gen-sym 'obj)
key-bindings
(vec
(concat
(for [k keys]
(let [sym (binding-sym k)
ks (lookup-key k)
default (get or ks)]
(if (not (nil? default))
[sym `(let [v# (get ~obj ~ks)]
(if (nil? v#)
~default
v#))]
[sym `(let [v# (get ~obj ~ks)]
v#)])))
(for [k strs]
(let [sym (binding-sym k)
ks (lookup-key k)
default (get or ks)]
(if (not (nil? default))
[sym `(let [v# (get ~obj ~ks)]
(if (nil? v#)
~default
v#))]
[sym `(let [v# (get ~obj ~ks)]
v#)])))))
rest-binding
(when rest-sym
[rest-sym `(into {}
(for [k (js-keys ~obj)
:when (not (contains? ~bound-keys k))]
[k (get ~obj k)])))]
as-binding (when as [as obj])
let-bindings (vec (concat key-bindings as-binding rest-binding))]
`(let [~obj ~expr
~@let-bindings]
~@body))))