Merge branch 'master' into gh-pages
This commit is contained in:
commit
28a89039da
17 changed files with 418 additions and 132 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,4 +9,5 @@ pom.xml.asc
|
|||
.lein-repl-history
|
||||
.lein-plugins/
|
||||
.lein-failures
|
||||
.cljs_node_repl
|
||||
out/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
language: clojure
|
||||
lein: 2.7.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
script:
|
||||
- lein test
|
||||
- lein doo node test-build once
|
||||
|
|
|
|||
28
CHANGES.md
28
CHANGES.md
|
|
@ -1,4 +1,30 @@
|
|||
## 0.13.3-SNAPSHOT
|
||||
## 1.0.3-SNAPSHOT
|
||||
|
||||
## 1.0.2
|
||||
|
||||
* Added `pred=`, `pred<`, `pred>`, `pred<=`, `pred>=` for filtering using common comparisons
|
||||
* Add `map-key` navigator
|
||||
* Add `set-elem` navigator
|
||||
* Add `ALL-WITH-META` navigator
|
||||
* `walker` and `codewalker` can now be used with `NONE` to remove elements
|
||||
* Improve `walker` performance by 70% by replacing clojure.walk implementation with custom recursive path
|
||||
* Extend `ALL` to work on records (navigate to key/value pairs)
|
||||
* Add ability to declare a function for end index of `srange-dynamic` that takes in the result of the start index fn. Use `end-fn` macro to declare this function (takes in 2 args of [collection, start-index]). Functions defined with normal mechanisms (e.g. `fn`) will still only take in the collection as an argument.
|
||||
* Workaround for ClojureScript bug that emits warnings for vars named the same as a private var in cljs.core (in this case `NONE`, added as private var to cljs.core with 1.9.562)
|
||||
* For ALL transforms on maps, interpret transformed key/value pair of size < 2 as removal
|
||||
* Bug fix: Fix incorrect inline compilation when a dynamic function invocation is nested in a data structure within a parameter to a navigator builder
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* `subselect`/`filterer` can remove entries in source by transforming to a smaller sequence
|
||||
* Add `satisfies-protpath?`
|
||||
* Inline cache vars are marked private so as not to interfere with tooling
|
||||
* Improve performance of `ALL` transform on lists by 20%
|
||||
* Bug fix: Using `pred` no longer inserts unnecessary `coerce-nav` call at callsite
|
||||
* Bug fix: Dynamic navs in argument position to another nav now properly expanded and compiled
|
||||
* Bug fix: Dynamic parameters nested inside data structures as arguments are now compiled correctly by inline compiler
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Transform to `com.rpl.specter/NONE` to remove elements from data structures. Works with `keypath` (for both sequences and maps), `must`, `nthpath`, `ALL`, `MAP-VALS`, `FIRST`, and `LAST`
|
||||
* Add `nthpath` navigator
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ lein cleantest
|
|||
# Running ClojureScript tests
|
||||
|
||||
```
|
||||
rm -rf .cljs_node_repl
|
||||
lein javac
|
||||
rlwrap java -cp `lein classpath` clojure.main repl.clj
|
||||
(require 'com.rpl.specter.cljs-test-runner)
|
||||
lein doo node test-build once
|
||||
```
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -80,7 +80,7 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
|
|||
|
||||
# Learn Specter
|
||||
|
||||
- Introductory blog post: [Functional-navigational programming in Clojure(Script) with Specter](http://nathanmarz.com/blog/functional-navigational-programming-in-clojurescript-with-sp.html)
|
||||
- Introductory blog post: [Clojure's missing piece](http://nathanmarz.com/blog/clojures-missing-piece.html)
|
||||
- Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk)
|
||||
- Note that this presentation was given before Specter's inline compilation/caching system was developed. You no longer need to do anything special to get near-optimal performance.
|
||||
- List of navigators with examples: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
|
||||
|
|
@ -90,8 +90,7 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
|
|||
|
||||
Specter's API is contained in these files:
|
||||
|
||||
- [macros.clj](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/macros.clj): This contains the core `select/transform/etc.` operations as well as macros for defining new navigators.
|
||||
- [specter.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and functional versions of `select/transform/etc.`
|
||||
- [specter.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and the definition of the core operations.
|
||||
- [transients.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
|
||||
- [zipper.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
|
||||
|
||||
|
|
@ -193,7 +192,7 @@ user> (transform [(srange 4 11) (filterer even?)]
|
|||
Append [:c :d] to every subsequence that has at least two even numbers:
|
||||
```clojure
|
||||
user> (setval [ALL
|
||||
(selected? (filterer even?) (view count) #(>= % 2))
|
||||
(selected? (filterer even?) (view count) (pred>= 2))
|
||||
END]
|
||||
[:c :d]
|
||||
[[1 2 3 4 5 6] [7 0 -1] [8 8] []])
|
||||
|
|
@ -293,6 +292,15 @@ Here's how to reverse the positions of all even numbers in a tree (with order ba
|
|||
;; => [1 10 [3 [[8]] 5] [6 [7 4] 9 [[2]]]]
|
||||
```
|
||||
|
||||
# ClojureScript
|
||||
|
||||
Specter supports ClojureScript! However, some of the differences between Clojure and ClojureScript affect how you use Specter in ClojureScript, in particular with the namespace declarations. In Clojure, you might `(use 'com.rpl.specter)` or say `(:require [com.rpl.specter :refer :all])` in your namespace declaration. But in ClojureScript, these options [aren't allowed](https://groups.google.com/d/msg/clojurescript/SzYK08Oduxo/MxLUjg50gQwJ). Instead, consider using one of these options:
|
||||
|
||||
```clojure
|
||||
(:require [com.rpl.specter :as s])
|
||||
(:require [com.rpl.specter :as s :refer-macros [select transform]]) ;; add in the Specter macros that you need
|
||||
```
|
||||
|
||||
# Future work
|
||||
- Integrate Specter with other kinds of data structures, such as graphs. Desired navigations include: reduction in topological order, navigate to outgoing/incoming nodes, to a subgraph (with metadata indicating how to attach external edges on transformation), to node attributes, to node values, to specific nodes.
|
||||
- Make it possible to parallelize selects/transforms
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.0.0
|
||||
1.0.3-SNAPSHOT
|
||||
|
|
|
|||
12
project.clj
12
project.clj
|
|
@ -9,7 +9,8 @@
|
|||
:test-paths ["test", "target/test-classes"]
|
||||
:auto-clean false
|
||||
:dependencies [[riddley "0.1.12"]]
|
||||
:plugins [[lein-codox "0.9.5"]]
|
||||
:plugins [[lein-codox "0.9.5"]
|
||||
[lein-doo "0.1.7"]]
|
||||
:codox {:source-paths ["target/classes" "src/clj"]
|
||||
:namespaces [com.rpl.specter
|
||||
com.rpl.specter.zipper
|
||||
|
|
@ -20,8 +21,15 @@
|
|||
#".*" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}#L{line}"}}
|
||||
|
||||
|
||||
:cljsbuild {:builds [{:id "test-build"
|
||||
:source-paths ["src/clj" "target/classes" "test"]
|
||||
:compiler {:output-to "out/testable.js"
|
||||
:main 'com.rpl.specter.cljs-test-runner
|
||||
:target :nodejs
|
||||
:optimizations :none}}]}
|
||||
|
||||
:profiles {:dev {:dependencies
|
||||
[[org.clojure/test.check "0.7.0"]
|
||||
[[org.clojure/test.check "0.9.0"]
|
||||
[org.clojure/clojure "1.8.0"]
|
||||
[org.clojure/clojurescript "1.9.229"]]}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
(ns com.rpl.specter.benchmarks
|
||||
(:use [com.rpl.specter]
|
||||
[com.rpl.specter.transients]
|
||||
[com.rpl.specter.impl :only [benchmark]])
|
||||
(:require [clojure.walk :as walk]))
|
||||
[com.rpl.specter.transients])
|
||||
(:require [clojure.walk :as walk]
|
||||
[com.rpl.specter.impl :as i]))
|
||||
|
||||
|
||||
;; run via `lein repl` with `(load-file "scripts/benchmarks.clj")`
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
(println "\n********************************\n"))))
|
||||
|
||||
(defn specter-dynamic-nested-get [data a b c]
|
||||
(select-any [(keypath a) (keypath b) (keypath c)] data))
|
||||
(select-any (keypath a b c) data))
|
||||
|
||||
|
||||
(defn get-k [k] (fn [m next] (next (get m k))))
|
||||
|
|
@ -85,7 +85,6 @@
|
|||
(select-any [(keypath :a) (keypath :b) (keypath :c)] data)))
|
||||
|
||||
|
||||
|
||||
(let [data {:a {:b {:c 1}}}]
|
||||
(run-benchmark "set value in nested map" 2500000
|
||||
(assoc-in data [:a :b :c] 1)
|
||||
|
|
@ -135,6 +134,13 @@
|
|||
ret)))))
|
||||
|
||||
|
||||
(let [data '(1 2 3 4 5)]
|
||||
(run-benchmark "transform values of a list" 500000
|
||||
(transform ALL inc data)
|
||||
(doall (sequence (map inc) data))
|
||||
(reverse (into '() (map inc) data))
|
||||
))
|
||||
|
||||
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
||||
(run-benchmark "transform values of a small map" 500000
|
||||
(into {} (for [[k v] data] [k (inc v)]))
|
||||
|
|
@ -145,8 +151,10 @@
|
|||
(transform MAP-VALS inc data)
|
||||
(zipmap (keys data) (map inc (vals data)))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
||||
(map-vals-map-iterable data inc)
|
||||
(map-vals-map-iterable-transient data inc)))
|
||||
(map-vals-map-iterable-transient data inc)
|
||||
))
|
||||
|
||||
|
||||
(let [data (->> (for [i (range 1000)] [i i]) (into {}))]
|
||||
|
|
@ -160,6 +168,7 @@
|
|||
(transform MAP-VALS inc data)
|
||||
(zipmap (keys data) (map inc (vals data)))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
||||
(map-vals-map-iterable data inc)
|
||||
(map-vals-map-iterable-transient data inc)))
|
||||
|
||||
|
|
@ -353,3 +362,15 @@
|
|||
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
|
||||
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
|
||||
))
|
||||
|
||||
(defnav walker-old [afn]
|
||||
(select* [this structure next-fn]
|
||||
(i/walk-select afn next-fn structure))
|
||||
(transform* [this structure next-fn]
|
||||
(i/walk-until afn next-fn structure)))
|
||||
|
||||
(let [data {:a [1 2 {:c '(3 4) :d {:e [1 2 3] 7 8 9 10}}]}]
|
||||
(run-benchmark "walker vs. clojure.walk version" 150000
|
||||
(transform (walker number?) inc data)
|
||||
(transform (walker-old number?) inc data)
|
||||
))
|
||||
|
|
|
|||
4
scripts/cljs-repl.sh
Executable file
4
scripts/cljs-repl.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
rlwrap java -cp `lein classpath` clojure.main scripts/repl.clj
|
||||
|
||||
|
|
@ -10,10 +10,15 @@
|
|||
defdynamicnav
|
||||
dynamicnav
|
||||
richnav
|
||||
defrichnav]]
|
||||
defrichnav
|
||||
recursive-path]]
|
||||
|
||||
[com.rpl.specter.util-macros :refer
|
||||
[doseqres]]))
|
||||
;; workaround for cljs bug that emits warnings for vars named the same as a
|
||||
;; private var in cljs.core (in this case `NONE`, added as private var to
|
||||
;; cljs.core with 1.9.562)
|
||||
#?(:cljs (:refer-clojure :exclude [NONE]))
|
||||
|
||||
(:use [com.rpl.specter.protocols :only [ImplicitNav RichNavigator]]
|
||||
#?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
|
||||
|
|
@ -32,10 +37,15 @@
|
|||
(defn wrap-dynamic-nav [f]
|
||||
(fn [& args]
|
||||
(let [ret (apply f args)]
|
||||
(if (and (sequential? ret) (static-path? ret))
|
||||
(i/comp-paths* ret)
|
||||
ret
|
||||
))))
|
||||
(cond (and (sequential? ret) (static-path? ret))
|
||||
(i/comp-paths* ret)
|
||||
|
||||
(and (sequential? ret) (= 1 (count ret)))
|
||||
(first ret)
|
||||
|
||||
:else
|
||||
ret
|
||||
))))
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
|
|
@ -69,7 +79,7 @@
|
|||
curr-params# [~@curr-params]]
|
||||
(if (every? (complement i/dynamic-param?) curr-params#)
|
||||
(apply builder# curr-params#)
|
||||
(com.rpl.specter.impl/->DynamicFunction builder# curr-params#)))))
|
||||
(com.rpl.specter.impl/->DynamicFunction builder# curr-params# nil)))))
|
||||
|
||||
(defmacro late-bound-nav [bindings & impls]
|
||||
(late-bound-operation bindings `nav impls))
|
||||
|
|
@ -186,7 +196,7 @@
|
|||
[e]
|
||||
|
||||
(sequential? e)
|
||||
(ic-possible-params e)))
|
||||
(concat (if (vector? e) [e]) (ic-possible-params e))))
|
||||
|
||||
|
||||
path)))
|
||||
|
|
@ -238,7 +248,7 @@
|
|||
|
||||
cache-sym (vary-meta
|
||||
(gensym "pathcache")
|
||||
merge {:cljs.analyzer/no-resolve true :no-doc true})
|
||||
merge {:cljs.analyzer/no-resolve true :no-doc true :private true})
|
||||
|
||||
info-sym (gensym "info")
|
||||
|
||||
|
|
@ -425,6 +435,9 @@
|
|||
(let [inav# ~retrieve]
|
||||
(i/exec-transform* inav# ~@rargs))))))))
|
||||
|
||||
(defmacro satisfies-protpath? [protpath o]
|
||||
`(satisfies? ~(protpath-sym protpath) ~o))
|
||||
|
||||
(defn extend-protocolpath* [protpath-prot extensions]
|
||||
(let [m (-> protpath-prot :sigs keys first)
|
||||
params (-> protpath-prot :sigs first last :arglists first)]
|
||||
|
|
@ -439,7 +452,12 @@
|
|||
embed (vec (for [[t p] extensions] [t `(quote ~p)]))]
|
||||
`(extend-protocolpath*
|
||||
~(protpath-sym protpath)
|
||||
~embed)))))
|
||||
~embed)))
|
||||
|
||||
(defmacro end-fn [& args]
|
||||
`(n/->SrangeEndFunction (fn ~@args)))
|
||||
|
||||
))
|
||||
|
||||
|
||||
|
||||
|
|
@ -639,6 +657,19 @@
|
|||
(transform* [this structure next-fn]
|
||||
(n/all-transform structure next-fn)))
|
||||
|
||||
(defnav
|
||||
^{:doc "Same as ALL, except maintains metadata on the structure."}
|
||||
ALL-WITH-META
|
||||
[]
|
||||
(select* [this structure next-fn]
|
||||
(n/all-select structure next-fn))
|
||||
(transform* [this structure next-fn]
|
||||
(let [m (meta structure)
|
||||
res (n/all-transform structure next-fn)]
|
||||
(if (some? res)
|
||||
(with-meta res m)
|
||||
))))
|
||||
|
||||
(defnav
|
||||
^{:doc "Navigate to each value of the map. This is more efficient than
|
||||
navigating via [ALL LAST]"}
|
||||
|
|
@ -679,14 +710,17 @@
|
|||
(n/PosNavigator n/get-first n/update-first))
|
||||
|
||||
(defnav
|
||||
^{:doc "Uses start-fn and end-fn to determine the bounds of the subsequence
|
||||
to select when navigating. Each function takes in the structure as input."}
|
||||
^{:doc "Uses start-index-fn and end-index-fn to determine the bounds of the subsequence
|
||||
to select when navigating. `start-index-fn` takes in the structure as input. `end-index-fn`
|
||||
can be one of two forms. If a regular function (e.g. defined with `fn`), it takes in only the structure as input. If a function defined using special `end-fn` macro, it takes in the structure and the result of `start-index-fn`."}
|
||||
srange-dynamic
|
||||
[start-fn end-fn]
|
||||
[start-index-fn end-index-fn]
|
||||
(select* [this structure next-fn]
|
||||
(n/srange-select structure (start-fn structure) (end-fn structure) next-fn))
|
||||
(let [s (start-index-fn structure)]
|
||||
(n/srange-select structure s (n/invoke-end-fn end-index-fn structure s) next-fn)))
|
||||
(transform* [this structure next-fn]
|
||||
(n/srange-transform structure (start-fn structure) (end-fn structure) next-fn)))
|
||||
(let [s (start-index-fn structure)]
|
||||
(n/srange-transform structure s (n/invoke-end-fn end-index-fn structure s) next-fn))))
|
||||
|
||||
|
||||
(defnav
|
||||
|
|
@ -814,32 +848,18 @@
|
|||
(merge (reduce dissoc structure m-keys)
|
||||
newmap))))
|
||||
|
||||
(defnav
|
||||
^{:doc "Using clojure.walk, navigate the data structure until reaching
|
||||
a value for which `afn` returns truthy."}
|
||||
walker
|
||||
[afn]
|
||||
(select* [this structure next-fn]
|
||||
(n/walk-select afn next-fn structure))
|
||||
(transform* [this structure next-fn]
|
||||
(n/walk-until afn next-fn structure)))
|
||||
|
||||
(defnav
|
||||
^{:doc "Like `walker` but maintains metadata of any forms traversed."}
|
||||
codewalker
|
||||
[afn]
|
||||
(select* [this structure next-fn]
|
||||
(n/walk-select afn next-fn structure))
|
||||
(transform* [this structure next-fn]
|
||||
(i/codewalk-until afn next-fn structure)))
|
||||
|
||||
(defdynamicnav subselect
|
||||
"Navigates to a sequence that contains the results of (select ...),
|
||||
but is a view to the original structure that can be transformed.
|
||||
|
||||
Requires that the input navigators will walk the structure's
|
||||
children in the same order when executed on \"select\" and then
|
||||
\"transform\"."
|
||||
\"transform\".
|
||||
|
||||
If transformed sequence is smaller than input sequence, missing entries
|
||||
will be filled in with NONE, triggering removal if supported by that navigator.
|
||||
|
||||
Value collection (e.g. collect, collect-one) may not be used in the subpath."
|
||||
[& path]
|
||||
(late-bound-nav [late (late-path path)]
|
||||
(select* [this structure next-fn]
|
||||
|
|
@ -849,11 +869,57 @@
|
|||
transformed (next-fn select-result)
|
||||
values-to-insert (i/mutable-cell transformed)]
|
||||
(compiled-transform late
|
||||
(fn [_] (let [next-val (first (i/get-cell values-to-insert))]
|
||||
(i/update-cell! values-to-insert rest)
|
||||
next-val))
|
||||
(fn [_] (let [vs (i/get-cell values-to-insert)]
|
||||
(if vs
|
||||
(do (i/update-cell! values-to-insert next)
|
||||
(first vs))
|
||||
NONE
|
||||
)))
|
||||
structure)))))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the given key in the map (not to the value). Navigates only if the
|
||||
key currently exists in the map. Can transform to NONE to remove the key/value
|
||||
pair from the map."}
|
||||
map-key
|
||||
[key]
|
||||
(select* [this vals structure next-fn]
|
||||
(if (contains? structure key)
|
||||
(next-fn vals key)
|
||||
NONE
|
||||
))
|
||||
(transform* [this vals structure next-fn]
|
||||
(if (contains? structure key)
|
||||
(let [newkey (next-fn vals key)
|
||||
dissoced (dissoc structure key)]
|
||||
(if (identical? NONE newkey)
|
||||
dissoced
|
||||
(assoc dissoced newkey (get structure key))
|
||||
))
|
||||
structure
|
||||
)))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the given element in the set only if it exists in the set.
|
||||
Can transform to NONE to remove the element from the set."}
|
||||
set-elem
|
||||
[elem]
|
||||
(select* [this vals structure next-fn]
|
||||
(if (contains? structure elem)
|
||||
(next-fn vals elem)
|
||||
NONE
|
||||
))
|
||||
(transform* [this vals structure next-fn]
|
||||
(if (contains? structure elem)
|
||||
(let [newelem (next-fn vals elem)
|
||||
removed (disj structure elem)]
|
||||
(if (identical? NONE newelem)
|
||||
removed
|
||||
(conj removed newelem)
|
||||
))
|
||||
structure
|
||||
)))
|
||||
|
||||
(def ^{:doc "Navigate to the specified keys one after another. If navigate to NONE,
|
||||
that element is removed from the map or vector."}
|
||||
keypath
|
||||
|
|
@ -944,7 +1010,11 @@
|
|||
(defdynamicnav filterer
|
||||
"Navigates to a view of the current sequence that only contains elements that
|
||||
match the given path. An element matches the selector path if calling select
|
||||
on that element with the path yields anything other than an empty sequence."
|
||||
on that element with the path yields anything other than an empty sequence.
|
||||
|
||||
For transformation: `NONE` entries in the result sequence cause corresponding entries in
|
||||
input to be removed. A result sequence smaller than the input sequence is equivalent to
|
||||
padding the result sequence with `NONE` at the end until the same size as the input."
|
||||
[& path]
|
||||
(subselect ALL (selected? path)))
|
||||
|
||||
|
|
@ -973,10 +1043,18 @@
|
|||
|
||||
(def
|
||||
^{:doc "Keeps the element only if it matches the supplied predicate. This is the
|
||||
late-bound parameterized version of using a function directly in a path."}
|
||||
late-bound parameterized version of using a function directly in a path."
|
||||
:direct-nav true}
|
||||
pred
|
||||
i/pred*)
|
||||
|
||||
|
||||
(defn ^:direct-nav pred= [v] (pred #(= % v)))
|
||||
(defn ^:direct-nav pred< [v] (pred #(< % v)))
|
||||
(defn ^:direct-nav pred> [v] (pred #(> % v)))
|
||||
(defn ^:direct-nav pred<= [v] (pred #(<= % v)))
|
||||
(defn ^:direct-nav pred>= [v] (pred #(>= % v)))
|
||||
|
||||
(extend-type nil
|
||||
ImplicitNav
|
||||
(implicit-nav [this] STAY))
|
||||
|
|
@ -1212,3 +1290,21 @@
|
|||
to implement post-order traversal."
|
||||
[& path]
|
||||
(multi-path path STAY))
|
||||
|
||||
(def
|
||||
^{:doc "Navigate the data structure until reaching
|
||||
a value for which `afn` returns truthy. Has
|
||||
same semantics as clojure.walk."}
|
||||
walker
|
||||
(recursive-path [afn] p
|
||||
(cond-path (pred afn) STAY
|
||||
coll? [ALL p]
|
||||
)))
|
||||
|
||||
(def
|
||||
^{:doc "Like `walker` but maintains metadata of any forms traversed."}
|
||||
codewalker
|
||||
(recursive-path [afn] p
|
||||
(cond-path (pred afn) STAY
|
||||
coll? [ALL-WITH-META p]
|
||||
)))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
#?(:cljs (:require-macros
|
||||
[com.rpl.specter.util-macros
|
||||
:refer [doseqres mk-comp-navs mk-late-fn mk-late-fn-records]]))
|
||||
|
||||
;; workaround for cljs bug that emits warnings for vars named the same as a
|
||||
;; private var in cljs.core (in this case `NONE`, added as private var to
|
||||
;; cljs.core with 1.9.562)
|
||||
#?(:cljs (:refer-clojure :exclude [NONE]))
|
||||
(:use [com.rpl.specter.protocols :only
|
||||
[select* transform* collect-val RichNavigator]]
|
||||
#?(:clj [com.rpl.specter.util-macros :only [doseqres mk-comp-navs]]))
|
||||
|
|
@ -435,7 +438,7 @@
|
|||
[path])
|
||||
|
||||
(defrecord DynamicFunction
|
||||
[op params])
|
||||
[op params code])
|
||||
|
||||
(defn dynamic-param? [o]
|
||||
(contains? #{DynamicPath DynamicVal DynamicFunction} (type o)))
|
||||
|
|
@ -572,6 +575,25 @@
|
|||
(with-meta ret (meta structure))
|
||||
ret))))
|
||||
|
||||
(defn walk-select [pred continue-fn structure]
|
||||
(let [ret (mutable-cell NONE)
|
||||
walker (fn this [structure]
|
||||
(if (pred structure)
|
||||
(let [r (continue-fn structure)]
|
||||
(if-not (identical? r NONE)
|
||||
(set-cell! ret r))
|
||||
r)
|
||||
|
||||
(walk/walk this identity structure)))]
|
||||
|
||||
(walker structure)
|
||||
(get-cell ret)))
|
||||
|
||||
(defn walk-until [pred on-match-fn structure]
|
||||
(if (pred structure)
|
||||
(on-match-fn structure)
|
||||
(walk/walk (partial walk-until pred on-match-fn) identity structure)))
|
||||
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
|
|
@ -645,20 +667,23 @@
|
|||
(-> o meta :direct-nav))
|
||||
|
||||
(defn all-static? [params]
|
||||
(every? (complement dynamic-param?) params))
|
||||
(identical? NONE (walk-select dynamic-param? identity params)))
|
||||
|
||||
(defn late-resolved-fn [afn]
|
||||
(fn [& args]
|
||||
(if (all-static? args)
|
||||
(apply afn args)
|
||||
(->DynamicFunction afn args)
|
||||
(->DynamicFunction afn args nil)
|
||||
)))
|
||||
|
||||
(defn preserve-map [afn o]
|
||||
(if (or (list? o) (seq? o))
|
||||
(map afn o)
|
||||
(into (empty o) (map afn o))))
|
||||
|
||||
(defn- magic-precompilation* [o]
|
||||
(cond (sequential? o)
|
||||
(if (or (list? o) (seq? o))
|
||||
(map magic-precompilation* o)
|
||||
(into (empty o) (map magic-precompilation* o)))
|
||||
(preserve-map magic-precompilation* o)
|
||||
|
||||
(instance? VarUse o)
|
||||
(if (dynamic-var? (:var o))
|
||||
|
|
@ -684,7 +709,7 @@
|
|||
(if (or (-> op meta :dynamicnav)
|
||||
(all-static? (conj params op)))
|
||||
(magic-precompilation* (apply op params))
|
||||
(->DynamicFunction op params)))
|
||||
(->DynamicFunction op params (:code o))))
|
||||
|
||||
:else
|
||||
;; this handles dynamicval as well
|
||||
|
|
@ -694,22 +719,21 @@
|
|||
([o] (static-combine o true))
|
||||
([o nav-pos?]
|
||||
(cond (sequential? o)
|
||||
(do
|
||||
(if-not nav-pos?
|
||||
;; should never happen...
|
||||
(throw-illegal "Cannot statically combine sequential when not in nav pos"))
|
||||
(if nav-pos?
|
||||
(let [res (continuous-subseqs-transform*
|
||||
rich-nav?
|
||||
(doall (map static-combine (flatten o)))
|
||||
(fn [s] [(comp-paths* s)]))]
|
||||
(if (= 1 (count res))
|
||||
(first res)
|
||||
res)))
|
||||
res))
|
||||
(preserve-map #(static-combine % false) o))
|
||||
|
||||
(instance? DynamicFunction o)
|
||||
(->DynamicFunction
|
||||
(static-combine (:op o) false)
|
||||
(doall (map #(static-combine % false) (:params o))))
|
||||
(doall (map #(static-combine % false) (:params o)))
|
||||
(:code o))
|
||||
|
||||
(instance? DynamicPath o)
|
||||
(->DynamicPath (static-combine (:path o)))
|
||||
|
|
@ -785,6 +809,10 @@
|
|||
|
||||
(declare resolve-nav-code)
|
||||
|
||||
(defn dynamic->code [o]
|
||||
;; works because both DynamicVal and DynamicFunction have :code field
|
||||
(walk-until dynamic-param? :code o))
|
||||
|
||||
(defn resolve-arg-code [o possible-params]
|
||||
(cond (instance? DynamicFunction o)
|
||||
(let [op (resolve-arg-code (:op o) possible-params)
|
||||
|
|
@ -800,7 +828,14 @@
|
|||
(resolve-nav-code o possible-params)
|
||||
|
||||
:else
|
||||
(static-val-code o)))
|
||||
;; handle dynamic params nested inside data structures
|
||||
;; e.g. (terminal-val [v])
|
||||
(if (identical? NONE (walk-select dynamic-param? identity o))
|
||||
(static-val-code o)
|
||||
;; done this way so it's compatible with cljs as well (since this dynamic val will be
|
||||
;; a possible param)
|
||||
(resolve-arg-code (->DynamicVal (dynamic->code o)) possible-params)
|
||||
)))
|
||||
|
||||
(defn resolve-nav-code [o possible-params]
|
||||
(cond
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
(:use #?(:clj [com.rpl.specter.macros :only [defnav defrichnav]])
|
||||
#?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
|
||||
(:require [com.rpl.specter.impl :as i]
|
||||
[clojure.walk :as walk]
|
||||
#?(:clj [clojure.core.reducers :as r])))
|
||||
|
||||
|
||||
|
|
@ -22,27 +21,6 @@
|
|||
[compiled-path vals structure]
|
||||
(not (not-selected?* compiled-path vals structure)))
|
||||
|
||||
(defn walk-select [pred continue-fn structure]
|
||||
(let [ret (i/mutable-cell i/NONE)
|
||||
walker (fn this [structure]
|
||||
(if (pred structure)
|
||||
(let [r (continue-fn structure)]
|
||||
(if-not (identical? r i/NONE)
|
||||
(i/set-cell! ret r))
|
||||
r)
|
||||
|
||||
(walk/walk this identity structure)))]
|
||||
|
||||
(walker structure)
|
||||
(i/get-cell ret)))
|
||||
|
||||
|
||||
(defn key-select [akey structure next-fn]
|
||||
(next-fn (get structure akey)))
|
||||
|
||||
(defn key-transform [akey structure next-fn]
|
||||
(assoc structure akey (next-fn (get structure akey))))
|
||||
|
||||
|
||||
(defn all-select [structure next-fn]
|
||||
(doseqres i/NONE [e structure]
|
||||
|
|
@ -61,11 +39,14 @@
|
|||
(defprotocol AllTransformProtocol
|
||||
(all-transform [structure next-fn]))
|
||||
|
||||
(defn void-transformed-kv-pair? [newkv]
|
||||
(or (identical? newkv i/NONE) (< (count newkv) 2)))
|
||||
|
||||
(defn- non-transient-map-all-transform [structure next-fn empty-map]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (identical? newkv i/NONE)
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
|
||||
|
|
@ -77,12 +58,14 @@
|
|||
|
||||
|
||||
(defn- all-transform-list [structure next-fn]
|
||||
;; this is done to maintain order, otherwise lists get reversed
|
||||
(->> structure
|
||||
(into '()
|
||||
(comp (map next-fn) (filter not-NONE?)))
|
||||
reverse
|
||||
))
|
||||
(doall (sequence (comp (map next-fn) (filter not-NONE?)) structure)))
|
||||
|
||||
(defn- all-transform-record [structure next-fn]
|
||||
(reduce
|
||||
(fn [res kv] (conj res (next-fn kv)))
|
||||
structure
|
||||
structure
|
||||
))
|
||||
|
||||
(extend-protocol AllTransformProtocol
|
||||
nil
|
||||
|
|
@ -127,7 +110,7 @@
|
|||
(let [k (.next k-it)
|
||||
v (.next v-it)
|
||||
newkv (next-fn [k v])]
|
||||
(if (identical? newkv i/NONE)
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
(do
|
||||
(i/update-cell! none-cell inc)
|
||||
(recur (+ i 2) j))
|
||||
|
|
@ -153,6 +136,10 @@
|
|||
(all-transform [structure next-fn]
|
||||
(non-transient-map-all-transform structure next-fn (empty structure)))
|
||||
|
||||
#?(:clj clojure.lang.IRecord)
|
||||
#?(:clj
|
||||
(all-transform [structure next-fn]
|
||||
(all-transform-record structure next-fn)))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashMap :cljs cljs.core/PersistentHashMap)
|
||||
(all-transform [structure next-fn]
|
||||
|
|
@ -160,7 +147,7 @@
|
|||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (identical? newkv i/NONE)
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc! m (nth newkv 0) (nth newkv 1)))))
|
||||
|
||||
|
|
@ -183,7 +170,7 @@
|
|||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (identical? newkv i/NONE)
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
|
||||
|
|
@ -201,12 +188,28 @@
|
|||
#?(:cljs default)
|
||||
#?(:cljs
|
||||
(all-transform [structure next-fn]
|
||||
(let [empty-structure (empty structure)]
|
||||
(if (and (list? empty-structure) (not (queue? empty-structure)))
|
||||
(all-transform-list structure next-fn)
|
||||
(into empty-structure
|
||||
(comp (map next-fn) (filter not-NONE?))
|
||||
structure))))))
|
||||
(if (record? structure)
|
||||
;; this case is solely for cljs since extending to IRecord doesn't work for cljs
|
||||
(all-transform-record structure next-fn)
|
||||
(let [empty-structure (empty structure)]
|
||||
(cond
|
||||
(and (list? empty-structure) (not (queue? empty-structure)))
|
||||
(all-transform-list structure next-fn)
|
||||
|
||||
(map? structure)
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
empty-structure
|
||||
structure)
|
||||
|
||||
:else
|
||||
(into empty-structure
|
||||
(comp (map next-fn) (filter not-NONE?))
|
||||
structure)))))))
|
||||
|
||||
|
||||
|
||||
|
|
@ -600,12 +603,6 @@
|
|||
(empty? s)))
|
||||
|
||||
|
||||
(defn walk-until [pred on-match-fn structure]
|
||||
(if (pred structure)
|
||||
(on-match-fn structure)
|
||||
(walk/walk (partial walk-until pred on-match-fn) identity structure)))
|
||||
|
||||
|
||||
(defn- do-keypath-transform [vals structure key next-fn]
|
||||
(let [newv (next-fn vals (get structure key))]
|
||||
(if (identical? newv i/NONE)
|
||||
|
|
@ -662,3 +659,12 @@
|
|||
[]
|
||||
[v])
|
||||
))))))
|
||||
|
||||
(defrecord SrangeEndFunction [end-fn])
|
||||
|
||||
;; done this way to maintain backwards compatibility
|
||||
(defn invoke-end-fn [end-fn structure start]
|
||||
(if (instance? SrangeEndFunction end-fn)
|
||||
((:end-fn end-fn) structure start)
|
||||
(end-fn structure)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
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)
|
||||
`(clojure.test.check.generators/bind ~code (fn [~v] ~curr)))
|
||||
`(clojure.test.check.generators/return ~vars)
|
||||
(reverse parts))]
|
||||
`(cljs.test.check.properties/for-all [~vars ~genned]
|
||||
`(clojure.test.check.properties/for-all [~vars ~genned]
|
||||
~@body)))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
(ns com.rpl.specter.cljs-test-runner
|
||||
(:require [cljs.test :as test :refer-macros [run-tests]]
|
||||
(:require [doo.runner :refer-macros [doo-tests]]
|
||||
[com.rpl.specter.core-test]
|
||||
[com.rpl.specter.zipper-test]))
|
||||
|
||||
|
||||
(run-tests 'com.rpl.specter.core-test)
|
||||
(run-tests 'com.rpl.specter.zipper-test)
|
||||
(doo-tests 'com.rpl.specter.core-test
|
||||
'com.rpl.specter.zipper-test)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns com.rpl.specter.core-test
|
||||
#?(:cljs (:require-macros
|
||||
[cljs.test :refer [is deftest]]
|
||||
[cljs.test.check.cljs-test :refer [defspec]]
|
||||
[clojure.test.check.clojure-test :refer [defspec]]
|
||||
[com.rpl.specter.cljs-test-helpers :refer [for-all+]]
|
||||
[com.rpl.specter.test-helpers :refer [ic-test]]
|
||||
[com.rpl.specter
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
select-first transform setval replace-in
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all]]))
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn]]))
|
||||
(:use
|
||||
#?(:clj [clojure.test :only [deftest is]])
|
||||
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
|
||||
|
|
@ -21,15 +21,15 @@
|
|||
select-first transform setval replace-in
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all]]))
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn]]))
|
||||
|
||||
|
||||
|
||||
(:require #?(:clj [clojure.test.check.generators :as gen])
|
||||
#?(:clj [clojure.test.check.properties :as prop])
|
||||
#?(:cljs [cljs.test.check :as tc])
|
||||
#?(:cljs [cljs.test.check.generators :as gen])
|
||||
#?(:cljs [cljs.test.check.properties :as prop :include-macros true])
|
||||
#?(:cljs [clojure.test.check :as tc])
|
||||
#?(:cljs [clojure.test.check.generators :as gen])
|
||||
#?(:cljs [clojure.test.check.properties :as prop :include-macros true])
|
||||
[com.rpl.specter :as s]
|
||||
[com.rpl.specter.transients :as t]
|
||||
[clojure.set :as set]))
|
||||
|
|
@ -1422,6 +1422,7 @@
|
|||
|
||||
(deftest string-navigation-test
|
||||
(is (= "ad" (setval (s/srange 1 3) "" "abcd")))
|
||||
(is (= "abcxd" (setval [(s/srange 1 3) s/END] "x" "abcd")))
|
||||
(is (= "bc" (select-any (s/srange 1 3) "abcd")))
|
||||
(is (= "ab" (setval s/END "b" "a")))
|
||||
(is (= "ba" (setval s/BEGINNING "b" "a")))
|
||||
|
|
@ -1507,3 +1508,81 @@
|
|||
(is (= "b" (setval s/FIRST s/NONE "ab")))
|
||||
(is (= "a" (setval s/LAST s/NONE "ab")))
|
||||
)
|
||||
|
||||
(deftest nested-dynamic-arg-test
|
||||
(let [foo (fn [v] (multi-transform (s/terminal-val [v]) nil))]
|
||||
(is (= [1] (foo 1)))
|
||||
(is (= [10] (foo 10)))
|
||||
))
|
||||
|
||||
(deftest filterer-remove-test
|
||||
(is (= [1 :a 3 5] (setval (s/filterer even?) [:a] [1 2 3 4 5])))
|
||||
)
|
||||
|
||||
(deftest helper-preds-test
|
||||
(let [data [1 2 2 3 4 0]]
|
||||
(is (= [2 2] (select [s/ALL (s/pred= 2)] data)))
|
||||
(is (= [1 2 2 0] (select [s/ALL (s/pred< 3)] data)))
|
||||
(is (= [1 2 2 3 0] (select [s/ALL (s/pred<= 3)] data)))
|
||||
(is (= [4] (select [s/ALL (s/pred> 3)] data)))
|
||||
(is (= [3 4] (select [s/ALL (s/pred>= 3)] data)))
|
||||
))
|
||||
|
||||
(deftest map-key-test
|
||||
(is (= {:c 3} (setval (s/map-key :a) :b {:c 3})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) :b {:a 2})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) :b {:a 2 :b 1})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) s/NONE {:a 1 :b 2})))
|
||||
)
|
||||
|
||||
(deftest set-elem-test
|
||||
(is (= #{:b :d} (setval (s/set-elem :a) :x #{:b :d})))
|
||||
(is (= #{:x :a} (setval (s/set-elem :b) :x #{:b :a})))
|
||||
(is (= #{:a} (setval (s/set-elem :b) :a #{:b :a})))
|
||||
(is (= #{:b} (setval (s/set-elem :a) s/NONE #{:a :b})))
|
||||
)
|
||||
|
||||
;; this function necessary to trigger the bug from happening
|
||||
(defn inc2 [v] (inc v))
|
||||
(deftest dynamic-function-arg-test
|
||||
(is (= {[2] 4} (let [a 1] (transform (s/keypath [(inc2 a)]) inc {[2] 3}))))
|
||||
)
|
||||
|
||||
(defrecord FooW [a b])
|
||||
|
||||
(deftest walker-test
|
||||
(is (= [1 2 3 4 5 6] (select (s/walker number?) [{1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(is (= [{:b '(:c)} #{:d}] (setval (s/walker number?) s/NONE [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(is (= [{:q 4 11 :l 2 3 :b '(4 :c 5)} 6 #{7 :d}]
|
||||
(transform (s/walker number?) inc [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(let [f (->FooW 1 2)]
|
||||
(is (= [[:a 1] [:b 2]] (select (s/walker (complement record?)) f)))
|
||||
(is (= (assoc f :a! 1 :b! 2) (setval [(s/walker (complement record?)) s/FIRST s/NAME s/END] "!" f)))
|
||||
(is (= (assoc f :b 1 :c 2) (transform [(s/walker (complement record?)) s/FIRST] (fn [k] (if (= :a k) :b :c)) f)))
|
||||
))
|
||||
|
||||
(def MIDDLE
|
||||
(s/comp-paths
|
||||
(s/srange-dynamic
|
||||
(fn [aseq] (long (/ (count aseq) 2)))
|
||||
(end-fn [aseq s] (if (empty? aseq) 0 (inc s))))
|
||||
s/FIRST
|
||||
))
|
||||
|
||||
(deftest srange-dynamic-test
|
||||
(is (= 2 (select-any MIDDLE [1 2 3])))
|
||||
(is (identical? s/NONE (select-any MIDDLE [])))
|
||||
(is (= 1 (select-any MIDDLE [1])))
|
||||
(is (= 2 (select-any MIDDLE [1 2])))
|
||||
(is (= [1 3 3] (transform MIDDLE inc [1 2 3])))
|
||||
)
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(defprotocolpath FooPP)
|
||||
(extend-protocolpath FooPP String s/STAY)
|
||||
|
||||
(deftest satisfies-protpath-test
|
||||
(is (satisfies-protpath? FooPP "a"))
|
||||
(is (not (satisfies-protpath? FooPP 1)))
|
||||
)))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns com.rpl.specter.zipper-test
|
||||
#?(:cljs (:require-macros
|
||||
[cljs.test :refer [is deftest]]
|
||||
[cljs.test.check.cljs-test :refer [defspec]]
|
||||
[clojure.test.check.clojure-test :refer [defspec]]
|
||||
[com.rpl.specter.cljs-test-helpers :refer [for-all+]]
|
||||
[com.rpl.specter
|
||||
:refer [declarepath providepath select select-one select-one!
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
(:require #?(:clj [clojure.test.check.generators :as gen])
|
||||
#?(:clj [clojure.test.check.properties :as prop])
|
||||
#?(:cljs [cljs.test.check :as tc])
|
||||
#?(:cljs [cljs.test.check.generators :as gen])
|
||||
#?(:cljs [cljs.test.check.properties :as prop :include-macros true])
|
||||
#?(:cljs [clojure.test.check :as tc])
|
||||
#?(:cljs [clojure.test.check.generators :as gen])
|
||||
#?(:cljs [clojure.test.check.properties :as prop :include-macros true])
|
||||
[com.rpl.specter :as s]
|
||||
[com.rpl.specter.zipper :as z]))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue