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
* 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 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

View file

@ -34,6 +34,7 @@
(defmacroalias richnav macros/richnav)
(defmacroalias nav macros/nav)
(defmacroalias defnav macros/defnav)
(defmacroalias defrichnav macros/defrichnav)
(defmacro collector [params [_ [_ structure-sym] & body]]
`(richnav ~params
@ -42,9 +43,6 @@
(~'transform* [this# vals# ~structure-sym next-fn#]
(next-fn# (conj vals# (do ~@body)) ~structure-sym))))
(defmacro defrichnav [name params & impls]
`(def ~name (richnav ~params ~@impls)))
(defmacro defcollector [name & body]
`(def ~name (collector ~@body)))
@ -543,6 +541,24 @@
(def late-path i/late-path)
(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
@ -752,28 +768,8 @@
next-val))
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))
NONE))
(transform* [this vals structure next-fn]
(if (contains? structure k)
(assoc structure k (next-fn vals (get structure k)))
structure)))
(def keypath (eachnav n/keypath*))
(def must (eachnav n/must*))
(defrichnav
@ -881,7 +877,7 @@
(extend-type #?(:clj clojure.lang.Keyword :cljs cljs.core/Keyword)
ImplicitNav
(implicit-nav [this] (keypath this)))
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj clojure.lang.AFn :cljs function)
ImplicitNav

View file

@ -569,7 +569,7 @@
:cljs (satisfies? RichNavigator o))
o
(vector? o)
(sequential? o)
(comp-paths* o)
:else
@ -602,9 +602,16 @@
(defn all-static? [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]
(cond (sequential? o)
(if (list? o)
(if (or (list? o) (seq? o))
(map magic-precompilation* o)
(into (empty o) (map magic-precompilation* o)))
@ -631,7 +638,7 @@
params (doall (map magic-precompilation* (:params o)))]
(if (or (-> op meta :dynamicnav)
(all-static? (conj params op)))
(apply op params)
(magic-precompilation* (apply op params))
(->DynamicFunction op params)))
:else

View file

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

View file

@ -5,7 +5,7 @@
[defnav]]
[com.rpl.specter.util-macros :refer
[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]]))
(:require [com.rpl.specter.impl :as i]
[clojure.walk :as walk]
@ -409,3 +409,27 @@
(if (pred structure)
(on-match-fn 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
(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)))
))