Implemented #146, dynamic navigator enhancements

This commit is contained in:
Nathan Marz 2016-10-31 23:57:12 -04:00
parent 728926c4ea
commit dfedd30b29
6 changed files with 72 additions and 30 deletions

View file

@ -1,6 +1,9 @@
## 0.13.1-SNAPSHOT ## 0.13.1-SNAPSHOT
* Remove any? in com.rpl.specter.impl to avoid conflict with Clojure 1.9 * Remove any? in com.rpl.specter.impl to avoid conflict with Clojure 1.9
* Enhanced dynamic navigators to continue expanding if any other dynamic navs are returned
* Added `eachnav` to turn any 1-argument navigator into a navigator that accepts any number of arguments, navigating by each argument in order
* `keypath` and `must` enhanced to take in multiple arguments for concisely specifying multiple steps
* Bug fix: Fix regression from 0.13.0 where [ALL FIRST] on a PersistentArrayMap that created duplicate keys would create an invalid PersistentArrayMap * Bug fix: Fix regression from 0.13.0 where [ALL FIRST] on a PersistentArrayMap that created duplicate keys would create an invalid PersistentArrayMap
* Bug fix: Fix problems with multi-path and if-path in latest versions of ClojureScript * Bug fix: Fix problems with multi-path and if-path in latest versions of ClojureScript
* Bug fix: Inline compiler no longer flattens and changes the type of sequential params * Bug fix: Inline compiler no longer flattens and changes the type of sequential params

View file

@ -34,6 +34,7 @@
(defmacroalias richnav macros/richnav) (defmacroalias richnav macros/richnav)
(defmacroalias nav macros/nav) (defmacroalias nav macros/nav)
(defmacroalias defnav macros/defnav) (defmacroalias defnav macros/defnav)
(defmacroalias defrichnav macros/defrichnav)
(defmacro collector [params [_ [_ structure-sym] & body]] (defmacro collector [params [_ [_ structure-sym] & body]]
`(richnav ~params `(richnav ~params
@ -42,9 +43,6 @@
(~'transform* [this# vals# ~structure-sym next-fn#] (~'transform* [this# vals# ~structure-sym next-fn#]
(next-fn# (conj vals# (do ~@body)) ~structure-sym)))) (next-fn# (conj vals# (do ~@body)) ~structure-sym))))
(defmacro defrichnav [name params & impls]
`(def ~name (richnav ~params ~@impls)))
(defmacro defcollector [name & body] (defmacro defcollector [name & body]
`(def ~name (collector ~@body))) `(def ~name (collector ~@body)))
@ -543,6 +541,24 @@
(def late-path i/late-path) (def late-path i/late-path)
(def dynamic-param? i/dynamic-param?) (def dynamic-param? i/dynamic-param?)
(def late-resolved-fn i/late-resolved-fn)
(defdynamicnav
^{:doc "Turns a navigator that takes one argument into a navigator that takes
many arguments and uses the same navigator with each argument. There
is no performance cost to using this. See implementation of `keypath`"}
eachnav
[navfn]
(let [latenavfn (late-resolved-fn navfn)]
(dynamicnav [& args]
(if (= 1 (count args))
;; optimization if dynamicnav is used in a runtime situation (like
;; via a dynamic var)
;; also makes the resulting nav function semantically identical to original
;; nav by returning a RichNavigator instead of a sequence
(latenavfn (first args))
(map latenavfn args)))))
;; Helpers for making recursive or mutually recursive navs ;; Helpers for making recursive or mutually recursive navs
@ -752,28 +768,8 @@
next-val)) next-val))
structure))))) structure)))))
(defrichnav (def keypath (eachnav n/keypath*))
^{:doc "Navigates to the specified key, navigating to nil if it does not exist."} (def must (eachnav n/must*))
keypath
[key]
(select* [this vals structure next-fn]
(next-fn vals (get structure key)))
(transform* [this vals structure next-fn]
(assoc structure key (next-fn vals (get structure key)))))
(defrichnav
^{:doc "Navigates to the key only if it exists in the map."}
must
[k]
(select* [this vals structure next-fn]
(if (contains? structure k)
(next-fn vals (get structure k))
NONE))
(transform* [this vals structure next-fn]
(if (contains? structure k)
(assoc structure k (next-fn vals (get structure k)))
structure)))
(defrichnav (defrichnav
@ -881,7 +877,7 @@
(extend-type #?(:clj clojure.lang.Keyword :cljs cljs.core/Keyword) (extend-type #?(:clj clojure.lang.Keyword :cljs cljs.core/Keyword)
ImplicitNav ImplicitNav
(implicit-nav [this] (keypath this))) (implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj clojure.lang.AFn :cljs function) (extend-type #?(:clj clojure.lang.AFn :cljs function)
ImplicitNav ImplicitNav

View file

@ -569,7 +569,7 @@
:cljs (satisfies? RichNavigator o)) :cljs (satisfies? RichNavigator o))
o o
(vector? o) (sequential? o)
(comp-paths* o) (comp-paths* o)
:else :else
@ -602,9 +602,16 @@
(defn all-static? [params] (defn all-static? [params]
(every? (complement dynamic-param?) params)) (every? (complement dynamic-param?) params))
(defn late-resolved-fn [afn]
(fn [& args]
(if (all-static? args)
(apply afn args)
(->DynamicFunction afn args)
)))
(defn- magic-precompilation* [o] (defn- magic-precompilation* [o]
(cond (sequential? o) (cond (sequential? o)
(if (list? o) (if (or (list? o) (seq? o))
(map magic-precompilation* o) (map magic-precompilation* o)
(into (empty o) (map magic-precompilation* o))) (into (empty o) (map magic-precompilation* o)))
@ -631,7 +638,7 @@
params (doall (map magic-precompilation* (:params o)))] params (doall (map magic-precompilation* (:params o)))]
(if (or (-> op meta :dynamicnav) (if (or (-> op meta :dynamicnav)
(all-static? (conj params op))) (all-static? (conj params op)))
(apply op params) (magic-precompilation* (apply op params))
(->DynamicFunction op params))) (->DynamicFunction op params)))
:else :else

View file

@ -44,3 +44,6 @@
~@decls ~@decls
~@helpers ~@helpers
(def ~name (nav ~params ~@impls))))) (def ~name (nav ~params ~@impls)))))
(defmacro defrichnav [name params & impls]
`(def ~name (richnav ~params ~@impls)))

View file

@ -5,7 +5,7 @@
[defnav]] [defnav]]
[com.rpl.specter.util-macros :refer [com.rpl.specter.util-macros :refer
[doseqres]])) [doseqres]]))
(:use #?(:clj [com.rpl.specter.macros :only [defnav]]) (:use #?(:clj [com.rpl.specter.macros :only [defnav defrichnav]])
#?(:clj [com.rpl.specter.util-macros :only [doseqres]])) #?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
(:require [com.rpl.specter.impl :as i] (:require [com.rpl.specter.impl :as i]
[clojure.walk :as walk] [clojure.walk :as walk]
@ -409,3 +409,27 @@
(if (pred structure) (if (pred structure)
(on-match-fn structure) (on-match-fn structure)
(walk/walk (partial walk-until pred on-match-fn) identity structure))) (walk/walk (partial walk-until pred on-match-fn) identity structure)))
(defrichnav
^{:doc "Navigates to the specified key, navigating to nil if it does not exist."}
keypath*
[key]
(select* [this vals structure next-fn]
(next-fn vals (get structure key)))
(transform* [this vals structure next-fn]
(assoc structure key (next-fn vals (get structure key)))))
(defrichnav
^{:doc "Navigates to the key only if it exists in the map."}
must*
[k]
(select* [this vals structure next-fn]
(if (contains? structure k)
(next-fn vals (get structure k))
i/NONE))
(transform* [this vals structure next-fn]
(if (contains? structure k)
(assoc structure k (next-fn vals (get structure k)))
structure)))

View file

@ -1305,3 +1305,12 @@
(deftest inline-caching-vector-params-test (deftest inline-caching-vector-params-test
(is (= [10 [11]] (multi-transform (s/terminal-val [10 [11]]) :a)))) (is (= [10 [11]] (multi-transform (s/terminal-val [10 [11]]) :a))))
(defn eachnav-fn-test [akey data]
(select-any (s/keypath "a" akey) data))
(deftest eachnav-test
(let [data {"a" {"b" 1 "c" 2}}]
(is (= 1 (eachnav-fn-test "b" data)))
(is (= 2 (eachnav-fn-test "c" data)))
))