diff --git a/CHANGES.md b/CHANGES.md index 3c2e044..a57fa18 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,9 @@ * Added `selected-any?` operation that returns true if any element is navigated to. * Huge performance improvements to `select`, `select-one`, `select-first`, and `select-one!` * Added META navigator (thanks @aengelberg) -* Bug fix: END, BEGINNING, FIRST, and LAST now work properly on nil +* Added DISPENSE navigator to drop all collected values for subsequent navigation +* Added `collected?` macro to create a filter function which operates on the collected values +* Bug fix: END, BEGINNING, FIRST, LAST, and MAP-VALS now work properly on nil ## 0.11.2 * Renamed com.rpl.specter.transient namespace to com.rpl.specter.transients to eliminate ClojureScript compiler warning about reserved keyword diff --git a/README.md b/README.md index a5d1229..1863498 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org - Introductory blog post: [Functional-navigational programming in Clojure(Script) with Specter](http://nathanmarz.com/blog/functional-navigational-programming-in-clojurescript-with-sp.html) - Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk) +- List of navigators with examples: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples. - [API docs](http://nathanmarz.github.io/specter/) - Performance guide: The [Specter 0.11.0 announcement post](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) provides a comprehensive overview of how Specter achieves its performance and what you need to know as a user to enable Specter to perform its optimizations. diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index f7b60da..91b2599 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -575,6 +575,11 @@ (collect-val [this structure] val )) +(def + ^{:doc "Drops all collected values for subsequent navigation."} + DISPENSE i/DISPENSE*) + + (defpathedfn if-path "Like cond-path, but with if semantics." ([cond-p then-path] diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 6dcaeab..b3f71a3 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -609,6 +609,36 @@ ret )))) + +(def collected?* + (->ParamsNeededPath + (->TransformFunctions + RichPathExecutor + (fn [params params-idx vals structure next-fn] + (let [afn (aget ^objects params params-idx)] + (if (afn vals) + (next-fn params (inc params-idx) vals structure) + NONE + ))) + (fn [params params-idx vals structure next-fn] + (let [afn (aget ^objects params params-idx)] + (if (afn vals) + (next-fn params (inc params-idx) vals structure) + structure + )))) + 1 + )) + +(def DISPENSE* + (no-params-compiled-path + (->TransformFunctions + RichPathExecutor + (fn [params params-idx vals structure next-fn] + (next-fn params params-idx [] structure)) + (fn [params params-idx vals structure next-fn] + (next-fn params params-idx [] structure)) + ))) + (defn transform-fns-field [^CompiledPath path] (.-transform-fns path)) @@ -893,6 +923,11 @@ structure)) (extend-protocol MapValsTransformProtocol + nil + (map-vals-transform [structure next-fn] + nil + ) + #+clj clojure.lang.PersistentArrayMap #+cljs cljs.core/PersistentArrayMap (map-vals-transform [structure next-fn] (map-vals-non-transient-transform structure {} next-fn) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index b5aeb6f..65bc229 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -620,3 +620,11 @@ [apath transform-fn structure & args] `(i/compiled-replace-in* (path ~apath) ~transform-fn ~structure ~@args)) +(defmacro collected? + "Creates a filter function navigator that takes in all the collected values + as input. For arguments, can use `(collected? [a b] ...)` syntax to look + at each collected value as individual arguments, or `(collected? v ...)` syntax + to capture all the collected values as a single vector." + [params & body] + `(i/collected?* (fn [~params] ~@body)) + ) diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index a7e4916..b4cb852 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -8,7 +8,7 @@ :refer [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! select-first transform setval replace-in defnavconstructor - select-any selected-any?]]) + select-any selected-any? collected?]]) (:use #+clj [clojure.test :only [deftest is]] #+clj [clojure.test.check.clojure-test :only [defspec]] @@ -17,7 +17,7 @@ :only [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! select-first transform setval replace-in defnavconstructor - select-any selected-any?]] + select-any selected-any? collected?]] ) @@ -1201,3 +1201,58 @@ (is (empty? (select s/LAST nil))) (is (empty? (select s/ALL nil))) ) + +(deftest map-vals-nil + (is (= nil (transform s/MAP-VALS inc nil))) + (is (empty? (select s/MAP-VALS nil))) + ) + +(defspec dispense-test + (for-all+ + [k1 gen/int + k2 gen/int + k3 gen/int + m (gen-map-with-keys gen/int gen/int k1 k2 k3)] + (= (select [(s/collect-one (s/keypath k1)) + (s/collect-one (s/keypath k2)) + s/DISPENSE + (s/collect-one (s/keypath k2)) + (s/keypath k3)] + m) + (select [(s/collect-one (s/keypath k2)) + (s/keypath k3)] + m) + ))) + +(deftest collected?-test + (let [data {:active-id 1 :items [{:id 1 :name "a"} {:id 2 :name "b"}]}] + (is (= {:id 1 :name "a"} + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE + ] + data) + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? v (apply = v)) + s/DISPENSE + ] + data) + ))) + (let [data {:active 3 :items [{:id 1 :val 0} {:id 3 :val 11}]}] + (is (= (transform [:items s/ALL (s/selected? :id #(= % 3)) :val] inc data) + (transform [(s/collect-one :active) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE + :val] + inc + data) + ))))