Compare commits

...

22 commits

Author SHA1 Message Date
tommy
9a615ff22c
Implement ALL for strings (#333) 2024-09-16 05:10:52 -10:00
Nathan Marz
67e8680602 update changelog 2022-03-18 07:45:26 -10:00
Nathan Marz
05ae730896 1.1.5-SNAPSHOT 2022-03-18 07:42:58 -10:00
Nathan Marz
a91aaaa34a 1.1.4 2022-03-18 07:41:05 -10:00
Michiel Borkent
efeefdfacd
Update CHANGES.md (#324) 2022-03-14 09:46:29 -10:00
Michiel Borkent
a64209d582
Make babashka-compatible (#323) 2022-03-14 07:11:40 -10:00
Joshua Suskalo
d0d6fdf581
Add section to the readme with clj-kondo configuration snippet. (#310) (#311) 2021-06-02 13:47:44 -10:00
Jeff Evans
e8225f0e58
Make it possible to run individual benchmark(s) (#298)
Parse command line args to benchmarks.clj and treat them as benchmark names to run

Add information on running benchmarks to DEVELOPER.md
2020-10-22 17:51:04 -04:00
Nathan Marz
a379893598 BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs 2020-09-17 15:47:26 -04:00
Nathan Marz
efaf35558a replace throw-illegal with ex-info 2020-09-17 15:36:16 -04:00
Nathan Marz
083376f904 update changelog 2020-09-17 15:26:28 -04:00
Nathan Marz
de48f24d70 update changelog 2020-09-17 15:25:22 -04:00
Nathan Marz
c54a46c686 before-index 0 on nil produces list, fix flaws in tests regarding predand= 2020-09-17 15:24:11 -04:00
Jeff Evans
2e002c1270
Improve before-index performance #223 (#291)
Adding new protocol for performing the insert-before-idx operation, with
implementations for core collection types

Adding new functional test to confirm behavior when operating on a string

Adding benchmarks to compare new performance vs old implementation vs
core Clojure in a couple of cases
2020-09-17 15:15:42 -04:00
Jeff Evans
789881b3ad
Fix instructions for running tests (#295) 2020-09-14 15:37:16 -04:00
Nathan Marz
edd7429281 update changelog 2020-08-16 19:40:59 -04:00
Adrian
e222ba2a6c
Add arglist metadata to navs. (#290) 2020-08-16 19:40:02 -04:00
Nathan Marz
40add561b6
Update README.md 2020-01-02 21:45:33 -05:00
Nathan Marz
123af0937e update release date 2019-10-13 23:07:26 -04:00
Nathan Marz
6e7d772755 update codox 2019-10-13 23:04:36 -04:00
Nathan Marz
134beac2a7 begin 1.1.4 2019-10-13 22:56:15 -04:00
Nathan Marz
86f8412ee4 update deploy 2019-10-13 22:56:04 -04:00
17 changed files with 422 additions and 150 deletions

37
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch_depth: 0
- name: Setup Babashka
uses: turtlequeue/setup-babashka@v1.3.0
with:
babashka-version: 0.7.8
- name: Prepare java
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '8'
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@4.0
with:
lein: 2.9.1
- name: Run clj tests
run: bb test:clj
- name: Run cljs tests
run: bb test:cljs
- name: Run babashka tests
run: bb test:bb

2
.gitignore vendored
View file

@ -11,3 +11,5 @@ pom.xml.asc
.lein-failures
.cljs_node_repl
out/
.cpcache
.cache

View file

@ -1,8 +0,0 @@
language: clojure
lein: 2.9.1
cache:
directories:
- $HOME/.m2
script:
- lein test
- lein doo node test-build once

View file

@ -1,4 +1,11 @@
## 1.1.3
## 1.1.4
* Add arglist metadata to navs (thanks @phronmophobic)
* Improve before-index performance by 150x on lists and 5x on vectors (thanks @jeff303)
* Bug fix: BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs
* Add compatibility with [babashka](https://babashka.org/)
## 1.1.3 - 2019-10-13
* Better AOT behavior: path functions for inline caching and protpath extensions no longer write eval'd class files. You can force path functions to not override `*compile-files*` by binding `com.rpl.specter.impl/*path-compile-files*` to `true`.
* Bug fix: fix throw-illegal in cljs

View file

@ -1,7 +1,7 @@
# Running Clojure tests
```
lein cleantest
lein do clean, test
```
# Running ClojureScript tests
@ -10,3 +10,16 @@ lein cleantest
lein javac
lein doo node test-build once
```
# Running benchmarks
## All benchmarks
```
scripts/run-benchmarks
```
## Individual benchmark(s)
Specify the benchmark names as command line args. They will likely each need quoted because they contain spaces.
Order is ignored.
```
scripts/run-benchmarks "prepend to a vector" "filter a sequence"
```

View file

@ -1,4 +1,4 @@
# Specter [![Build Status](https://secure.travis-ci.org/redplanetlabs/specter.png?branch=master)](http://travis-ci.org/redplanetlabs/specter)
# Specter
Specter rejects Clojure's restrictive approach to immutable data structure manipulation, instead exposing an elegant API to allow any sort of manipulation imaginable. Specter especially excels at querying and transforming nested and recursive data, important use cases that are very complex to handle with vanilla Clojure.
@ -322,6 +322,22 @@ Specter supports ClojureScript! However, some of the differences between Clojure
# 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.
# clj-kondo
When using Specter in a project with [clj-kondo](https://github.com/clj-kondo/clj-kondo), a lot of the vars will be considered unresolved because internally Specter defines them with macros. The following configuration snippet will resolve these issues if you include it in your `.clj-kondo/config.edn` file.
```clojure
{:lint-as {com.rpl.specter/defcollector clojure.core/defn
com.rpl.specter/defdynamicnav clojure.core/defn
com.rpl.specter/defmacroalias clojure.core/def
com.rpl.specter/defnav clojure.core/defn
com.rpl.specter/defrichnav clojure.core/defn}}
```
# Babashka
This library is compatible with [babashka](https://babashka.org/) as of specter 1.1.4 and babashka 0.7.8.
# License
Copyright 2015-2019 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.
Copyright 2015-2020 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.

View file

@ -1 +1 @@
1.1.3
1.1.5-SNAPSHOT

19
bb.edn Normal file
View file

@ -0,0 +1,19 @@
{:paths ["src/clj"]
:tasks
{test:clj {:doc "Run clj tests with leiningen"
:task (shell "lein test")}
test:cljs {:doc "Run cljs tests with leiningen"
:task (shell "lein doo node test-build once")}
test:bb {:doc "Run bb tests"
:extra-paths ["test"]
:extra-deps {org.clojure/test.check {:mvn/version "0.9.0"}
io.github.cognitect-labs/test-runner
{:git/tag "v0.5.0" :git/sha "b3fd0d2"}
org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace"
:git/sha "3625153ee66dfcec2ba600851b5b2cbdab8fae6c"}}
:requires ([cognitect.test-runner :as tr])
:task (apply tr/-main
"-d" "test"
*command-line-args*)}}}

View file

@ -9,7 +9,7 @@
:test-paths ["test", "target/test-classes"]
:auto-clean false
:dependencies [[riddley "0.1.12"]]
:plugins [[lein-codox "0.9.5"]
:plugins [[lein-codox "0.10.7"]
[lein-doo "0.1.7"]]
:codox {:source-paths ["target/classes" "src/clj"]
:namespaces [com.rpl.specter
@ -36,4 +36,8 @@
[criterium "0.4.4"]]}
:test {:dependencies [[org.clojure/clojure "1.7.0"]]}}
:deploy-repositories
[["clojars" {:url "https://repo.clojars.org"
:sign-releases false}]]
:aliases {"deploy" ["do" "clean," "deploy" "clojars"]})

View file

@ -19,11 +19,14 @@
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
(defmacro run-benchmark [name & exprs]
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
`(do
(println "Benchmark:" ~name)
(compare-benchmark ~afn-map)
(println "\n********************************\n"))))
(let [only-benchmarks (set (filter some? *command-line-args*))
all-benchmarks? (empty? only-benchmarks)]
(if (or all-benchmarks? (contains? only-benchmarks name))
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
`(do
(println "Benchmark:" ~name)
(compare-benchmark ~afn-map)
(println "\n********************************\n"))))))
(defn specter-dynamic-nested-get [data a b c]
(select-any (keypath a b c) data))
@ -335,3 +338,20 @@
(transform (walker number?) inc data)
(transform (walker-old number?) inc data)
))
(let [size 1000
middle-idx (/ size 2)
v -1
rng (range size)
data-vec (vec rng)
data-lst (apply list rng)]
(run-benchmark "before-index vs. srange in middle (vector)"
(setval (before-index middle-idx) v data-vec)
(setval (srange middle-idx middle-idx) [v] data-vec))
(run-benchmark "before-index vs. srange in middle (list)"
(setval (before-index middle-idx) v data-lst)
(setval (srange middle-idx middle-idx) [v] data-lst))
(run-benchmark "before-index at 0 vs. srange vs. cons (list)"
(setval (before-index 0) v data-lst)
(setval (srange 0 0) [v] data-lst)
(cons v data-lst)))

View file

@ -5,4 +5,4 @@ lein version
echo
lein show-profiles bench
echo
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj "$@"

View file

@ -115,7 +115,32 @@
(providepath ~self-sym ~path)
~self-sym)))))
;; copied from tools.macro to avoid the dependency
;; copied from clojure.core
(def
^{:private true}
sigs
(fn [fdecl]
(let [asig
(fn [fdecl]
(let [arglist (first fdecl)
;; elide implicit macro args
arglist (if (= '&form (first arglist))
(subvec arglist 2 (count arglist))
arglist)
body (next fdecl)]
(if (map? (first body))
(if (next body)
(with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body)))
arglist)
arglist)))]
(if (seq? (first fdecl))
(loop [ret [] fdecls fdecl]
(if fdecls
(recur (conj ret (asig (first fdecls))) (next fdecls))
(seq ret)))
(list (asig fdecl))))))
;; partially copied from clojure.core/defn
(defn- name-with-attributes
"To be used in macro definitions.
Handles optional docstrings and attribute maps for a name to be defined
@ -126,20 +151,31 @@
macro argument list. The return value is a vector containing the name
with its extended metadata map and the list of unprocessed macro
arguments."
[name macro-args]
(let [[docstring macro-args] (if (string? (first macro-args))
[(first macro-args) (next macro-args)]
[nil macro-args])
[attr macro-args] (if (map? (first macro-args))
[(first macro-args) (next macro-args)]
[{} macro-args])
attr (if docstring
(assoc attr :doc docstring)
attr)
attr (if (meta name)
(conj (meta name) attr)
attr)]
[(with-meta name attr) macro-args]))
[name fdecl]
(let [m (if (string? (first fdecl))
{:doc (first fdecl)}
{})
fdecl (if (string? (first fdecl))
(next fdecl)
fdecl)
m (if (map? (first fdecl))
(conj m (first fdecl))
m)
fdecl (if (map? (first fdecl))
(next fdecl)
fdecl)
fdecl (if (vector? (first fdecl))
(list fdecl)
fdecl)
m (if (map? (last fdecl))
(conj m (last fdecl))
m)
fdecl (if (map? (last fdecl))
(butlast fdecl)
fdecl)
m (conj {:arglists (list 'quote (sigs fdecl))} m)]
[(with-meta name m) fdecl]))
(defmacro dynamicnav [& args]
`(vary-meta (wrap-dynamic-nav (fn ~@args)) assoc :dynamicnav true))
@ -974,11 +1010,10 @@
NONE)
(transform* [this vals structure next-fn]
(let [v (next-fn vals NONE)]
(if (identical? NONE v)
structure
;; TODO: make a more efficient impl
(setval (srange index index) [v] structure)
))))
(if
(identical? NONE v)
structure
(n/insert-before-idx structure index v)))))
(defrichnav
^{:doc "Navigates to the index of the sequence if within 0 and size. Transforms move element
@ -1273,7 +1308,7 @@
ns (namespace structure)]
(cond (keyword? structure) (keyword ns new-name)
(symbol? structure) (symbol ns new-name)
:else (i/throw-illegal "NAME can only be used on symbols or keywords - " structure)
:else (throw (ex-info "NAME can only be used on symbols or keywords" {:structure structure}))
))))
(defnav ^{:doc "Navigates to the namespace portion of the keyword or symbol"}
@ -1286,7 +1321,8 @@
new-ns (next-fn (namespace structure))]
(cond (keyword? structure) (keyword new-ns name)
(symbol? structure) (symbol new-ns name)
:else (i/throw-illegal "NAMESPACE can only be used on symbols or keywords - " structure)
:else (throw (ex-info "NAMESPACE can only be used on symbols or keywords"
{:structure structure}))
))))
(defdynamicnav

View file

@ -2,7 +2,7 @@
#?(:cljs (:require-macros
[com.rpl.specter.util-macros
:refer [doseqres mk-comp-navs mk-late-fn mk-late-fn-records]]
[com.rpl.specter.impl :refer [throw-illegal]]))
))
;; 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)
@ -15,10 +15,11 @@
#?(:clj [clojure.pprint :as pp])
[clojure.string :as s]
[clojure.walk :as walk]
#?(:clj [riddley.walk :as riddley]))
#?(:clj (:import [com.rpl.specter Util MutableCell])))
#?(:bb [clojure.walk :as riddley]
:clj [riddley.walk :as riddley]))
#?@(:bb []
:clj [(:import [com.rpl.specter Util MutableCell])]))
(def NONE ::NONE)
@ -50,24 +51,6 @@
([a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 & r] v)))
#?(:clj
(defmacro throw* [etype & args]
`(throw (new ~etype (smart-str ~@args)))))
#?(
:clj
(defmacro throw-illegal [& args]
(let [platform (if (contains? &env :locals) :cljs :clj)]
(if (= platform :clj)
`(throw* IllegalArgumentException ~@args)
`(com.rpl.specter.impl/throw-illegal* ~@args)
)))
:cljs
(defn throw-illegal* [& args]
(throw (js/Error. (apply str args)))))
;; need to get the expansion function like this so that
;; this code compiles in a clojure environment where cljs.analyzer
;; namespace does not exist
@ -89,7 +72,7 @@
:cljs
(defn clj-macroexpand-all [form]
(throw-illegal "not implemented")))
(throw (ex-info "not implemented" {}))))
#?(
@ -98,9 +81,11 @@
:cljs
(defn intern* [ns name val]
(throw-illegal "intern not supported in ClojureScript")))
(throw (ex-info "intern not supported in ClojureScript" {}))))
#?(
#?(:bb
(defmacro fast-object-array [i]
`(object-array ~i))
:clj
(defmacro fast-object-array [i]
`(com.rpl.specter.Util/makeObjectArray ~i)))
@ -119,7 +104,8 @@
(if (= platform :cljs)
`(p/select* ~this ~@args)
`(let [~hinted ~this]
(.select* ~hinted ~@args)))))
(#?(:bb p/select*
:clj .select*) ~hinted ~@args)))))
:cljs
(defn exec-select* [this vals structure next-fn]
(p/select* ^not-native this vals structure next-fn)))
@ -133,7 +119,8 @@
(if (= platform :cljs)
`(p/transform* ~this ~@args)
`(let [~hinted ~this]
(.transform* ~hinted ~@args)))))
(#?(:bb p/transform*
:clj .transform*) ~hinted ~@args)))))
:cljs
(defn exec-transform* [this vals structure next-fn]
@ -152,7 +139,9 @@
(defn- coerce-object [this]
(cond (rich-nav? this) this
(satisfies? p/ImplicitNav this) (p/implicit-nav this)
:else (throw-illegal "Not a navigator: " this " " (pr-str (type this)))))
:else (throw (ex-info "Not a navigator"
{:this this
:type-str (pr-str (type this))}))))
(defprotocol CoercePath
@ -230,13 +219,19 @@
(set_cell [cell x])))
#?(:cljs
#?(:bb
(defrecord MutableCell [x])
:cljs
(deftype MutableCell [^:volatile-mutable q]
PMutableCell
(set_cell [this x] (set! q x))))
#?(
#?(:bb
(defn mutable-cell
([] (mutable-cell nil))
([v] (MutableCell. (volatile! v))))
:clj
(defn mutable-cell
([] (mutable-cell nil))
@ -248,7 +243,10 @@
([init] (MutableCell. init))))
#?(
#?(:bb
(defn set-cell! [^MutableCell c v]
(vreset! (:x c) v))
:clj
(defn set-cell! [^MutableCell c v]
(.set c v))
@ -258,7 +256,10 @@
(set_cell cell val)))
#?(
#?(:bb
(defn get-cell [^MutableCell c]
@(:x c))
:clj
(defn get-cell [^MutableCell c]
(.get c))
@ -306,8 +307,10 @@
(defn do-compiled-traverse* [apath structure]
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
(#?(:clj reduce :cljs -reduce)
[this afn]
(#?(:clj .reduce :cljs -reduce) this afn (afn)))
[this afn]
#?(:bb (reduce afn (afn) this)
:default
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
(#?(:clj reduce :cljs -reduce)
[this afn start]
(let [cell (mutable-cell start)]
@ -324,6 +327,9 @@
))))
#?(
:bb
(defn- call-reduce-interface [^clojure.lang.IReduce traverser afn start]
(reduce afn start traverser))
:clj
(defn- call-reduce-interface [^clojure.lang.IReduce traverser afn start]
(.reduce traverser afn start)
@ -338,8 +344,10 @@
(let [traverser (do-compiled-traverse* apath structure)]
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
(#?(:clj reduce :cljs -reduce)
[this afn]
(#?(:clj .reduce :cljs -reduce) this afn (afn)))
[this afn]
#?(:bb (reduce afn (afn) this)
:default
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
(#?(:clj reduce :cljs -reduce)
[this afn start]
(let [res (call-reduce-interface traverser afn start)]
@ -376,7 +384,8 @@
(let [curr (get-cell res)]
(if (identical? curr NONE)
(set-cell! res structure)
(throw-illegal "More than one element found in structure: " structure))))]
(throw (ex-info "More than one element found in structure"
{:structure structure})))))]
(compiled-traverse* path result-fn structure)
(let [ret (get-cell res)]
@ -391,11 +400,12 @@
(let [curr (get-cell res)]
(if (identical? curr NONE)
(set-cell! res structure)
(throw-illegal "More than one element found in structure: " structure))))]
(throw (ex-info "More than one element found in structure"
{:structure structure})))))]
(compiled-traverse* path result-fn structure)
(let [ret (get-cell res)]
(if (identical? NONE ret)
(throw-illegal "Found no elements for select-one! on " structure))
(throw (ex-info "Found no elements for select-one!" {:structure structure})))
ret)))
@ -813,7 +823,8 @@
(defn dynamic-val-code [code possible-params]
(let [[i] (keep-indexed (fn [i v] (if (= v code) i)) possible-params)]
(if (nil? i)
(throw-illegal "Could not find " code " in possible params " possible-params))
(throw (ex-info "Could not find code in possible params"
{:code code :possible-params possible-params})))
(maybe-direct-nav
(->LocalParam i)
(direct-nav? code)))))
@ -985,9 +996,9 @@
(defn- multi-transform-error-fn [& nav]
(throw-illegal
"All navigation in multi-transform must end in 'terminal' "
"navigators. Instead navigated to: " nav))
(throw
(ex-info "All navigation in multi-transform must end in 'terminal' navigators"
{:nav nav})))
(defn compiled-multi-transform* [path structure]
(compiled-transform* path multi-transform-error-fn structure))

View file

@ -6,8 +6,8 @@
(defn- determine-params-impls [impls]
(let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))]
(if-not (= #{'select* 'transform*} (-> grouped keys set))
(i/throw-illegal "defnav must implement select* and transform*, instead got "
(keys grouped)))
(throw (ex-info "defnav must implement select* and transform*"
{:methods (keys grouped)})))
grouped))
@ -39,11 +39,16 @@
(let [helpers (for [[mname [_ & mparams] & mbody] impls]
`(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody))
decls (for [[mname & _] impls]
`(declare ~(helper-name name mname)))]
`(declare ~(helper-name name mname)))
name-with-meta (vary-meta name
assoc :arglists (list 'quote (list params)))]
`(do
~@decls
~@helpers
(def ~name (nav ~params ~@impls)))))
(def ~name-with-meta (nav ~params ~@impls)))))
(defmacro defrichnav [name params & impls]
`(def ~name (richnav ~params ~@impls)))
(let [name-with-meta (vary-meta name
assoc :arglists (list 'quote (list params)))]
`(def ~name-with-meta
(richnav ~params ~@impls))))

View file

@ -8,7 +8,8 @@
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
[com.rpl.specter.util-macros :only [doseqres]]))
(:require [com.rpl.specter.impl :as i]
#?(:clj [clojure.core.reducers :as r])))
#?@(:bb []
:clj [[clojure.core.reducers :as r]])))
(defn not-selected?*
@ -95,6 +96,13 @@
(filter not-NONE?))
structure))
#?(:clj String :cljs string)
(all-transform [structure next-fn]
(apply str (into []
(comp (map next-fn)
(filter not-NONE?))
structure)))
#?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet)
(all-transform [structure next-fn]
(into #{}
@ -103,7 +111,10 @@
structure))
#?(:clj clojure.lang.PersistentArrayMap)
#?(:clj
#?(:bb
(all-transform [structure next-fn]
(non-transient-map-all-transform structure next-fn {}))
:clj
(all-transform [structure next-fn]
(let [k-it (.keyIterator structure)
v-it (.valIterator structure)
@ -185,10 +196,13 @@
:else
(->> structure
(r/map next-fn)
(r/filter not-NONE?)
(into empty-structure))))))
#?(:bb (into empty-structure
(comp (map next-fn) (filter not-NONE?))
structure)
:clj (->> structure
(r/map next-fn)
(r/filter not-NONE?)
(into empty-structure)))))))
#?(:cljs default)
@ -255,7 +269,10 @@
#?(:clj clojure.lang.PersistentArrayMap)
#?(:clj
#?(:bb
(map-vals-transform [structure next-fn]
(map-vals-non-transient-transform structure {} next-fn))
:clj
(map-vals-transform [structure next-fn]
(let [k-it (.keyIterator structure)
v-it (.valIterator structure)
@ -282,7 +299,10 @@
array
)]
(clojure.lang.PersistentArrayMap. array)))))
#?(:clj
#?(:bb
(map-keys-transform [structure next-fn]
(map-keys-non-transient-transform structure {} next-fn))
:clj
(map-keys-transform [structure next-fn]
(let [k-it (.keyIterator structure)
v-it (.valIterator structure)
@ -449,6 +469,24 @@
(prepend-one [structure elem]
(into [elem] structure))
#?(:cljs cljs.core/Subvec)
#?(:cljs
(append-all [structure elements]
(reduce conj structure elements)))
#?(:cljs
(prepend-all [structure elements]
(let [ret (transient [])]
(as-> ret <>
(reduce conj! <> elements)
(reduce conj! <> structure)
(persistent! <>)))))
#?(:cljs
(append-one [structure elem]
(conj structure elem)))
#?(:cljs
(prepend-one [structure elem]
(into [elem] structure)))
#?(:clj Object :cljs default)
(append-all [structure elements]
@ -474,6 +512,9 @@
(defprotocol FastEmpty
(fast-empty? [s]))
(defprotocol InsertBeforeIndex
(insert-before-idx [aseq idx val]))
(defnav PosNavigator [getter updater]
(select* [this structure next-fn]
(if-not (fast-empty? structure)
@ -484,6 +525,17 @@
structure
(updater structure next-fn))))
#?(:bb
(defn vec-count [v]
(count v))
:clj
(defn vec-count [^clojure.lang.IPersistentVector v]
(.length v))
:cljs
(defn vec-count [v]
(count v)))
(defn- update-first-list [l afn]
(let [newf (afn (first l))
@ -499,17 +551,39 @@
(if (nil? bl) '() bl)
(concat bl [lastl]))))
#?(
:clj
(defn vec-count [^clojure.lang.IPersistentVector v]
(.length v))
(defn- update-first-vector [v afn]
(let [val (nth v 0)
newv (afn val)]
(if (identical? i/NONE newv)
(subvec v 1)
(assoc v 0 newv)
)))
:cljs
(defn vec-count [v]
(count v)))
(defn- update-last-vector [v afn]
;; type-hinting vec-count to ^int caused weird errors with case
(let [c (int (vec-count v))]
(case c
1 (let [[e] v
newe (afn e)]
(if (identical? i/NONE newe)
[]
[newe]))
2 (let [[e1 e2] v
newe (afn e2)]
(if (identical? i/NONE newe)
[e1]
[e1 newe]))
(let [i (dec c)
newe (afn (nth v i))]
(if (identical? i/NONE newe)
(pop v)
(assoc v i newe))))))
#?(
#?(:bb
(defn transient-vec-count [v]
(count v))
:clj
(defn transient-vec-count [^clojure.lang.ITransientVector v]
(.count v))
@ -522,32 +596,18 @@
(extend-protocol UpdateExtremes
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(update-first [v afn]
(let [val (nth v 0)
newv (afn val)]
(if (identical? i/NONE newv)
(subvec v 1)
(assoc v 0 newv)
)))
(update-first-vector v afn))
(update-last [v afn]
;; type-hinting vec-count to ^int caused weird errors with case
(let [c (int (vec-count v))]
(case c
1 (let [[e] v
newe (afn e)]
(if (identical? i/NONE newe)
[]
[newe]))
2 (let [[e1 e2] v
newe (afn e2)]
(if (identical? i/NONE newe)
[e1]
[e1 newe]))
(let [i (dec c)
newe (afn (nth v i))]
(if (identical? i/NONE newe)
(pop v)
(assoc v i newe))))))
(update-last-vector v afn))
#?(:cljs cljs.core/Subvec)
#?(:cljs
(update-first [v afn]
(update-first-vector v afn)))
#?(:cljs
(update-last [v afn]
(update-last-vector v afn)))
#?(:clj String :cljs string)
(update-first [s afn]
@ -691,3 +751,36 @@
((:end-fn end-fn) structure start)
(end-fn structure)
))
(defn- insert-before-index-list [lst idx val]
;; an implementation that is most efficient for list style structures
(let [[front back] (split-at idx lst)]
(concat front (cons val back))))
(extend-protocol InsertBeforeIndex
nil
(insert-before-idx [_ idx val]
(if (= 0 idx)
(list val)
(throw (ex-info "For a nil structure, can only insert before index 0"
{:insertion-index idx}))))
#?(:clj java.lang.String :cljs string)
(insert-before-idx [aseq idx val]
(apply str (insert-before-index-list aseq idx val)))
#?(:clj clojure.lang.LazySeq :cljs cljs.core/LazySeq)
(insert-before-idx [aseq idx val]
(insert-before-index-list aseq idx val))
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(insert-before-idx [aseq idx val]
(let [front (subvec aseq 0 idx)
back (subvec aseq idx)]
(into (conj front val) back)))
#?(:clj clojure.lang.IPersistentList :cljs cljs.core/List)
(insert-before-idx [aseq idx val]
(cond (= idx 0)
(cons val aseq)
:else (insert-before-index-list aseq idx val))))

View file

@ -65,4 +65,4 @@
`(defn ~'late-fn [~f ~args]
(case (count ~args)
~@(apply concat cases)
(com.rpl.specter.impl/throw-illegal "Cannot have late function with more than 18 args")))))
(throw (ex-info "Cannot have late function with more than 18 args" {}))))))

View file

@ -1320,9 +1320,13 @@
(deftest traversed-test
(is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4]))))
(defn- predand= [pred ret v]
(and (pred ret)
(= ret v)))
(defn- predand= [pred v1 v2]
(and (pred v1)
(pred v2)
(= v1 v2)))
(defn listlike? [v]
(or (list? v) (seq? v)))
(deftest nthpath-test
(is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4])))
@ -1334,7 +1338,7 @@
(deftest remove-with-NONE-test
(is (predand= vector? [1 2 3] (setval [s/ALL nil?] s/NONE [1 2 nil 3 nil])))
(is (predand= list? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil))))
(is (predand= listlike? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil))))
(is (= {:b 2} (setval :a s/NONE {:a 1 :b 2})))
(is (= {:b 2} (setval (s/must :a) s/NONE {:a 1 :b 2})))
(is (predand= vector? [1 3] (setval (s/keypath 1) s/NONE [1 2 3])))
@ -1436,6 +1440,15 @@
(is (= "abq" (setval s/LAST "q" "abc")))
)
(defn whitespace? [char]
(re-matches #"\s" (str char)))
(deftest string-transform-test
(is (= "123" (transform s/ALL identity "123")))
(is (= "123" (transform [s/ALL whitespace?] s/NONE "1 2 3")))
#?(:clj (is (= "123" (setval [s/ALL #(Character/isWhitespace %)] s/NONE "1 2 3"))))
#?(:clj (is (= "123" (transform [(s/filterer #(Character/isWhitespace %))] s/NONE "1 2 3")))))
(deftest regex-navigation-test
;; also test regexes as implicit navs
(is (= (select #"t" "test") ["t" "t"]))
@ -1454,11 +1467,11 @@
(deftest single-value-none-navigators-test
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2])))
(is (predand= list? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/AFTER-ELEM 1 nil)))
(is (predand= listlike? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
(is (predand= listlike? '(1) (setval s/AFTER-ELEM 1 nil)))
(is (predand= vector? [3 1 2] (setval s/BEFORE-ELEM 3 [1 2])))
(is (predand= list? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/BEFORE-ELEM 1 nil)))
(is (predand= listlike? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
(is (predand= listlike? '(1) (setval s/BEFORE-ELEM 1 nil)))
(is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2})))
(is (= #{1} (setval s/NONE-ELEM 1 nil)))
)
@ -1502,7 +1515,7 @@
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
(let [newl (setval s/FIRST s/NONE l)]
(and (= newl (rest l))
(list? newl)
(listlike? newl)
))))
(defspec remove-last-vector
@ -1609,14 +1622,18 @@
(deftest before-index-test
(let [data [1 2 3]
datal '(1 2 3)]
datal '(1 2 3)
data-str "abcdef"]
(is (predand= vector? [:a 1 2 3] (setval (s/before-index 0) :a data)))
(is (predand= vector? [1 2 3] (setval (s/before-index 1) s/NONE data)))
(is (predand= vector? [1 :a 2 3] (setval (s/before-index 1) :a data)))
(is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data)))
(is (predand= list? '(:a 1 2 3) (setval (s/before-index 0) :a datal)))
(is (predand= list? '(1 :a 2 3) (setval (s/before-index 1) :a datal)))
(is (predand= list? '(1 2 3 :a) (setval (s/before-index 3) :a datal)))
; ensure inserting at index 0 in nil structure works, as in previous impl
(is (predand= listlike? '(:a) (setval (s/before-index 0) :a nil)))
(is (predand= listlike? '(:a 1 2 3) (setval (s/before-index 0) :a datal)))
(is (predand= listlike? '(1 :a 2 3) (setval (s/before-index 1) :a datal)))
(is (predand= listlike? '(1 2 3 :a) (setval (s/before-index 3) :a datal)))
(is (predand= string? "abcxdef" (setval (s/before-index 3) (char \x) data-str)))
))
(deftest index-nav-test
@ -1629,12 +1646,12 @@
(is (predand= vector? [1 2 4 5 6 3] (setval (s/index-nav 2) 5 data)))
(is (predand= vector? [6 1 2 3 4 5] (setval (s/index-nav 5) 0 data)))
(is (predand= list? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal)))
(is (predand= list? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal)))
(is (predand= list? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal)))
(is (predand= list? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal)))
(is (predand= list? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal)))
(is (predand= list? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal)))
(is (predand= listlike? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal)))
(is (predand= listlike? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal)))
(is (predand= listlike? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal)))
(is (predand= listlike? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal)))
(is (predand= listlike? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal)))
(is (predand= listlike? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal)))
))
(deftest indexed-vals-test