2015-02-26 15:55:20 +00:00
|
|
|
(ns com.rpl.specter
|
|
|
|
|
(:use [com.rpl.specter impl protocols])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
;;TODO: can make usage of vals much more efficient by determining during composition how many vals
|
|
|
|
|
;;there are going to be. this should make it much easier to allocate space for vals without doing concats
|
|
|
|
|
;;all over the place. The apply to the vals + structure can also be avoided since the number of vals is known
|
|
|
|
|
;;beforehand
|
2015-04-19 17:45:20 +00:00
|
|
|
(defn comp-paths [& paths]
|
|
|
|
|
(comp-paths* (vec paths)))
|
2015-02-26 15:55:20 +00:00
|
|
|
|
|
|
|
|
;; Selector functions
|
|
|
|
|
|
2015-04-22 15:46:13 +00:00
|
|
|
(defn select
|
|
|
|
|
"Navigates to and returns a sequence of all the elements specified by the selector."
|
|
|
|
|
[selector structure]
|
2015-04-19 17:45:20 +00:00
|
|
|
(let [sp (comp-paths* selector)]
|
2015-04-15 17:43:19 +00:00
|
|
|
(select-full* sp
|
|
|
|
|
[]
|
|
|
|
|
structure
|
|
|
|
|
(fn [vals structure]
|
|
|
|
|
(if-not (empty? vals) [(conj vals structure)] [structure])))
|
2015-02-26 15:55:20 +00:00
|
|
|
))
|
|
|
|
|
|
2015-05-10 06:12:06 +00:00
|
|
|
(defn select-fast
|
|
|
|
|
[^com.rpl.specter.impl.StructureValsPathFunctions selfns structure]
|
|
|
|
|
((.selector selfns) [] structure
|
|
|
|
|
(fn [vals structure]
|
|
|
|
|
(if-not (empty? vals) [(conj vals structure)] [structure])))
|
|
|
|
|
)
|
|
|
|
|
|
2015-02-26 15:55:20 +00:00
|
|
|
(defn select-one
|
2015-04-22 15:46:13 +00:00
|
|
|
"Like select, but returns either one element or nil. Throws exception if multiple elements found"
|
2015-02-26 15:55:20 +00:00
|
|
|
[selector structure]
|
|
|
|
|
(let [res (select selector structure)]
|
|
|
|
|
(when (> (count res) 1)
|
|
|
|
|
(throw-illegal "More than one element found for params: " selector structure))
|
|
|
|
|
(first res)
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
(defn select-one!
|
2015-04-22 15:46:13 +00:00
|
|
|
"Returns exactly one element, throws exception if zero or multiple elements found"
|
2015-02-26 15:55:20 +00:00
|
|
|
[selector structure]
|
|
|
|
|
(let [res (select-one selector structure)]
|
|
|
|
|
(when (nil? res) (throw-illegal "No elements found for params: " selector structure))
|
|
|
|
|
res
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
(defn select-first
|
2015-04-22 15:46:13 +00:00
|
|
|
"Returns first element found. Not any more efficient than select, just a convenience"
|
2015-02-26 15:55:20 +00:00
|
|
|
[selector structure]
|
|
|
|
|
(first (select selector structure)))
|
|
|
|
|
|
|
|
|
|
;; Update functions
|
|
|
|
|
|
2015-04-22 15:46:13 +00:00
|
|
|
(defn update
|
|
|
|
|
"Navigates to each value specified by the selector and replaces it by the result of running
|
|
|
|
|
the update-fn on it"
|
|
|
|
|
[selector update-fn structure]
|
2015-04-19 17:45:20 +00:00
|
|
|
(let [selector (comp-paths* selector)]
|
2015-04-15 17:43:19 +00:00
|
|
|
(update-full* selector
|
|
|
|
|
[]
|
|
|
|
|
structure
|
|
|
|
|
(fn [vals structure]
|
|
|
|
|
(if (empty? vals)
|
|
|
|
|
(update-fn structure)
|
|
|
|
|
(apply update-fn (conj vals structure)))
|
|
|
|
|
))))
|
2015-02-26 15:55:20 +00:00
|
|
|
|
2015-05-10 12:09:48 +00:00
|
|
|
(defn update-fast [^com.rpl.specter.impl.StructureValsPathFunctions selfns update-fn structure]
|
|
|
|
|
((.updater selfns) [] structure
|
|
|
|
|
(fn [vals structure]
|
|
|
|
|
(if (empty? vals)
|
|
|
|
|
(update-fn structure)
|
|
|
|
|
(apply update-fn (conj vals structure)))
|
|
|
|
|
))
|
|
|
|
|
)
|
|
|
|
|
|
2015-04-22 15:46:13 +00:00
|
|
|
(defn setval
|
|
|
|
|
"Navigates to each value specified by the selector and replaces it by val"
|
|
|
|
|
[selector val structure]
|
2015-04-01 22:00:22 +00:00
|
|
|
(update selector (fn [_] val) structure))
|
2015-02-26 15:55:20 +00:00
|
|
|
|
|
|
|
|
(defn replace-in [selector update-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
|
2015-04-22 15:46:13 +00:00
|
|
|
"Similar to update, except returns a pair of [updated-structure sequence-of-user-ret].
|
2015-05-10 06:12:06 +00:00
|
|
|
The update-fn in this case is expected to return [ret user-ret]. ret is
|
2015-04-22 15:46:13 +00:00
|
|
|
what's used to update the data structure, while user-ret will be added to the user-ret sequence
|
|
|
|
|
in the final return. replace-in is useful for situations where you need to know the specific values
|
|
|
|
|
of what was updated in the data structure."
|
2015-02-26 15:55:20 +00:00
|
|
|
(let [state (mutable-cell nil)]
|
|
|
|
|
[(update selector
|
|
|
|
|
(fn [e]
|
|
|
|
|
(let [res (update-fn e)]
|
|
|
|
|
(if res
|
|
|
|
|
(let [[ret user-ret] res]
|
|
|
|
|
(->> user-ret
|
|
|
|
|
(merge-fn (get-cell state))
|
|
|
|
|
(set-cell! state))
|
|
|
|
|
ret)
|
|
|
|
|
e
|
|
|
|
|
)))
|
|
|
|
|
structure)
|
|
|
|
|
(get-cell state)]
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
;; Built-in pathing and context operations
|
|
|
|
|
|
|
|
|
|
(def ALL (->AllStructurePath))
|
|
|
|
|
|
2015-04-19 17:45:20 +00:00
|
|
|
(def VAL (->ValCollect))
|
2015-02-26 15:55:20 +00:00
|
|
|
|
|
|
|
|
(def LAST (->LastStructurePath))
|
|
|
|
|
|
|
|
|
|
(def FIRST (->FirstStructurePath))
|
|
|
|
|
|
2015-04-15 03:49:32 +00:00
|
|
|
(defn srange-dynamic [start-fn end-fn] (->SRangePath start-fn end-fn))
|
|
|
|
|
|
|
|
|
|
(defn srange [start end] (srange-dynamic (fn [_] start) (fn [_] end)))
|
|
|
|
|
|
|
|
|
|
(def START (srange 0 0))
|
|
|
|
|
|
|
|
|
|
(def END (srange-dynamic count count))
|
|
|
|
|
|
2015-02-26 15:55:20 +00:00
|
|
|
(defn walker [afn] (->WalkerStructurePath afn))
|
|
|
|
|
|
|
|
|
|
(defn codewalker [afn] (->CodeWalkerStructurePath afn))
|
|
|
|
|
|
|
|
|
|
(defn filterer [afn] (->FilterStructurePath afn))
|
|
|
|
|
|
|
|
|
|
(defn keypath [akey] (->KeyPath akey))
|
|
|
|
|
|
2015-04-18 16:16:51 +00:00
|
|
|
(defn view [afn] (->ViewPath afn))
|
|
|
|
|
|
|
|
|
|
(defmacro viewfn [& args]
|
|
|
|
|
`(view (fn ~@args)))
|
|
|
|
|
|
2015-04-19 17:45:20 +00:00
|
|
|
(defn selected?
|
|
|
|
|
"Filters the current value based on whether a selector finds anything.
|
|
|
|
|
e.g. (selected? :vals ALL even?) keeps the current element only if an
|
|
|
|
|
even number exists for the :vals key"
|
|
|
|
|
[& selectors]
|
|
|
|
|
(let [s (comp-paths selectors)]
|
|
|
|
|
(fn [structure]
|
|
|
|
|
(->> structure
|
|
|
|
|
(select s)
|
|
|
|
|
empty?
|
|
|
|
|
not))))
|
|
|
|
|
|
2015-02-26 15:55:20 +00:00
|
|
|
(extend-type clojure.lang.Keyword
|
|
|
|
|
StructurePath
|
2015-04-15 17:43:19 +00:00
|
|
|
(select* [kw structure next-fn]
|
|
|
|
|
(key-select kw structure next-fn))
|
|
|
|
|
(update* [kw structure next-fn]
|
|
|
|
|
(key-update kw structure next-fn)
|
2015-02-26 15:55:20 +00:00
|
|
|
))
|
|
|
|
|
|
|
|
|
|
(extend-type clojure.lang.AFn
|
|
|
|
|
StructurePath
|
2015-04-15 17:43:19 +00:00
|
|
|
(select* [afn structure next-fn]
|
2015-02-26 15:55:20 +00:00
|
|
|
(if (afn structure)
|
2015-04-15 17:43:19 +00:00
|
|
|
(next-fn structure)))
|
|
|
|
|
(update* [afn structure next-fn]
|
2015-02-26 15:55:20 +00:00
|
|
|
(if (afn structure)
|
2015-04-15 17:43:19 +00:00
|
|
|
(next-fn structure)
|
2015-02-26 15:55:20 +00:00
|
|
|
structure)))
|
|
|
|
|
|
2015-04-19 17:45:20 +00:00
|
|
|
(defn collect [& selector]
|
|
|
|
|
(->SelectCollector select (comp-paths* selector)))
|
2015-02-26 15:55:20 +00:00
|
|
|
|
2015-04-19 17:45:20 +00:00
|
|
|
(defn collect-one [& selector]
|
|
|
|
|
(->SelectCollector select-one (comp-paths* selector)))
|