diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 023390c..f5d5704 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -757,6 +757,55 @@ (next-fn structure) )) +(deftype TransientEndNavigator []) + +(extend-protocol p/Navigator + TransientEndNavigator + (select* [this structure next-fn] + []) + (transform* [this structure next-fn] + (let [res (next-fn [])] + (reduce conj! structure res)))) + +(deftype TransientLastNavigator []) + +(extend-protocol p/Navigator + TransientLastNavigator + (select* [this structure next-fn] + (next-fn (nth structure (dec (count structure))))) + (transform* [this structure next-fn] + (let [i (dec (count structure))] + (assoc! structure i (next-fn (nth structure i)))))) + +#+clj +(defn transient-all-select + [structure next-fn] + (into [] (r/mapcat #(next-fn (nth structure %)) + (range (count structure))))) + +#+cljs +(defn transient-all-select + [structure next-fn] + (into [] + (r/mapcat #(next-fn (nth structure %))) + (range (count structure)))) + +(defn transient-all-transform! + [structure next-fn] + (reduce (fn [structure i] + (assoc! structure i (next-fn (nth structure i)))) + structure + (range (count structure)))) + +(deftype TransientAllNavigator []) + +(extend-protocol p/Navigator + TransientAllNavigator + (select* [this structure next-fn] + (transient-all-select structure next-fn)) + (transform* [this structure next-fn] + (transient-all-transform! structure next-fn))) + (defn extract-basic-filter-fn [path] (cond (fn? path) path diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx new file mode 100644 index 0000000..db0d1cd --- /dev/null +++ b/src/clj/com/rpl/specter/transient.cljx @@ -0,0 +1,79 @@ +(ns com.rpl.specter.transient + #+cljs + (:require-macros [com.rpl.specter.macros + :refer + [defnav + defpathedfn]]) + (:use #+clj + [com.rpl.specter.macros :only + [defnav + defpathedfn]]) + (:require [com.rpl.specter.impl :as i] + [com.rpl.specter :refer [subselect]])) + +(defnav + ^{:doc "Navigates to the specified key of a transient collection, + navigating to nil if it doesn't exist."} + keypath! + [key] + (select* [this structure next-fn] + (next-fn (get structure key))) + (transform* [this structure next-fn] + (assoc! structure key (next-fn (get structure key))))) + +(def END! + "Navigates to an empty (persistent) vector at the end of a transient vector." + (i/comp-paths* [(i/->TransientEndNavigator)])) + +(def FIRST! + "Navigates to the first element of a transient vector." + (keypath! 0)) + +(def LAST! + "Navigates to the last element of a transient vector." + (i/comp-paths* [(i/->TransientLastNavigator)])) + +(def ALL! + ;; TODO: only works on transient vectors, not sets / maps; need a + ;; different name? + "Navigates to each element of a transient vector." + (i/comp-paths* [(i/->TransientAllNavigator)])) + +(defpathedfn filterer! + "Navigates to a view of the current transient vector that only + contains elements that match the given path." + [& path] + (subselect ALL! (selected? path))) + +(defn- select-keys-from-transient-map + "Selects keys from transient map, because built-in select-keys uses + `find` which is unsupported." + [m m-keys] + (loop [result {} + m-keys m-keys] + (if-not (seq m-keys) + result + (let [k (first m-keys) + ;; support Clojure 1.6 where contains? is broken on transients + item (get m k ::not-found)] + (recur (if-not (identical? item ::not-found) + (assoc result k item) + result) + (rest m-keys)))))) + +(defnav + ^{:doc "Navigates to the specified persistent submap of a transient map."} + submap! + [m-keys] + (select* [this structure next-fn] + (select-keys-from-transient-map structure m-keys)) + (transform* [this structure next-fn] + (let [selected (select-keys-from-transient-map structure m-keys) + res (next-fn selected)] + (as-> structure % + (reduce (fn [m k] + (dissoc! m k)) + % m-keys) + (reduce (fn [m [k v]] + (assoc! m k v)) + % res)))))