From dfedd30b294069ab628f36c4ecccdab37a2d2e52 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 31 Oct 2016 23:57:12 -0400 Subject: [PATCH] Implemented #146, dynamic navigator enhancements --- CHANGES.md | 3 ++ src/clj/com/rpl/specter.cljc | 48 +++++++++++++---------------- src/clj/com/rpl/specter/impl.cljc | 13 ++++++-- src/clj/com/rpl/specter/macros.clj | 3 ++ src/clj/com/rpl/specter/navs.cljc | 26 +++++++++++++++- test/com/rpl/specter/core_test.cljc | 9 ++++++ 6 files changed, 72 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 61bbc08..db7e333 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index d49d5f7..810ae97 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -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 diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 1e255e7..364913c 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -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 diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index f75335b..d0fb15d 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -44,3 +44,6 @@ ~@decls ~@helpers (def ~name (nav ~params ~@impls))))) + +(defmacro defrichnav [name params & impls] + `(def ~name (richnav ~params ~@impls))) diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index a5ce306..acb849c 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -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))) diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index aa51079..9f65c7f 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -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))) + ))