diff --git a/CHANGES.md b/CHANGES.md index 0f54829..8114222 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +## 0.5.7 +* Fix bug in select-one! which wouldn't allow nil result + +## 0.5.6 +* Add multi-path implementation +* change FIRST/LAST to select nothing on an empty sequence +* Allow sets to be used directly as selectors (acts as filter) + ## 0.5.5 * Change filterer to accept a selector (that acts like selected? to determine whether or not to select value) diff --git a/VERSION b/VERSION index d1d899f..d3532a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.5 +0.5.7 diff --git a/src/com/rpl/specter.cljc b/src/com/rpl/specter.cljc index 1719e04..0f97fb2 100644 --- a/src/com/rpl/specter.cljc +++ b/src/com/rpl/specter.cljc @@ -38,9 +38,9 @@ (defn compiled-select-one! "Version of select-one! that takes in a selector pre-compiled with comp-paths" [selector structure] - (let [res (compiled-select-one selector structure)] - (when (nil? res) (i/throw-illegal "No elements found for params: " selector structure)) - res + (let [res (compiled-select selector structure)] + (when (not= 1 (count res)) (i/throw-illegal "Expected exactly one element for params: " selector structure)) + (first res) )) (defn select-one! @@ -114,9 +114,9 @@ (def VAL (i/->ValCollect)) -(def LAST (i/->LastStructurePath)) +(def LAST (i/->PosStructurePath last set-last)) -(def FIRST (i/->FirstStructurePath)) +(def FIRST (i/->PosStructurePath first set-first)) (defn srange-dynamic [start-fn end-fn] (i/->SRangePath start-fn end-fn)) @@ -159,12 +159,16 @@ (extend-type #?(:clj clojure.lang.AFn :cljs js/Function) StructurePath (select* [afn structure next-fn] - (if (afn structure) - (next-fn structure))) + (i/filter-select afn structure next-fn)) (transform* [afn structure next-fn] - (if (afn structure) - (next-fn structure) - structure))) + (i/filter-transform afn structure next-fn))) + +(extend-type #?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet) + StructurePath + (select* [aset structure next-fn] + (i/filter-select aset structure next-fn)) + (transform* [aset structure next-fn] + (i/filter-transform aset structure next-fn))) (defn collect [& selector] (i/->SelectCollector select (comp-paths* selector))) diff --git a/src/com/rpl/specter/impl.cljc b/src/com/rpl/specter/impl.cljc index f777f77..efeecab 100644 --- a/src/com/rpl/specter/impl.cljc +++ b/src/com/rpl/specter/impl.cljc @@ -460,23 +460,17 @@ (collect-val [this structure] structure)) -(deftype LastStructurePath []) +(deftype PosStructurePath [getter setter]) (extend-protocol p/StructurePath - LastStructurePath + PosStructurePath (select* [this structure next-fn] - (next-fn (last structure))) + (if-not (empty? structure) + (next-fn ((field this 'getter) structure)))) (transform* [this structure next-fn] - (set-last structure (next-fn (last structure))))) - -(deftype FirstStructurePath []) - -(extend-protocol p/StructurePath - FirstStructurePath - (select* [this structure next-fn] - (next-fn (first structure))) - (transform* [this structure next-fn] - (set-first structure (next-fn (first structure))))) + (if (empty? structure) + structure + ((field this 'setter) structure (next-fn ((field this 'getter) structure)))))) (deftype WalkerStructurePath [afn]) @@ -624,3 +618,12 @@ (field this 'paths)) )) +(defn filter-select [afn structure next-fn] + (if (afn structure) + (next-fn structure))) + +(defn filter-transform [afn structure next-fn] + (if (afn structure) + (next-fn structure) + structure)) + diff --git a/test/com/rpl/specter/core_test.clj b/test/com/rpl/specter/core_test.clj index 00d857e..ca07fbf 100644 --- a/test/com/rpl/specter/core_test.clj +++ b/test/com/rpl/specter/core_test.clj @@ -361,3 +361,24 @@ (transform k1 inc) (transform k2 inc))) )) + +(deftest empty-pos-transform + (is (empty? (select FIRST []))) + (is (empty? (select LAST []))) + (is (= [] (transform FIRST inc []))) + (is (= [] (transform LAST inc []))) + ) + +(defspec set-filter-test + (for-all+ + [k1 gen/keyword + k2 (gen/such-that #(not= k1 %) gen/keyword) + k3 (gen/such-that (complement #{k1 k2}) gen/keyword) + v (gen/vector (gen/elements [k1 k2 k3]))] + (= (filter #{k1 k2} v) (select [ALL #{k1 k2}] v)) + )) + +(deftest nil-select-one-test + (is (= nil (select-one! ALL [nil]))) + (is (thrown? Exception (select-one! ALL []))) + )