From 3dc7ad25ff473dcf83aea29c8b35e79c800294b2 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Fri, 10 Jun 2016 07:57:18 -0400 Subject: [PATCH] added collected? and DISPENSE navigators --- CHANGES.md | 2 ++ src/clj/com/rpl/specter.cljx | 5 ++++ src/clj/com/rpl/specter/impl.cljx | 30 ++++++++++++++++++++ src/clj/com/rpl/specter/macros.clj | 8 ++++++ test/com/rpl/specter/core_test.cljx | 43 +++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c2e044..343afbc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ * 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) +* 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, and LAST now work properly on nil ## 0.11.2 diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index 9d185ac..2368552 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -564,6 +564,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 ffddf99..2bce1a1 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)) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index c87a29e..c5e5c49 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -611,3 +611,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..666c190 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,42 @@ (is (empty? (select s/LAST nil))) (is (empty? (select s/ALL 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) + )) + ))