diff --git a/src/clj/com/rpl/specter.clj b/src/clj/com/rpl/specter.clj index 805361e..ebab51e 100644 --- a/src/clj/com/rpl/specter.clj +++ b/src/clj/com/rpl/specter.clj @@ -11,12 +11,8 @@ ;; Selector functions -(defn compiled-select - "Version of select that takes in a selector pre-compiled with comp-paths" - [^com.rpl.specter.impl.TransformFunctions tfns structure] - (let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] - ((.select-executor ex) (.selector tfns) structure) - )) +(def ^{:doc "Version of select that takes in a selector pre-compiled with comp-paths"} + compiled-select compiled-select*) (defn select "Navigates to and returns a sequence of all the elements specified by the selector." @@ -63,12 +59,9 @@ ;; Update functions -(defn compiled-update - "Version of update that takes in a selector pre-compiled with comp-paths" - [^com.rpl.specter.impl.TransformFunctions tfns update-fn structure] - (let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] - ((.update-executor ex) (.updater tfns) update-fn structure) - )) + +(def ^{:doc "Version of update that takes in a selector pre-compiled with comp-paths"} + compiled-update compiled-update*) (defn update "Navigates to each value specified by the selector and replaces it by the result of running @@ -190,3 +183,22 @@ (update [:a :b (putval 3)] + some-map)" [val] (->PutValCollector val)) + +(defn cond-path + "Takes in alternating cond-fn selector cond-fn selector... + Tests the structure on the cond-fn, and if it matches uses the following selector for + the rest of the selector. Otherwise, it moves on to the next selector. If nothing + matches, then the structure is not selected." + [& conds] + (->> conds + (partition 2) + (map (fn [[c p]] [c (comp-paths* p)])) + doall + ->ConditionalPath + )) + +(defn if-path + "Like cond-path, but with if semantics." + ([cond-fn if-path] (cond-path cond-fn if-path)) + ([cond-fn if-path else-path] + (cond-path cond-fn if-path (fn [_] true) else-path))) diff --git a/src/clj/com/rpl/specter/impl.clj b/src/clj/com/rpl/specter/impl.clj index 095cd1c..03df7a4 100644 --- a/src/clj/com/rpl/specter/impl.clj +++ b/src/clj/com/rpl/specter/impl.clj @@ -469,3 +469,39 @@ (update* [this structure next-fn] (next-fn structure) )) + +(defn compiled-select* + [^com.rpl.specter.impl.TransformFunctions tfns structure] + (let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] + ((.select-executor ex) (.selector tfns) structure) + )) + +(defn compiled-update* + [^com.rpl.specter.impl.TransformFunctions tfns update-fn structure] + (let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] + ((.update-executor ex) (.updater tfns) update-fn structure) + )) + +(deftype ConditionalPath [cond-pairs]) + +(defn- retrieve-selector [cond-pairs structure] + (->> cond-pairs + (drop-while (fn [[c-fn _]] (not (c-fn structure)))) + first + second + )) + +;;TODO: test nothing matches case +(extend-protocol StructurePath + ConditionalPath + (select* [this structure next-fn] + (if-let [selector (retrieve-selector (.cond-pairs this) structure)] + (->> (compiled-select* selector structure) + (mapcat next-fn) + doall))) + (update* [this structure next-fn] + (if-let [selector (retrieve-selector (.cond-pairs this) structure)] + (compiled-update* selector next-fn structure) + structure + ))) + diff --git a/test/clj/com/rpl/specter/core_test.clj b/test/clj/com/rpl/specter/core_test.clj index 5c4aaa5..c4a7740 100644 --- a/test/clj/com/rpl/specter/core_test.clj +++ b/test/clj/com/rpl/specter/core_test.clj @@ -295,3 +295,23 @@ (select [(comp-paths) k1 k2] m) (select [k1 (comp-paths) k2] m) ))) + +(deftest cond-path-test + (is (= [4 2 6 8 10] + (select [ALL (cond-path even? [(view inc) (view inc)] + #(= 3 %) (view dec))] + [1 2 3 4 5 6 7 8]))) + (is (empty? (select (if-path odd? (view inc)) 2))) + (is (= [6 2 10 6 14] + (update [(putval 2) + ALL + (if-path odd? [(view inc) (view inc)] (view dec))] + * + [1 2 3 4 5] + ))) + (is (= 2 + (update [(putval 2) + (if-path odd? (view inc))] + * + 2))) + )