Merge branch 'cljs'

This commit is contained in:
Nathan Marz 2015-06-30 20:04:20 -04:00
commit 2137159311
11 changed files with 396 additions and 258 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ pom.xml.asc
.lein-repl-history .lein-repl-history
.lein-plugins/ .lein-plugins/
.lein-failures .lein-failures
out/

12
DEVELOPER.md Normal file
View file

@ -0,0 +1,12 @@
# Running Clojure tests
```
lein test com.rpl.specter.core-test
```
# Running ClojureScript tests
```
$ rlwrap java -cp `lein classpath`:cljs.jar clojure.main repl.clj
cljs.user=> (require 'com.rpl.specter.cljs-test-runner)
```

View file

@ -1,11 +1,24 @@
(def VERSION (.trim (slurp "VERSION"))) (def VERSION (.trim (slurp "VERSION")))
(defproject com.rpl/specter VERSION (defproject com.rpl/specter VERSION
:dependencies [[org.clojure/clojure "1.6.0"] :dependencies [[org.clojure/clojure "1.7.0"]
;;TODO: how to make this a dep of only the cljs version?
[org.clojure/clojurescript "0.0-3308"]
] ]
:jvm-opts ["-XX:-OmitStackTraceInFastThrow"] ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions :jvm-opts ["-XX:-OmitStackTraceInFastThrow"] ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions
:source-paths ["src/clj"] :plugins [[lein-cljsbuild "1.0.6"]]
:test-paths ["test/clj"] :source-paths ["src"]
:test-paths ["test"]
:profiles {:dev {:dependencies :profiles {:dev {:dependencies
[[org.clojure/test.check "0.5.9"]]} [[org.clojure/test.check "0.7.0"]]}
}
:cljsbuild {
:builds {:dev
{:source-paths ["src"]
:compiler {
:output-to "target/main.js"
:optimizations :whitespace
:pretty-print true
}}
}
}) })

11
repl.clj Normal file
View file

@ -0,0 +1,11 @@
(require 'cljs.repl)
(require 'cljs.build.api)
(require 'cljs.repl.node)
(cljs.build.api/build "src"
{:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.node/repl-env)
:watch "src"
:output-dir "out")

View file

@ -1,5 +1,6 @@
(ns com.rpl.specter (ns com.rpl.specter
(:use [com.rpl.specter impl protocols]) (:use [com.rpl.specter.protocols :only [StructurePath comp-paths*]])
(:require [com.rpl.specter.impl :as i])
) )
;;TODO: can make usage of vals much more efficient by determining during composition how many vals ;;TODO: can make usage of vals much more efficient by determining during composition how many vals
@ -12,12 +13,12 @@
;; Selector functions ;; Selector functions
(def ^{:doc "Version of select that takes in a selector pre-compiled with comp-paths"} (def ^{:doc "Version of select that takes in a selector pre-compiled with comp-paths"}
compiled-select compiled-select*) compiled-select i/compiled-select*)
(defn select (defn select
"Navigates to and returns a sequence of all the elements specified by the selector." "Navigates to and returns a sequence of all the elements specified by the selector."
[selector structure] [selector structure]
(compiled-select (comp-unoptimal selector) (compiled-select (i/comp-unoptimal selector)
structure)) structure))
(defn compiled-select-one (defn compiled-select-one
@ -25,27 +26,27 @@
[selector structure] [selector structure]
(let [res (compiled-select selector structure)] (let [res (compiled-select selector structure)]
(when (> (count res) 1) (when (> (count res) 1)
(throw-illegal "More than one element found for params: " selector structure)) (i/throw-illegal "More than one element found for params: " selector structure))
(first res) (first res)
)) ))
(defn select-one (defn select-one
"Like select, but returns either one element or nil. Throws exception if multiple elements found" "Like select, but returns either one element or nil. Throws exception if multiple elements found"
[selector structure] [selector structure]
(compiled-select-one (comp-unoptimal selector) structure)) (compiled-select-one (i/comp-unoptimal selector) structure))
(defn compiled-select-one! (defn compiled-select-one!
"Version of select-one! that takes in a selector pre-compiled with comp-paths" "Version of select-one! that takes in a selector pre-compiled with comp-paths"
[selector structure] [selector structure]
(let [res (compiled-select selector structure)] (let [res (compiled-select selector structure)]
(when (not= 1 (count res)) (throw-illegal "Expected exactly one element for params: " selector structure)) (when (not= 1 (count res)) (i/throw-illegal "Expected exactly one element for params: " selector structure))
(first res) (first res)
)) ))
(defn select-one! (defn select-one!
"Returns exactly one element, throws exception if zero or multiple elements found" "Returns exactly one element, throws exception if zero or multiple elements found"
[selector structure] [selector structure]
(compiled-select-one! (comp-unoptimal selector) structure)) (compiled-select-one! (i/comp-unoptimal selector) structure))
(defn compiled-select-first (defn compiled-select-first
"Version of select-first that takes in a selector pre-compiled with comp-paths" "Version of select-first that takes in a selector pre-compiled with comp-paths"
@ -55,19 +56,19 @@
(defn select-first (defn select-first
"Returns first element found. Not any more efficient than select, just a convenience" "Returns first element found. Not any more efficient than select, just a convenience"
[selector structure] [selector structure]
(compiled-select-first (comp-unoptimal selector) structure)) (compiled-select-first (i/comp-unoptimal selector) structure))
;; Transformfunctions ;; Transformfunctions
(def ^{:doc "Version of transform that takes in a selector pre-compiled with comp-paths"} (def ^{:doc "Version of transform that takes in a selector pre-compiled with comp-paths"}
compiled-transform compiled-transform*) compiled-transform i/compiled-transform*)
(defn transform (defn transform
"Navigates to each value specified by the selector and replaces it by the result of running "Navigates to each value specified by the selector and replaces it by the result of running
the transform-fn on it" the transform-fn on it"
[selector transform-fn structure] [selector transform-fn structure]
(compiled-transform (comp-unoptimal selector) transform-fn structure)) (compiled-transform (i/comp-unoptimal selector) transform-fn structure))
(defn compiled-setval (defn compiled-setval
"Version of setval that takes in a selector pre-compiled with comp-paths" "Version of setval that takes in a selector pre-compiled with comp-paths"
@ -77,25 +78,25 @@
(defn setval (defn setval
"Navigates to each value specified by the selector and replaces it by val" "Navigates to each value specified by the selector and replaces it by val"
[selector val structure] [selector val structure]
(compiled-setval (comp-unoptimal selector) val structure)) (compiled-setval (i/comp-unoptimal selector) val structure))
(defn compiled-replace-in (defn compiled-replace-in
"Version of replace-in that takes in a selector pre-compiled with comp-paths" "Version of replace-in that takes in a selector pre-compiled with comp-paths"
[selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}] [selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
(let [state (mutable-cell nil)] (let [state (i/mutable-cell nil)]
[(compiled-transform selector [(compiled-transform selector
(fn [e] (fn [e]
(let [res (transform-fn e)] (let [res (transform-fn e)]
(if res (if res
(let [[ret user-ret] res] (let [[ret user-ret] res]
(->> user-ret (->> user-ret
(merge-fn (get-cell state)) (merge-fn (i/get-cell state))
(set-cell! state)) (i/set-cell! state))
ret) ret)
e e
))) )))
structure) structure)
(get-cell state)] (i/get-cell state)]
)) ))
(defn replace-in (defn replace-in
@ -105,38 +106,35 @@
in the final return. replace-in is useful for situations where you need to know the specific values in the final return. replace-in is useful for situations where you need to know the specific values
of what was transformd in the data structure." of what was transformd in the data structure."
[selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}] [selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
(compiled-replace-in (comp-unoptimal selector) transform-fn structure :merge-fn merge-fn)) (compiled-replace-in (i/comp-unoptimal selector) transform-fn structure :merge-fn merge-fn))
;; Built-in pathing and context operations ;; Built-in pathing and context operations
(def ALL (->AllStructurePath)) (def ALL (i/->AllStructurePath))
(def VAL (->ValCollect)) (def VAL (i/->ValCollect))
(def LAST (->PosStructurePath last set-last)) (def LAST (i/->PosStructurePath last i/set-last))
(def FIRST (->PosStructurePath first set-first)) (def FIRST (i/->PosStructurePath first i/set-first))
(defn srange-dynamic [start-fn end-fn] (->SRangePath start-fn end-fn)) (defn srange-dynamic [start-fn end-fn] (i/->SRangePath start-fn end-fn))
(defn srange [start end] (srange-dynamic (fn [_] start) (fn [_] end))) (defn srange [start end] (srange-dynamic (fn [_] start) (fn [_] end)))
(def START (srange 0 0)) (def BEGINNING (srange 0 0))
(def END (srange-dynamic count count)) (def END (srange-dynamic count count))
(defn walker [afn] (->WalkerStructurePath afn)) (defn walker [afn] (i/->WalkerStructurePath afn))
(defn codewalker [afn] (->CodeWalkerStructurePath afn)) (defn codewalker [afn] (i/->CodeWalkerStructurePath afn))
(defn filterer [& path] (->FilterStructurePath (comp-paths* path))) (defn filterer [& path] (i/->FilterStructurePath (comp-paths* path)))
(defn keypath [akey] (->KeyPath akey)) (defn keypath [akey] (i/->KeyPath akey))
(defn view [afn] (->ViewPath afn)) (defn view [afn] (i/->ViewPath afn))
(defmacro viewfn [& args]
`(view (fn ~@args)))
(defn selected? (defn selected?
"Filters the current value based on whether a selector finds anything. "Filters the current value based on whether a selector finds anything.
@ -150,7 +148,7 @@
empty? empty?
not)))) not))))
(extend-type clojure.lang.Keyword (extend-type #?(:clj clojure.lang.Keyword :cljs cljs.core/Keyword)
StructurePath StructurePath
(select* [kw structure next-fn] (select* [kw structure next-fn]
(next-fn (get structure kw))) (next-fn (get structure kw)))
@ -158,25 +156,25 @@
(assoc structure kw (next-fn (get structure kw))) (assoc structure kw (next-fn (get structure kw)))
)) ))
(extend-type clojure.lang.AFn (extend-type #?(:clj clojure.lang.AFn :cljs js/Function)
StructurePath StructurePath
(select* [afn structure next-fn] (select* [afn structure next-fn]
(filter-select afn structure next-fn)) (i/filter-select afn structure next-fn))
(transform* [afn structure next-fn] (transform* [afn structure next-fn]
(filter-transform afn structure next-fn))) (i/filter-transform afn structure next-fn)))
(extend-protocol StructurePath (extend-type #?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet)
clojure.lang.PersistentHashSet StructurePath
(select* [aset structure next-fn] (select* [aset structure next-fn]
(filter-select aset structure next-fn)) (i/filter-select aset structure next-fn))
(transform* [aset structure next-fn] (transform* [aset structure next-fn]
(filter-transform aset structure next-fn))) (i/filter-transform aset structure next-fn)))
(defn collect [& selector] (defn collect [& selector]
(->SelectCollector select (comp-paths* selector))) (i/->SelectCollector select (comp-paths* selector)))
(defn collect-one [& selector] (defn collect-one [& selector]
(->SelectCollector select-one (comp-paths* selector))) (i/->SelectCollector select-one (comp-paths* selector)))
(defn putval (defn putval
"Adds an external value to the collected vals. Useful when additional arguments "Adds an external value to the collected vals. Useful when additional arguments
@ -186,7 +184,7 @@
e.g., incrementing val at path [:a :b] by 3: e.g., incrementing val at path [:a :b] by 3:
(transform [:a :b (putval 3)] + some-map)" (transform [:a :b (putval 3)] + some-map)"
[val] [val]
(->PutValCollector val)) (i/->PutValCollector val))
(defn cond-path (defn cond-path
"Takes in alternating cond-path selector cond-path selector... "Takes in alternating cond-path selector cond-path selector...
@ -199,7 +197,7 @@
(partition 2) (partition 2)
(map (fn [[c p]] [(comp-paths* c) (comp-paths* p)])) (map (fn [[c p]] [(comp-paths* c) (comp-paths* p)]))
doall doall
->ConditionalPath i/->ConditionalPath
)) ))
(defn if-path (defn if-path
@ -212,4 +210,4 @@
"A path that branches on multiple paths. For updates, "A path that branches on multiple paths. For updates,
applies updates to the paths in order." applies updates to the paths in order."
[& paths] [& paths]
(->MultiPath (->> paths (map comp-paths*) doall))) (i/->MultiPath (->> paths (map comp-paths*) doall)))

View file

@ -1,14 +1,38 @@
(ns com.rpl.specter.impl (ns com.rpl.specter.impl
(:use [com.rpl.specter protocols]) (:use [com.rpl.specter.protocols :only
(:require [clojure.walk :as walk] [comp-paths*
[clojure.core.reducers :as r]) select* transform* collect-val select-full* transform-full*]])
(:require [com.rpl.specter.protocols :as p]
[clojure.walk :as walk]
[clojure.core.reducers :as r]
[clojure.string :as s])
) )
#?(
:clj
(defmacro field [obj quoted-field]
`(. ~obj ~(second quoted-field)))
:cljs
(defn field [obj field]
(aget obj (s/replace (str field) "-" "_")))
)
#?(
:clj
(do
(defmacro throw* [etype & args] (defmacro throw* [etype & args]
`(throw (new ~etype (pr-str ~@args)))) `(throw (new ~etype (pr-str ~@args))))
(defmacro throw-illegal [& args] (defmacro throw-illegal [& args]
`(throw* IllegalArgumentException ~@args)) `(throw* IllegalArgumentException ~@args)))
:cljs
(defn throw-illegal [& args]
(throw (js/Error. (apply str args)))
)
)
(defn benchmark [iters afn] (defn benchmark [iters afn]
(time (time
@ -47,6 +71,11 @@
(defprotocol CoerceTransformFunctions (defprotocol CoerceTransformFunctions
(coerce-path [this])) (coerce-path [this]))
(defn- seq-contains? [aseq val]
(->> aseq
(filter (partial = val))
empty?
not))
(defn no-prot-error-str [obj] (defn no-prot-error-str [obj]
(str "Protocol implementation cannot be found for object. (str "Protocol implementation cannot be found for object.
@ -55,15 +84,69 @@
optimized performance. Instead, you should extend the protocols via an optimized performance. Instead, you should extend the protocols via an
explicit extend-protocol call. \n" obj)) explicit extend-protocol call. \n" obj))
#?(
:clj
(defn find-protocol-impl! [prot obj] (defn find-protocol-impl! [prot obj]
(let [ret (find-protocol-impl prot obj)] (let [ret (find-protocol-impl prot obj)]
(if (= ret obj) (if (= ret obj)
(throw-illegal (no-prot-error-str obj)) (throw-illegal (no-prot-error-str obj))
ret ret
))) )))
)
#?(
:clj
(defmacro obj-extends? [quoted-prot-sym obj]
`(->> ~obj (find-protocol-impl ~(second quoted-prot-sym)) nil? not))
:cljs
(defn obj-extends? [prot-sym obj]
(if (nil? obj)
(= prot-sym `p/StructurePath)
;; this requires that prot-sym be fully qualified
(let [props (->> obj type .-prototype (.getOwnPropertyNames js/Object) seq)
ns (namespace prot-sym)
n (name prot-sym)
lookup (str (s/replace ns "." "$") "$" n "$")]
(seq-contains? props lookup)
)))
)
#?(:clj
(do
(defn structure-path-impl [this]
(if (fn? this)
;;TODO: this isn't kosher, it uses knowledge of internals of protocols
(-> p/StructurePath :impls (get clojure.lang.AFn))
(find-protocol-impl! p/StructurePath this)))
(defn collector-impl [this]
(find-protocol-impl! p/Collector this))
(defn structure-vals-path-impl [this]
(find-protocol-impl! p/StructureValsPath this))
))
#?(:cljs
(do
(defn structure-path-impl [_]
{:select* (fn [this structure next-fn] (select* this structure next-fn))
:transform* (fn [this structure next-fn] (transform* this structure next-fn))
})
(defn collector-impl [_]
{:collect-val (fn [this structure] (collect-val this structure))
})
(defn structure-vals-path-impl [_]
{:select-full* (fn [this structure next-fn] (select-full* this structure next-fn))
:transform-full* (fn [this structure next-fn] (transform-full* this structure next-fn))
})
))
(defn coerce-structure-vals-path [this] (defn coerce-structure-vals-path [this]
(let [pimpl (find-protocol-impl! StructureValsPath this) (let [pimpl (structure-vals-path-impl this)
selector (:select-full* pimpl) selector (:select-full* pimpl)
transformer (:transform-full* pimpl)] transformer (:transform-full* pimpl)]
(->TransformFunctions (->TransformFunctions
@ -76,7 +159,7 @@
(defn coerce-collector [this] (defn coerce-collector [this]
(let [cfn (->> this (let [cfn (->> this
(find-protocol-impl! Collector) collector-impl
:collect-val :collect-val
) )
afn (fn [vals structure next-fn] afn (fn [vals structure next-fn]
@ -85,12 +168,6 @@
(->TransformFunctions StructureValsPathExecutor afn afn))) (->TransformFunctions StructureValsPathExecutor afn afn)))
(defn structure-path-impl [this]
(if (fn? this)
;;TODO: this isn't kosher, it uses knowledge of internals of protocols
(-> StructurePath :impls (get clojure.lang.AFn))
(find-protocol-impl! StructurePath this)))
(defn coerce-structure-path [this] (defn coerce-structure-path [this]
(let [pimpl (structure-path-impl this) (let [pimpl (structure-path-impl this)
selector (:select* pimpl) selector (:select* pimpl)
@ -115,11 +192,8 @@
(transformer this structure (fn [structure] (next-fn vals structure)))) (transformer this structure (fn [structure] (next-fn vals structure))))
))) )))
(defn obj-extends? [prot obj]
(->> obj (find-protocol-impl prot) nil? not))
(defn structure-path? [obj] (defn structure-path? [obj]
(or (fn? obj) (obj-extends? StructurePath obj))) (or (fn? obj) (obj-extends? `p/StructurePath obj)))
(extend-protocol CoerceTransformFunctions (extend-protocol CoerceTransformFunctions
nil ; needs its own path because it doesn't count as an Object nil ; needs its own path because it doesn't count as an Object
@ -130,30 +204,43 @@
(coerce-path [this] (coerce-path [this]
this) this)
java.util.List
#?(:clj java.util.List :cljs cljs.core/PersistentVector)
(coerce-path [this] (coerce-path [this]
(comp-paths* this)) (comp-paths* this))
Object #?@(:cljs [
cljs.core/IndexedSeq
(coerce-path [this]
(coerce-path (vec this)))
cljs.core/EmptyList
(coerce-path [this]
(coerce-path (vec this)))
cljs.core/List
(coerce-path [this]
(coerce-path (vec this)))
])
#?(:clj Object :cljs js/Object)
(coerce-path [this] (coerce-path [this]
(cond (structure-path? this) (coerce-structure-path this) (cond (structure-path? this) (coerce-structure-path this)
(obj-extends? Collector this) (coerce-collector this) (obj-extends? `p/Collector this) (coerce-collector this)
(obj-extends? StructureValsPath this) (coerce-structure-vals-path this) (obj-extends? `p/StructureValsPath this) (coerce-structure-vals-path this)
:else (throw-illegal (no-prot-error-str this)) :else (throw-illegal (no-prot-error-str this))
))) )))
(defn extype [^TransformFunctions f] (defn extype [^TransformFunctions f]
(let [^ExecutorFunctions exs (.executors f)] (let [^ExecutorFunctions exs (field f 'executors)]
(.type exs) (field exs 'type)
)) ))
(defn- combine-same-types [[^TransformFunctions f & _ :as all]] (defn- combine-same-types [[^TransformFunctions f & _ :as all]]
(if (empty? all) (if (empty? all)
(coerce-path nil) (coerce-path nil)
(let [^ExecutorFunctions exs (.executors f) (let [^ExecutorFunctions exs (field f 'executors)
t (.type exs) t (field exs 'type)
combiner combiner
(if (= t :svalspath) (if (= t :svalspath)
@ -171,16 +258,16 @@
(reduce (fn [^TransformFunctions curr ^TransformFunctions next] (reduce (fn [^TransformFunctions curr ^TransformFunctions next]
(->TransformFunctions (->TransformFunctions
exs exs
(combiner (.selector curr) (.selector next)) (combiner (field curr 'selector) (field next 'selector))
(combiner (.transformer curr) (.transformer next)) (combiner (field curr 'transformer) (field next 'transformer))
)) ))
all)))) all))))
(defn coerce-structure-vals [^TransformFunctions tfns] (defn coerce-structure-vals [^TransformFunctions tfns]
(if (= (extype tfns) :svalspath) (if (= (extype tfns) :svalspath)
tfns tfns
(let [selector (.selector tfns) (let [selector (field tfns 'selector)
transformer (.transformer tfns)] transformer (field tfns 'transformer)]
(->TransformFunctions (->TransformFunctions
StructureValsPathExecutor StructureValsPathExecutor
(fn [vals structure next-fn] (fn [vals structure next-fn]
@ -189,14 +276,14 @@
(transformer structure (fn [structure] (next-fn vals structure)))) (transformer structure (fn [structure] (next-fn vals structure))))
)))) ))))
(extend-protocol StructureValsPathComposer (extend-protocol p/StructureValsPathComposer
nil nil
(comp-paths* [sp] (comp-paths* [sp]
(coerce-path sp)) (coerce-path sp))
Object #?(:clj Object :cljs js/Object)
(comp-paths* [sp] (comp-paths* [sp]
(coerce-path sp)) (coerce-path sp))
java.util.List #?(:clj java.util.List :cljs cljs.core/PersistentVector)
(comp-paths* [structure-paths] (comp-paths* [structure-paths]
(let [combined (->> structure-paths (let [combined (->> structure-paths
(map coerce-path) (map coerce-path)
@ -212,8 +299,8 @@
(defn coerce-structure-vals-direct [this] (defn coerce-structure-vals-direct [this]
(cond (structure-path? this) (coerce-structure-path-direct this) (cond (structure-path? this) (coerce-structure-path-direct this)
(obj-extends? Collector this) (coerce-collector this) (obj-extends? `p/Collector this) (coerce-collector this)
(obj-extends? StructureValsPath this) (coerce-structure-vals-path this) (obj-extends? `p/StructureValsPath this) (coerce-structure-vals-path this)
(instance? TransformFunctions this) (coerce-structure-vals this) (instance? TransformFunctions this) (coerce-structure-vals this)
:else (throw-illegal (no-prot-error-str this)) :else (throw-illegal (no-prot-error-str this))
)) ))
@ -222,31 +309,32 @@
;;won't execute as fast. Useful for when select/transform are used without pre-compiled paths ;;won't execute as fast. Useful for when select/transform are used without pre-compiled paths
;;(where cost of compiling dominates execution time) ;;(where cost of compiling dominates execution time)
(defn comp-unoptimal [sp] (defn comp-unoptimal [sp]
(if (instance? java.util.List sp) (if (instance? #?(:clj java.util.List :cljs cljs.core/PersistentVector) sp)
(->> sp (->> sp
(map coerce-structure-vals-direct) (map coerce-structure-vals-direct)
combine-same-types) combine-same-types)
(coerce-path sp))) (coerce-path sp)))
;; cell implementation idea taken from prismatic schema library ;; cell implementation idea taken from prismatic schema library
(definterface PMutableCell (defprotocol PMutableCell
(get_cell ^Object []) #?(:clj (get_cell [cell]))
(set_cell [^Object x])) (set_cell [cell x]))
(deftype MutableCell [^:volatile-mutable ^Object q] (deftype MutableCell [^:volatile-mutable q]
PMutableCell PMutableCell
(get_cell [this] q) #?(:clj (get_cell [cell] q))
(set_cell [this x] (set! q x))) (set_cell [this x] (set! q x)))
(defn mutable-cell ^PMutableCell (defn mutable-cell
([] (mutable-cell nil)) ([] (mutable-cell nil))
([init] (MutableCell. init))) ([init] (MutableCell. init)))
(defn set-cell! [^PMutableCell cell val] (defn set-cell! [cell val]
(.set_cell cell val)) (set_cell cell val))
(defn get-cell [^PMutableCell cell] (defn get-cell [cell]
(.get_cell cell)) #?(:clj (get_cell cell) :cljs (field cell 'q))
)
(defn update-cell! [cell afn] (defn update-cell! [cell afn]
(let [ret (afn (get-cell cell))] (let [ret (afn (get-cell cell))]
@ -267,12 +355,12 @@
(append (butlast l) v)) (append (butlast l) v))
(extend-protocol SetExtremes (extend-protocol SetExtremes
clojure.lang.PersistentVector #?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
(set-first [v val] (set-first [v val]
(assoc v 0 val)) (assoc v 0 val))
(set-last [v val] (set-last [v val]
(assoc v (-> v count dec) val)) (assoc v (-> v count dec) val))
Object #?(:clj Object :cljs js/Object)
(set-first [l val] (set-first [l val]
(set-first-list l val)) (set-first-list l val))
(set-last [l val] (set-last [l val]
@ -304,14 +392,14 @@
(defn compiled-select* (defn compiled-select*
[^com.rpl.specter.impl.TransformFunctions tfns structure] [^com.rpl.specter.impl.TransformFunctions tfns structure]
(let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] (let [^com.rpl.specter.impl.ExecutorFunctions ex (field tfns 'executors)]
((.select-executor ex) (.selector tfns) structure) ((field ex 'select-executor) (field tfns 'selector) structure)
)) ))
(defn compiled-transform* (defn compiled-transform*
[^com.rpl.specter.impl.TransformFunctions tfns transform-fn structure] [^com.rpl.specter.impl.TransformFunctions tfns transform-fn structure]
(let [^com.rpl.specter.impl.ExecutorFunctions ex (.executors tfns)] (let [^com.rpl.specter.impl.ExecutorFunctions ex (field tfns 'executors)]
((.transform-executor ex) (.transformer tfns) transform-fn structure) ((field ex 'transform-executor) (field tfns 'transformer) transform-fn structure)
)) ))
(defn selected?* (defn selected?*
@ -355,7 +443,7 @@
(deftype AllStructurePath []) (deftype AllStructurePath [])
(extend-protocol StructurePath (extend-protocol p/StructurePath
AllStructurePath AllStructurePath
(select* [this structure next-fn] (select* [this structure next-fn]
(into [] (r/mapcat next-fn structure))) (into [] (r/mapcat next-fn structure)))
@ -369,50 +457,50 @@
(deftype ValCollect []) (deftype ValCollect [])
(extend-protocol Collector (extend-protocol p/Collector
ValCollect ValCollect
(collect-val [this structure] (collect-val [this structure]
structure)) structure))
(deftype PosStructurePath [getter setter]) (deftype PosStructurePath [getter setter])
(extend-protocol StructurePath (extend-protocol p/StructurePath
PosStructurePath PosStructurePath
(select* [this structure next-fn] (select* [this structure next-fn]
(if-not (empty? structure) (if-not (empty? structure)
(next-fn ((.getter this) structure)))) (next-fn ((field this 'getter) structure))))
(transform* [this structure next-fn] (transform* [this structure next-fn]
(if (empty? structure) (if (empty? structure)
structure structure
((.setter this) structure (next-fn ((.getter this) structure)))))) ((field this 'setter) structure (next-fn ((field this 'getter) structure))))))
(deftype WalkerStructurePath [afn]) (deftype WalkerStructurePath [afn])
(extend-protocol StructurePath (extend-protocol p/StructurePath
WalkerStructurePath WalkerStructurePath
(select* [^WalkerStructurePath this structure next-fn] (select* [^WalkerStructurePath this structure next-fn]
(walk-select (.afn this) next-fn structure)) (walk-select (field this 'afn) next-fn structure))
(transform* [^WalkerStructurePath this structure next-fn] (transform* [^WalkerStructurePath this structure next-fn]
(walk-until (.afn this) next-fn structure))) (walk-until (field this 'afn) next-fn structure)))
(deftype CodeWalkerStructurePath [afn]) (deftype CodeWalkerStructurePath [afn])
(extend-protocol StructurePath (extend-protocol p/StructurePath
CodeWalkerStructurePath CodeWalkerStructurePath
(select* [^CodeWalkerStructurePath this structure next-fn] (select* [^CodeWalkerStructurePath this structure next-fn]
(walk-select (.afn this) next-fn structure)) (walk-select (field this 'afn) next-fn structure))
(transform* [^CodeWalkerStructurePath this structure next-fn] (transform* [^CodeWalkerStructurePath this structure next-fn]
(codewalk-until (.afn this) next-fn structure))) (codewalk-until (field this 'afn) next-fn structure)))
(deftype FilterStructurePath [path]) (deftype FilterStructurePath [path])
(extend-protocol StructurePath (extend-protocol p/StructurePath
FilterStructurePath FilterStructurePath
(select* [^FilterStructurePath this structure next-fn] (select* [^FilterStructurePath this structure next-fn]
(->> structure (filter #(selected?* (.path this) %)) doall next-fn)) (->> structure (filter #(selected?* (field this 'path) %)) doall next-fn))
(transform* [^FilterStructurePath this structure next-fn] (transform* [^FilterStructurePath this structure next-fn]
(let [[filtered ancestry] (filter+ancestry (.path this) structure) (let [[filtered ancestry] (filter+ancestry (field this 'path) structure)
;; the vec is necessary so that we can get by index later ;; the vec is necessary so that we can get by index later
;; (can't get by index for cons'd lists) ;; (can't get by index for cons'd lists)
next (vec (next-fn filtered))] next (vec (next-fn filtered))]
@ -423,33 +511,33 @@
(deftype KeyPath [akey]) (deftype KeyPath [akey])
(extend-protocol StructurePath (extend-protocol p/StructurePath
KeyPath KeyPath
(select* [^KeyPath this structure next-fn] (select* [^KeyPath this structure next-fn]
(key-select (.akey this) structure next-fn)) (key-select (field this 'akey) structure next-fn))
(transform* [^KeyPath this structure next-fn] (transform* [^KeyPath this structure next-fn]
(key-transform (.akey this) structure next-fn) (key-transform (field this 'akey) structure next-fn)
)) ))
(deftype SelectCollector [sel-fn selector]) (deftype SelectCollector [sel-fn selector])
(extend-protocol Collector (extend-protocol p/Collector
SelectCollector SelectCollector
(collect-val [^SelectCollector this structure] (collect-val [^SelectCollector this structure]
((.sel-fn this) (.selector this) structure))) ((field this 'sel-fn) (field this 'selector) structure)))
(deftype SRangePath [start-fn end-fn]) (deftype SRangePath [start-fn end-fn])
(extend-protocol StructurePath (extend-protocol p/StructurePath
SRangePath SRangePath
(select* [^SRangePath this structure next-fn] (select* [^SRangePath this structure next-fn]
(let [start ((.start-fn this) structure) (let [start ((field this 'start-fn) structure)
end ((.end-fn this) structure)] end ((field this 'end-fn) structure)]
(next-fn (-> structure vec (subvec start end))) (next-fn (-> structure vec (subvec start end)))
)) ))
(transform* [^SRangePath this structure next-fn] (transform* [^SRangePath this structure next-fn]
(let [start ((.start-fn this) structure) (let [start ((field this 'start-fn) structure)
end ((.end-fn this) structure) end ((field this 'end-fn) structure)
structurev (vec structure) structurev (vec structure)
newpart (next-fn (-> structurev (subvec start end))) newpart (next-fn (-> structurev (subvec start end)))
res (concat (subvec structurev 0 start) res (concat (subvec structurev 0 start)
@ -462,24 +550,24 @@
(deftype ViewPath [view-fn]) (deftype ViewPath [view-fn])
(extend-protocol StructurePath (extend-protocol p/StructurePath
ViewPath ViewPath
(select* [^ViewPath this structure next-fn] (select* [^ViewPath this structure next-fn]
(->> structure ((.view-fn this)) next-fn)) (->> structure ((field this 'view-fn)) next-fn))
(transform* [^ViewPath this structure next-fn] (transform* [^ViewPath this structure next-fn]
(->> structure ((.view-fn this)) next-fn) (->> structure ((field this 'view-fn)) next-fn)
)) ))
(deftype PutValCollector [val]) (deftype PutValCollector [val])
(extend-protocol Collector (extend-protocol p/Collector
PutValCollector PutValCollector
(collect-val [^PutValCollector this structure] (collect-val [^PutValCollector this structure]
(.val this) (field this 'val)
)) ))
(extend-protocol StructurePath (extend-protocol p/StructurePath
nil nil
(select* [this structure next-fn] (select* [this structure next-fn]
(next-fn structure)) (next-fn structure))
@ -501,25 +589,25 @@
)) ))
;;TODO: test nothing matches case ;;TODO: test nothing matches case
(extend-protocol StructurePath (extend-protocol p/StructurePath
ConditionalPath ConditionalPath
(select* [this structure next-fn] (select* [this structure next-fn]
(if-let [selector (retrieve-selector (.cond-pairs this) structure)] (if-let [selector (retrieve-selector (field this 'cond-pairs) structure)]
(->> (compiled-select* selector structure) (->> (compiled-select* selector structure)
(mapcat next-fn) (mapcat next-fn)
doall))) doall)))
(transform* [this structure next-fn] (transform* [this structure next-fn]
(if-let [selector (retrieve-selector (.cond-pairs this) structure)] (if-let [selector (retrieve-selector (field this 'cond-pairs) structure)]
(compiled-transform* selector next-fn structure) (compiled-transform* selector next-fn structure)
structure structure
))) )))
(deftype MultiPath [paths]) (deftype MultiPath [paths])
(extend-protocol StructurePath (extend-protocol p/StructurePath
MultiPath MultiPath
(select* [this structure next-fn] (select* [this structure next-fn]
(->> (.paths this) (->> (field this 'paths)
(mapcat #(compiled-select* % structure)) (mapcat #(compiled-select* % structure))
(mapcat next-fn) (mapcat next-fn)
doall doall
@ -529,7 +617,7 @@
(fn [structure selector] (fn [structure selector]
(compiled-transform* selector next-fn structure)) (compiled-transform* selector next-fn structure))
structure structure
(.paths this)) (field this 'paths))
)) ))
(defn filter-select [afn structure next-fn] (defn filter-select [afn structure next-fn]

View file

@ -0,0 +1,13 @@
(ns com.rpl.specter.cljs-test-helpers)
;; it seems like gen/bind and gen/return are a monad (hence the names)
(defmacro for-all+ [bindings & body]
(let [parts (partition 2 bindings)
vars (vec (map first parts))
genned (reduce
(fn [curr [v code]]
`(cljs.test.check.generators/bind ~code (fn [~v] ~curr)))
`(cljs.test.check.generators/return ~vars)
(reverse parts))]
`(cljs.test.check.properties/for-all [~vars ~genned]
~@body )))

View file

@ -0,0 +1,5 @@
(ns com.rpl.specter.cljs-test-runner
(:require [cljs.test :as test :refer-macros [run-tests]]
[com.rpl.specter.core-test]))
(run-tests 'com.rpl.specter.core-test)

View file

@ -1,19 +1,32 @@
(ns com.rpl.specter.core-test (ns com.rpl.specter.core-test
(:use [clojure.test] #?(:cljs (:require-macros
[clojure.test.check.clojure-test] [cljs.test :refer [is deftest]]
[com.rpl specter] [cljs.test.check.cljs-test :refer [defspec]]
[com.rpl.specter protocols] [com.rpl.specter.cljs-test-helpers :refer [for-all+]]))
[com.rpl.specter test-helpers]) (:use
(:require [clojure.test.check #?(:clj [clojure.test :only [deftest is]])
[generators :as gen] #?(:clj [clojure.test.check.clojure-test :only [defspec]])
[properties :as prop]] #?(:clj [com.rpl.specter.test-helpers :only [for-all+]])
[clojure.test.check :as qc])) [com.rpl.specter.protocols :only [comp-paths*]])
(:require #?@(:clj [[clojure.test.check.generators :as gen]
[clojure.test.check.properties :as prop]]
:cljs [[cljs.test.check :as tc]
[cljs.test.check.generators :as gen]
[cljs.test.check.properties :as prop :include-macros true]]
)
[com.rpl.specter :as s]))
;;TODO: ;;TODO:
;; test walk, codewalk ;; test walk, codewalk
;; test keypath ;; test keypath
;; test comp-structure-paths ;; test comp-structure-paths
(defn limit-size [n {gen :gen}]
(gen/->Generator
(fn [rnd _size]
(gen rnd (if (< _size n) _size n)))))
(defn gen-map-with-keys [key-gen val-gen & keys] (defn gen-map-with-keys [key-gen val-gen & keys]
(gen/bind (gen/map key-gen val-gen) (gen/bind (gen/map key-gen val-gen)
(fn [m] (fn [m]
@ -25,10 +38,10 @@
(defspec select-all-keyword-filter (defspec select-all-keyword-filter
(for-all+ (for-all+
[kw gen/keyword [kw gen/keyword
v (gen/vector (max-size 5 v (gen/vector (limit-size 5
(gen-map-with-keys gen/keyword gen/int kw))) (gen-map-with-keys gen/keyword gen/int kw)))
pred (gen/elements [odd? even?])] pred (gen/elements [odd? even?])]
(= (select [ALL kw pred] v) (= (s/select [s/ALL kw pred] v)
(->> v (map kw) (filter pred)) (->> v (map kw) (filter pred))
))) )))
@ -36,46 +49,46 @@
(for-all+ (for-all+
[v (gen/vector gen/int) [v (gen/vector gen/int)
pred (gen/elements [odd? even?]) pred (gen/elements [odd? even?])
pos (gen/elements [[FIRST first] [LAST last]])] pos (gen/elements [[s/FIRST first] [s/LAST last]])]
(= (select-one [(filterer pred) (first pos)] v) (= (s/select-one [(s/filterer pred) (first pos)] v)
(->> v (filter pred) ((last pos))) (->> v (filter pred) ((last pos)))
))) )))
(defspec select-all-on-map (defspec select-all-on-map
(for-all+ (for-all+
[m (max-size 5 (gen/map gen/keyword gen/int))] [m (limit-size 5 (gen/map gen/keyword gen/int))]
(= (select [ALL LAST] m) (= (s/select [s/ALL s/LAST] m)
(for [[k v] m] v)) (for [[k v] m] v))
)) ))
(deftest select-one-test (deftest select-one-test
(is (thrown? Exception (select-one [ALL even?] [1 2 3 4]))) (is (thrown? #?(:clj Exception :cljs js/Error) (s/select-one [s/ALL even?] [1 2 3 4])))
(is (= 1 (select-one [ALL odd?] [2 4 1 6]))) (is (= 1 (s/select-one [s/ALL odd?] [2 4 1 6])))
) )
(deftest select-first-test (deftest select-first-test
(is (= 7 (select-first [(filterer odd?) ALL #(> % 4)] [3 4 2 3 7 5 9 8]))) (is (= 7 (s/select-first [(s/filterer odd?) s/ALL #(> % 4)] [3 4 2 3 7 5 9 8])))
(is (nil? (select-first [ALL even?] [1 3 5 9]))) (is (nil? (s/select-first [s/ALL even?] [1 3 5 9])))
) )
(defspec transform-all-on-map (defspec transform-all-on-map
(for-all+ (for-all+
[m (max-size 5 (gen/map gen/keyword gen/int))] [m (limit-size 5 (gen/map gen/keyword gen/int))]
(= (transform [ALL LAST] inc m) (= (s/transform [s/ALL s/LAST] inc m)
(into {} (for [[k v] m] [k (inc v)])) (into {} (for [[k v] m] [k (inc v)]))
))) )))
(defspec transform-all (defspec transform-all
(for-all+ (for-all+
[v (gen/vector gen/int)] [v (gen/vector gen/int)]
(let [v2 (transform [ALL] inc v)] (let [v2 (s/transform [s/ALL] inc v)]
(and (vector? v2) (= v2 (map inc v))) (and (vector? v2) (= v2 (map inc v)))
))) )))
(defspec transform-all-list (defspec transform-all-list
(for-all+ (for-all+
[v (gen/list gen/int)] [v (gen/list gen/int)]
(let [v2 (transform [ALL] inc v)] (let [v2 (s/transform [s/ALL] inc v)]
(and (seq? v2) (= v2 (map inc v))) (and (seq? v2) (= v2 (map inc v)))
))) )))
@ -84,7 +97,7 @@
[v (gen/vector gen/int) [v (gen/vector gen/int)
pred (gen/elements [odd? even?]) pred (gen/elements [odd? even?])
action (gen/elements [inc dec])] action (gen/elements [inc dec])]
(let [v2 (transform [ALL pred] action v)] (let [v2 (s/transform [s/ALL pred] action v)]
(= v2 (map (fn [v] (if (pred v) (action v) v)) v)) (= v2 (map (fn [v] (if (pred v) (action v) v)) v))
))) )))
@ -92,7 +105,7 @@
(for-all+ (for-all+
[v (gen/not-empty (gen/vector gen/int)) [v (gen/not-empty (gen/vector gen/int))
pred (gen/elements [inc dec])] pred (gen/elements [inc dec])]
(let [v2 (transform [LAST] pred v)] (let [v2 (s/transform [s/LAST] pred v)]
(= v2 (concat (butlast v) [(pred (last v))])) (= v2 (concat (butlast v) [(pred (last v))]))
))) )))
@ -100,7 +113,7 @@
(for-all+ (for-all+
[v (gen/not-empty (gen/vector gen/int)) [v (gen/not-empty (gen/vector gen/int))
pred (gen/elements [inc dec])] pred (gen/elements [inc dec])]
(let [v2 (transform [FIRST] pred v)] (let [v2 (s/transform [s/FIRST] pred v)]
(= v2 (concat [(pred (first v))] (rest v) )) (= v2 (concat [(pred (first v))] (rest v) ))
))) )))
@ -109,8 +122,8 @@
[v (gen/vector gen/int) [v (gen/vector gen/int)
pred (gen/elements [even? odd?]) pred (gen/elements [even? odd?])
updater (gen/elements [inc dec])] updater (gen/elements [inc dec])]
(let [v2 (transform [(filterer pred) ALL] updater v) (let [v2 (s/transform [(s/filterer pred) s/ALL] updater v)
v3 (transform [ALL pred] updater v)] v3 (s/transform [s/ALL pred] updater v)]
(= v2 v3)) (= v2 v3))
)) ))
@ -118,9 +131,9 @@
(for-all+ (for-all+
[kw1 gen/keyword [kw1 gen/keyword
kw2 gen/keyword kw2 gen/keyword
m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2)) m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2))
pred (gen/elements [odd? even?])] pred (gen/elements [odd? even?])]
(= (transform [(collect-one kw2) kw1 pred] + m) (= (s/transform [(s/collect-one kw2) kw1 pred] + m)
(if (pred (kw1 m)) (if (pred (kw1 m))
(assoc m kw1 (+ (kw1 m) (kw2 m))) (assoc m kw1 (+ (kw1 m) (kw2 m)))
m m
@ -137,7 +150,7 @@
(for-all+ (for-all+
[pred (gen/elements [odd? even?]) [pred (gen/elements [odd? even?])
v (gen/such-that #(some pred %) (gen/vector gen/int))] v (gen/such-that #(some pred %) (gen/vector gen/int))]
(let [v2 (transform [(filterer pred) LAST] inc v) (let [v2 (s/transform [(s/filterer pred) s/LAST] inc v)
differing-elems (differing-elements v v2)] differing-elems (differing-elements v v2)]
(and (= (count v2) (count v)) (and (= (count v2) (count v))
(= (count differing-elems) 1) (= (count differing-elems) 1)
@ -147,15 +160,15 @@
;; max sizes prevent too much data from being generated and keeps test from taking forever ;; max sizes prevent too much data from being generated and keeps test from taking forever
(defspec transform-keyword (defspec transform-keyword
(for-all+ (for-all+
[k1 (max-size 3 gen/keyword) [k1 (limit-size 3 gen/keyword)
k2 (max-size 3 gen/keyword) k2 (limit-size 3 gen/keyword)
m1 (max-size 5 m1 (limit-size 5
(gen-map-with-keys (gen-map-with-keys
gen/keyword gen/keyword
(gen-map-with-keys gen/keyword gen/int k2) (gen-map-with-keys gen/keyword gen/int k2)
k1)) k1))
pred (gen/elements [inc dec])] pred (gen/elements [inc dec])]
(let [m2 (transform [k1 k2] pred m1)] (let [m2 (s/transform [k1 k2] pred m1)]
(= (assoc-in m1 [k1 k2] nil) (assoc-in m2 [k1 k2] nil)) (= (assoc-in m1 [k1 k2] nil) (assoc-in m2 [k1 k2] nil))
(= (pred (get-in m1 [k1 k2])) (get-in m2 [k1 k2])) (= (pred (get-in m1 [k1 k2])) (get-in m2 [k1 k2]))
))) )))
@ -169,7 +182,7 @@
(map (fn [v] [v v])) (map (fn [v] [v v]))
(apply concat)) (apply concat))
user-ret (if (empty? user-ret) nil user-ret)] user-ret (if (empty? user-ret) nil user-ret)]
(= (replace-in [ALL even?] (fn [v] [(inc v) [v v]]) v) (= (s/replace-in [s/ALL even?] (fn [v] [(inc v) [v v]]) v)
[res user-ret] [res user-ret]
)))) ))))
@ -179,7 +192,7 @@
(let [res (->> v (map (fn [v] (if (even? v) (inc v) v)))) (let [res (->> v (map (fn [v] (if (even? v) (inc v) v))))
last-even (->> v (filter even?) last) last-even (->> v (filter even?) last)
user-ret (if last-even {:a last-even})] user-ret (if last-even {:a last-even})]
(= (replace-in [ALL even?] (fn [v] [(inc v) v]) v :merge-fn (fn [curr new] (= (s/replace-in [s/ALL even?] (fn [v] [(inc v) v]) v :merge-fn (fn [curr new]
(assoc curr :a new))) (assoc curr :a new)))
[res user-ret] [res user-ret]
)))) ))))
@ -188,8 +201,8 @@
(for-all+ (for-all+
[v (gen/vector gen/int) [v (gen/vector gen/int)
v2 (gen/vector gen/int)] v2 (gen/vector gen/int)]
(let [b (setval START v2 v) (let [b (s/setval s/BEGINNING v2 v)
e (setval END v2 v)] e (s/setval s/END v2 v)]
(and (= b (concat v2 v)) (and (= b (concat v2 v))
(= e (concat v v2))) (= e (concat v v2)))
))) )))
@ -204,30 +217,29 @@
predcount (fn [pred v] (->> v (filter pred) count)) predcount (fn [pred v] (->> v (filter pred) count))
even-count (partial predcount even?) even-count (partial predcount even?)
odd-count (partial predcount odd?) odd-count (partial predcount odd?)
b (transform (srange b e) (fn [r] (filter odd? r)) v)] b (s/transform (s/srange b e) (fn [r] (filter odd? r)) v)]
(and (= (odd-count v) (odd-count b)) (and (= (odd-count v) (odd-count b))
(= (+ (even-count b) (even-count sv)) (= (+ (even-count b) (even-count sv))
(even-count v))) (even-count v)))
))) )))
(deftest structure-path-directly-test (deftest structure-path-directly-test
(is (= 3 (select-one :b {:a 1 :b 3}))) (is (= 3 (s/select-one :b {:a 1 :b 3})))
(is (= 5 (select-one (comp-paths :a :b) {:a {:b 5}}))) (is (= 5 (s/select-one (s/comp-paths :a :b) {:a {:b 5}})))
) )
(defspec view-test (defspec view-test
(for-all+ (for-all+
[i gen/int [i gen/int
afn (gen/elements [inc dec])] afn (gen/elements [inc dec])]
(= (first (select (view afn) i)) (= (first (s/select (s/view afn) i))
(first (select (viewfn [i] (afn i)) i))
(afn i) (afn i)
(transform (view afn) identity i) (s/transform (s/view afn) identity i)
))) )))
(deftest selected?-test (deftest selected?-test
(is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []] (is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []]
(setval [ALL (selected? ALL even?) END] (s/setval [s/ALL (s/selected? s/ALL even?) s/END]
[:a] [:a]
[[1 3 5] [2] [7 11 4 2] [10 1] []] [[1 3 5] [2] [7 11 4 2] [10 1] []]
)))) ))))
@ -236,19 +248,19 @@
(for-all+ (for-all+
[i gen/int [i gen/int
afn (gen/elements [inc dec])] afn (gen/elements [inc dec])]
(and (= [i] (select nil i)) (and (= [i] (s/select nil i))
(= (afn i) (transform nil afn i))))) (= (afn i) (s/transform nil afn i)))))
(deftest nil-comp-test (deftest nil-comp-test
(is (= [5] (select (comp-paths* nil) 5)))) (is (= [5] (s/select (comp-paths* nil) 5))))
(defspec putval-test (defspec putval-test
(for-all+ (for-all+
[kw gen/keyword [kw gen/keyword
m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw)) m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))
c gen/int] c gen/int]
(= (transform [(putval c) kw] + m) (= (s/transform [(s/putval c) kw] + m)
(transform [kw (putval c)] + m) (s/transform [kw (s/putval c)] + m)
(assoc m kw (+ c (get m kw))) (assoc m kw (+ c (get m kw)))
))) )))
@ -256,79 +268,79 @@
(for-all+ (for-all+
[v (gen/vector gen/int)] [v (gen/vector gen/int)]
(= [v] (= [v]
(select [] v) (s/select [] v)
(select nil v) (s/select nil v)
(select (comp-paths) v) (s/select (s/comp-paths) v)
(select (comp-paths nil) v) (s/select (s/comp-paths nil) v)
(select [nil nil nil] v) (s/select [nil nil nil] v)
))) )))
(defspec empty-selector-transform-test (defspec empty-selector-transform-test
(for-all+ (for-all+
[kw gen/keyword [kw gen/keyword
m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw))] m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))]
(and (= m (and (= m
(transform nil identity m) (s/transform nil identity m)
(transform [] identity m) (s/transform [] identity m)
(transform (comp-paths []) identity m) (s/transform (s/comp-paths []) identity m)
(transform (comp-paths nil nil) identity m) (s/transform (s/comp-paths nil nil) identity m)
) )
(= (transform kw inc m) (= (s/transform kw inc m)
(transform [nil kw] inc m) (s/transform [nil kw] inc m)
(transform (comp-paths kw nil) inc m) (s/transform (s/comp-paths kw nil) inc m)
(transform (comp-paths nil kw nil) inc m) (s/transform (s/comp-paths nil kw nil) inc m)
)))) ))))
(deftest compose-empty-comp-path-test (deftest compose-empty-comp-path-test
(let [m {:a 1}] (let [m {:a 1}]
(is (= [1] (is (= [1]
(select [:a (comp-paths)] m) (s/select [:a (s/comp-paths)] m)
(select [(comp-paths) :a] m) (s/select [(s/comp-paths) :a] m)
)))) ))))
(defspec mixed-selector-test (defspec mixed-selector-test
(for-all+ (for-all+
[k1 (max-size 3 gen/keyword) [k1 (limit-size 3 gen/keyword)
k2 (max-size 3 gen/keyword) k2 (limit-size 3 gen/keyword)
m (max-size 5 m (limit-size 5
(gen-map-with-keys (gen-map-with-keys
gen/keyword gen/keyword
(gen-map-with-keys gen/keyword gen/int k2) (gen-map-with-keys gen/keyword gen/int k2)
k1))] k1))]
(= [(-> m k1 k2)] (= [(-> m k1 k2)]
(select [k1 (comp-paths k2)] m) (s/select [k1 (s/comp-paths k2)] m)
(select [(comp-paths k1) k2] m) (s/select [(s/comp-paths k1) k2] m)
(select [(comp-paths k1 k2) nil] m) (s/select [(s/comp-paths k1 k2) nil] m)
(select [(comp-paths) k1 k2] m) (s/select [(s/comp-paths) k1 k2] m)
(select [k1 (comp-paths) k2] m) (s/select [k1 (s/comp-paths) k2] m)
))) )))
(deftest cond-path-test (deftest cond-path-test
(is (= [4 2 6 8 10] (is (= [4 2 6 8 10]
(select [ALL (cond-path even? [(view inc) (view inc)] (s/select [s/ALL (s/cond-path even? [(s/view inc) (s/view inc)]
#(= 3 %) (view dec))] #(= 3 %) (s/view dec))]
[1 2 3 4 5 6 7 8]))) [1 2 3 4 5 6 7 8])))
(is (empty? (select (if-path odd? (view inc)) 2))) (is (empty? (s/select (s/if-path odd? (s/view inc)) 2)))
(is (= [6 2 10 6 14] (is (= [6 2 10 6 14]
(transform [(putval 2) (s/transform [(s/putval 2)
ALL s/ALL
(if-path odd? [(view inc) (view inc)] (view dec))] (s/if-path odd? [(s/view inc) (s/view inc)] (s/view dec))]
* *
[1 2 3 4 5] [1 2 3 4 5]
))) )))
(is (= 2 (is (= 2
(transform [(putval 2) (s/transform [(s/putval 2)
(if-path odd? (view inc))] (s/if-path odd? (s/view inc))]
* *
2))) 2)))
) )
(defspec cond-path-selector-test (defspec cond-path-selector-test
(for-all+ (for-all+
[k1 (max-size 3 gen/keyword) [k1 (limit-size 3 gen/keyword)
k2 (max-size 3 gen/keyword) k2 (limit-size 3 gen/keyword)
k3 (max-size 3 gen/keyword) k3 (limit-size 3 gen/keyword)
m (max-size 5 m (limit-size 5
(gen-map-with-keys (gen-map-with-keys
gen/keyword gen/keyword
gen/int gen/int
@ -340,34 +352,34 @@
(let [v1 (get m k1) (let [v1 (get m k1)
k (if (pred v1) k2 k3)] k (if (pred v1) k2 k3)]
(and (and
(= (transform (if-path [k1 pred] k2 k3) inc m) (= (s/transform (s/if-path [k1 pred] k2 k3) inc m)
(transform k inc m)) (s/transform k inc m))
(= (select (if-path [k1 pred] k2 k3) m) (= (s/select (s/if-path [k1 pred] k2 k3) m)
(select k m)) (s/select k m))
)))) ))))
(defspec multi-path-test (defspec multi-path-test
(for-all+ (for-all+
[k1 (max-size 3 gen/keyword) [k1 (limit-size 3 gen/keyword)
k2 (max-size 3 gen/keyword) k2 (limit-size 3 gen/keyword)
m (max-size 5 m (limit-size 5
(gen-map-with-keys (gen-map-with-keys
gen/keyword gen/keyword
gen/int gen/int
k1 k1
k2)) k2))
] ]
(= (transform (multi-path k1 k2) inc m) (= (s/transform (s/multi-path k1 k2) inc m)
(->> m (->> m
(transform k1 inc) (s/transform k1 inc)
(transform k2 inc))) (s/transform k2 inc)))
)) ))
(deftest empty-pos-transform (deftest empty-pos-transform
(is (empty? (select FIRST []))) (is (empty? (s/select s/FIRST [])))
(is (empty? (select LAST []))) (is (empty? (s/select s/LAST [])))
(is (= [] (transform FIRST inc []))) (is (= [] (s/transform s/FIRST inc [])))
(is (= [] (transform LAST inc []))) (is (= [] (s/transform s/LAST inc [])))
) )
(defspec set-filter-test (defspec set-filter-test
@ -376,10 +388,10 @@
k2 (gen/such-that #(not= k1 %) gen/keyword) k2 (gen/such-that #(not= k1 %) gen/keyword)
k3 (gen/such-that (complement #{k1 k2}) gen/keyword) k3 (gen/such-that (complement #{k1 k2}) gen/keyword)
v (gen/vector (gen/elements [k1 k2 k3]))] v (gen/vector (gen/elements [k1 k2 k3]))]
(= (filter #{k1 k2} v) (select [ALL #{k1 k2}] v)) (= (filter #{k1 k2} v) (s/select [s/ALL #{k1 k2}] v))
)) ))
(deftest nil-select-one-test (deftest nil-select-one-test
(is (= nil (select-one! ALL [nil]))) (is (= nil (s/select-one! s/ALL [nil])))
(is (thrown? Exception (select-one! ALL []))) (is (thrown? #?(:clj Exception :cljs js/Error) (s/select-one! s/ALL [])))
) )

View file

@ -1,11 +1,8 @@
(ns com.rpl.specter.test-helpers (ns com.rpl.specter.test-helpers
(:use [clojure.test]
[clojure.test.check.clojure-test])
(:require [clojure.test.check (:require [clojure.test.check
[generators :as gen] [generators :as gen]
[properties :as prop]] [properties :as prop]]))
[clojure.test.check :as qc]
[clojure.pprint :as pp]))
;; it seems like gen/bind and gen/return are a monad (hence the names) ;; it seems like gen/bind and gen/return are a monad (hence the names)
(defmacro for-all+ [bindings & body] (defmacro for-all+ [bindings & body]
@ -18,15 +15,3 @@
(reverse parts))] (reverse parts))]
`(prop/for-all [~vars ~genned] `(prop/for-all [~vars ~genned]
~@body ))) ~@body )))
(defn max-size [n {gen :gen}]
(gen/make-gen
(fn [rnd _size]
(gen rnd (if (< _size n) _size n)))))
(defn gen-tuple+ [& inputs]
(->> inputs
(map #(if (gen/generator? %) % (gen/return %)))
(apply gen/tuple)
))