Compare commits

...

216 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
Nathan Marz
28ef42152a 1.1.3 2019-10-13 22:22:18 -04:00
Nathan Marz
ffe8130bf0 Merge branch 'master' of github.com:redplanetlabs/specter 2019-09-10 18:19:08 -04:00
Nathan Marz
9c7f6fb65e don't write class files for eval'd functions for inline path functions or protpath extensions. 2019-09-10 18:18:56 -04:00
Nathan Marz
029a33427f
Update README.md
Update links in README
2019-08-29 13:39:20 -04:00
nathanmarz
925e2e91d6 Merge branch 'master' of github.com:nathanmarz/specter 2019-08-27 20:48:58 -04:00
nathanmarz
29ee91d01e add CONTRIBUTING.md 2019-08-27 20:48:49 -04:00
Nathan Marz
b7ff05bf54
Update .travis.yml 2019-08-24 19:19:34 -04:00
Nathan Marz
ae24856ec3
Update travis link 2019-08-24 19:14:08 -04:00
Nathan Marz
23f4e1a370
Update README.md 2019-01-29 14:25:15 -05:00
Nathan Marz
e38a2561d8
Merge pull request #275 from xificurC/master
use criterium for benchmarks
2018-12-13 21:23:03 -05:00
Peter Nagy (NPE)
fe1deb5195 use criterium for benchmarks, output JVM and clojure version 2018-12-13 12:54:28 +01:00
nathanmarz
7790213b16 update changelog 2018-12-03 14:30:28 -05:00
nathanmarz
8764a4b2b6 fix throw-illegal in cljs code 2018-12-03 14:29:58 -05:00
nathanmarz
c65b31181a update changelog 2018-11-01 21:40:57 -04:00
nathanmarz
f13b5db08a begin 1.1.3 2018-11-01 21:39:09 -04:00
nathanmarz
6c3253f16a 1.1.2 2018-11-01 21:38:17 -04:00
nathanmarz
e7abb2b538 fix handling of subvector paths in cljs 2018-06-26 08:39:28 -04:00
nathanmarz
3536e3c461 update class-constant-test for cljs 2018-06-12 10:48:56 -04:00
nathanmarz
350c8b857f Fix inline compiler symbol handling so class references can be used as constants within paths 2018-06-12 10:09:11 -04:00
nathanmarz
798cda211f eliminate reflection warning 2018-05-18 13:45:20 -04:00
nathanmarz
4580de8cc6 1.1.1 2018-04-23 11:28:23 -04:00
nathanmarz
79a3610a64 remove usage of pprint in cljs 2018-04-21 10:19:05 -04:00
nathanmarz
1590b2c2d4 update changelog 2018-04-09 14:57:45 -04:00
Nathan Marz
efe94b6539
Merge pull request #253 from gnl/master
Fix ns form to comply with cljs.core.specs.alpha
2018-04-09 14:55:46 -04:00
gnl
eea5fcb48b Fix ns form to comply with cljs.core.specs.alpha
When requiring the cljs.core.specs.alpha namespace with ClojureScript
1.10, the ns form in com.rpl.specter.navs fails spec validation and
produces a compile error, because the nested :clj reader conditionals
result in an empty :use clause.

Moving the reader conditional up to enclose :use fixes this.

Relevant spec:
b11cbeefa5/src/main/cljs/cljs/core/specs/alpha.cljc (L170)

Additional info:
https://clojurescript.org/news/2018-03-26-release#_core_specs_alpha
2018-04-07 16:54:11 +03:00
nathanmarz
ac48127871 fix 2018-03-23 18:24:01 -04:00
nathanmarz
01617b6264 fix build 2018-03-22 16:01:43 -04:00
nathanmarz
38a27ecc43 Merge branch 'master' of github.com:nathanmarz/specter 2018-03-22 15:44:11 -04:00
nathanmarz
d4887c2090 fix issue with walker caused by change in cljs 1.10 2018-03-22 15:43:53 -04:00
Nathan Marz
4a589c3074
add cheat sheet to readme 2018-02-13 09:09:38 -05:00
nathanmarz
1a05546f27 Merge branch 'master' of github.com:nathanmarz/specter 2018-02-08 22:27:06 -05:00
nathanmarz
d5143f136c update readme 2018-02-08 22:26:58 -05:00
Nathan Marz
f7b7b0cba2
Merge pull request #245 from DjebbZ/master
Add dates to all changelog entries
2018-02-08 10:53:44 -05:00
DjebbZ
05e354a9a2 Add dates to all changelog entries 2018-02-08 15:47:20 +01:00
nathanmarz
b4a3c4f601 begin 1.1.1 2018-01-02 09:30:22 -05:00
nathanmarz
c81a4b1a7b 1.1.0 2018-01-02 09:29:28 -05:00
nathanmarz
e92fd674b1 add examples 2017-12-24 18:39:01 -05:00
nathanmarz
c2f669db71 update example 2017-12-24 17:57:26 -05:00
nathanmarz
09d0d071ef improve compact implementation 2017-12-24 10:50:22 -05:00
nathanmarz
70d9fef5cc improve subselect fix 2017-12-21 23:28:00 -05:00
nathanmarz
b1050b910c improve compact test 2017-12-21 22:32:30 -05:00
nathanmarz
7b646ca566 fix subselect changing first matched element to nil when transformed to empty sequence 2017-12-21 22:24:37 -05:00
nathanmarz
6a5054feea add compact navigator 2017-12-21 13:15:22 -05:00
nathanmarz
632e710b07 make terminal and vterminal select codepath no-ops 2017-12-21 13:04:26 -05:00
nathanmarz
7bd119aa52 fix build 2017-12-04 10:47:42 -05:00
nathanmarz
0ceda21151 add vtransform 2017-12-04 10:06:43 -05:00
nathanmarz
9515582a19 add vterminal 2017-12-04 09:57:43 -05:00
nathanmarz
680c36ae5b update readme 2017-11-17 15:18:49 -05:00
nathanmarz
d54aa28d49 begin 1.0.6 2017-11-16 21:53:00 -05:00
nathanmarz
3478e5b6d7 1.0.5 2017-11-16 14:23:25 -05:00
nathanmarz
5b60eb17e3 extend ImplicitNav for strings, numbers, characters, booleans, symbols, and regexes 2017-11-08 13:47:40 -05:00
nathanmarz
349e03342f formatting 2017-10-22 12:23:25 -04:00
nathanmarz
844050545a update changelog 2017-10-20 11:06:25 -04:00
Nathan Marz
5efafd2d9b Merge pull request #231 from mwfogleman/regex
Regex navigator.
2017-10-20 11:05:15 -04:00
Michael Fogleman
98c7510d1c Remove implicit regex functionality. 2017-10-20 10:50:16 -04:00
Michael Fogleman
efaeff4fc5 Add regex test result. 2017-10-20 10:26:57 -04:00
Michael Fogleman
9a8f79774c Re-formatted test for consistent style. 2017-10-19 20:33:07 -04:00
Michael Fogleman
fdfaecd0d0 Expand regex-nav test-suite. 2017-10-19 19:08:44 -04:00
Michael Fogleman
4cf7ee965f Rename regex* to regex-nav and move to specter.cljc. 2017-10-19 12:42:14 -04:00
Michael Fogleman
f593753a68 Update CHANGES.md. 2017-10-19 11:29:41 -04:00
Michael Fogleman
0e88c57a87 Correctly refer to regex type in CLJS.
Before it thought the type was com.rpl.specter/RegExp - but it's
actually js/RegExp.
2017-10-19 11:25:42 -04:00
Michael Fogleman
14dad51fd4 Specify where NONE is in regex*. 2017-10-19 11:25:42 -04:00
Michael Fogleman
5aed3b254e Add regex-navigation-test. 2017-10-19 11:25:42 -04:00
Michael Fogleman
d7ee2f7c6a Move regex* location to a better spot.
...since it is no longer a "defrichnav."
2017-10-19 11:25:42 -04:00
Michael Fogleman
7dbe4dc524 Use defnav with regex* rather than defrichnav.
Nathan: vals is how collect / collect-one are implemented. You should
use defnav rather than defrichnav since it handles that parameter in
the background. defrichnav exists since the compiler doesn't do
certain inlining optimizations, so defrichnav allows the handling of
vals to be inlined manually. This is only important for navigators
which do very little work (like keypath) – I think regexes are
expensive enough that the inlining optimization won't be noticeable.
2017-10-19 11:25:42 -04:00
Michael Fogleman
dc9e2205c3 Wrap re-seq in doseqres.
This navigates to each match, not a sequence of matches.
2017-10-19 11:25:42 -04:00
Michael Fogleman
6e90ceadea Use re-seq rather than re-find. 2017-10-19 11:25:42 -04:00
Michael Fogleman
b5f840db22 Initial select implementation for Regexes. 2017-10-19 11:25:42 -04:00
nathanmarz
796ad700f7 begin 1.0.5 2017-10-17 12:14:52 -04:00
nathanmarz
3a6d8620d7 1.0.4 2017-10-17 12:13:35 -04:00
nathanmarz
92caabbb32 add screencast link 2017-10-07 14:39:49 -04:00
nathanmarz
49eed0079c update readme 2017-10-02 08:51:18 -04:00
nathanmarz
915c0a9471 Merge branch 'master' of github.com:nathanmarz/specter 2017-09-20 18:58:23 -04:00
nathanmarz
86033f3df1 link new wiki page 2017-09-20 18:58:17 -04:00
Nathan Marz
ac3fe713ac Merge pull request #230 from mwfogleman/space
Add a space to the nthpath documentation.
2017-09-11 16:14:46 -04:00
Michael Fogleman
efd123d17e Add a space to the nthpath documentation. 2017-09-11 10:21:42 -04:00
nathanmarz
c233fb7e9d add indexed-vals 2017-08-16 07:28:05 -04:00
nathanmarz
8d5f39a861 fix INDEXED-VALS 2017-08-15 01:32:43 -04:00
nathanmarz
09d00ac7e7 typo 2017-08-14 06:48:54 -04:00
nathanmarz
e2308e0cda begin 1.0.4 2017-08-14 06:30:44 -04:00
nathanmarz
31935fca2e 1.0.3 2017-08-14 06:25:24 -04:00
nathanmarz
85d3f14de9 fix docstring 2017-08-13 12:16:49 -04:00
nathanmarz
43fd7ab2e2 add INDEXED-VALS tests 2017-08-13 11:50:42 -04:00
nathanmarz
1805094df8 update changelog 2017-08-13 07:51:02 -04:00
nathanmarz
1b4a7d3d59 add INDEXED-VALS navigator 2017-08-13 07:49:08 -04:00
nathanmarz
1b7f987eaa fix outdated docstrings 2017-07-29 12:35:33 -04:00
nathanmarz
03e686cd9c tighten code 2017-07-24 13:02:19 -04:00
nathanmarz
dbae629472 cljs tests fix 2017-07-24 10:46:12 -04:00
nathanmarz
40c883c1f3 update changelog 2017-07-24 10:32:14 -04:00
nathanmarz
b7c62e444c add before-index and index-nav navigators 2017-07-24 10:29:11 -04:00
nathanmarz
179b705211 changes 2017-06-27 08:26:15 -04:00
nathanmarz
b18733b14e update changelog 2017-06-27 08:23:11 -04:00
nathanmarz
179d818503 fix regression in cljs that causes warning for record fields named var and other reserved words 2017-06-27 08:22:20 -04:00
nathanmarz
cfb191e1cd add note about value collection in subselect 2017-06-14 12:11:09 -04:00
nathanmarz
3fffee336e update .gitignore 2017-06-12 12:30:09 -04:00
nathanmarz
d36b4340c3 move repl.clj to scripts 2017-06-12 12:29:49 -04:00
nathanmarz
e8ba45e22b begin 1.0.3 2017-06-12 12:03:34 -04:00
nathanmarz
891a067bbd 1.0.2 2017-06-12 12:02:20 -04:00
nathanmarz
4325abab9d typo 2017-06-09 15:47:35 -04:00
Nathan Marz
abc5d2bf63 Merge pull request #213 from mwfogleman/cljs-documentation
Document ClojureScript usage.
2017-06-09 15:46:31 -04:00
Michael Fogleman
cfe5780ada Document ClojureScript usage. 2017-06-09 14:06:51 -04:00
nathanmarz
5259e5df55 add cljs repl script 2017-06-08 17:58:38 -04:00
nathanmarz
c2e3b779d9 typo 2017-06-07 17:54:56 -04:00
nathanmarz
7523e43823 improve doc for srange-dynamic 2017-06-07 17:17:13 -04:00
nathanmarz
3e4730b44f add ability to declare end-fn for srange-dynamic that takes in result of start-fn 2017-06-07 17:14:20 -04:00
nathanmarz
8f30918d59 add transducer cases to map vals benchmarks 2017-06-06 15:12:57 -04:00
nathanmarz
4b23bd9392 update changelog 2017-06-03 18:24:11 -04:00
nathanmarz
dbf3f60167 add walker vs. old clojure.walk version benchmark 2017-06-03 18:23:13 -04:00
nathanmarz
edf9d8c544 rename test 2017-06-03 06:11:23 -04:00
nathanmarz
0b88411f88 rename walker param to be consistent with doc 2017-06-02 13:46:47 -04:00
nathanmarz
b2589e00a6 fix handling of records with ALL in cljs 2017-06-02 10:17:35 -04:00
nathanmarz
b66db48a84 walker tests 2017-06-02 09:31:16 -04:00
nathanmarz
0841aa1587 add another string nav test 2017-06-01 06:18:56 -04:00
nathanmarz
d0ff9bbc35 reimplement codewalker so it works with NONE 2017-06-01 02:38:20 -04:00
nathanmarz
340d6d3065 reimplement walker in terms of recursive-path to support NONE removal 2017-06-01 01:38:12 -04:00
nathanmarz
e8f15c4137 extend ALL to records (for walker reimplementation) 2017-06-01 01:27:24 -04:00
nathanmarz
550d486954 update changelog 2017-05-31 21:01:31 -04:00
nathanmarz
5d185ec9f4 for ALL on maps, interpret transformed key value pair of < size 2 as removal 2017-05-31 21:00:23 -04:00
nathanmarz
1411ac495e add comments about workaround for cljs bug 2017-05-29 21:52:40 -04:00
nathanmarz
af029ac149 update changelog 2017-05-29 21:48:12 -04:00
nathanmarz
a924c75ac6 workaround to cljs bug where private vars in cljs.core can cause warnings with same named var (in this case, NONE) 2017-05-29 21:46:59 -04:00
nathanmarz
4f5376450a update changelog 2017-05-08 18:49:41 -04:00
nathanmarz
0608ca6396 fix bug with nested dynamic params with dynamic function invocations 2017-05-08 18:48:46 -04:00
nathanmarz
426873da98 support transforms to NONE for set-elem and map-key 2017-05-07 20:30:50 -04:00
nathanmarz
2dd6432c69 add set-elem navigator and change semantics of map-key to only navigate if the key exists (for consistency with set-elem) 2017-05-07 19:36:50 -04:00
nathanmarz
82314f50ba add test case for map-key 2017-05-05 18:43:20 -04:00
nathanmarz
2423127877 add map-key 2017-05-05 18:41:46 -04:00
nathanmarz
5dea8919be add helper pred navs for common comparisons 2017-04-24 20:35:41 -04:00
nathanmarz
af17c4617e begin 1.0.2 2017-04-17 16:22:35 -04:00
nathanmarz
d3010c0af8 1.0.1 2017-04-17 16:21:28 -04:00
nathanmarz
e25c400778 fix typo 2017-04-15 15:46:42 -04:00
nathanmarz
e3abd1ef9a better doc for filterer 2017-04-15 15:45:22 -04:00
nathanmarz
be30e46960 allow subselect/filterer to remove subvals 2017-04-13 10:20:24 -04:00
nathanmarz
ee56ddc1ab force ALL on lists to realize lazy sequence, fix benchmark 2017-04-11 11:58:52 -04:00
nathanmarz
a113946076 update changelog 2017-04-11 11:45:04 -04:00
nathanmarz
b18a249623 improve performance of ALL transform on lists, add benchmark 2017-04-11 11:44:39 -04:00
nathanmarz
a46ff5e8d9 fix tests for cljs 2017-04-03 14:11:23 -04:00
nathanmarz
c52f7671a7 make satisfies-protpath test clojure only 2017-04-03 14:03:10 -04:00
nathanmarz
c3164d722d add satisfies-protpath? 2017-04-03 13:46:08 -04:00
nathanmarz
0bb3ac8440 update cljs test instructions 2017-04-01 04:46:02 -04:00
nathanmarz
06d3ba548e fix nested dynamic arg issue for cljs 2017-04-01 04:45:50 -04:00
nathanmarz
6f6fa98680 update changelog 2017-04-01 04:29:18 -04:00
nathanmarz
b6ef861338 Fix #199 2017-04-01 04:27:57 -04:00
nathanmarz
3916c0dcf4 mark pred as direct-nav 2017-03-29 16:38:13 -04:00
nathanmarz
37441c149d update changelog 2017-03-22 13:31:28 -04:00
nathanmarz
fedef396eb change dynamic navs to return single navigator instead of sequence of one nav, helps inline compiler when a nav takes a parameterized navs as input 2017-03-22 13:30:35 -04:00
nathanmarz
468dcb05f9 update changelog 2017-03-15 11:53:16 -04:00
nathanmarz
70f41a635a make inline cache vars private 2017-03-15 11:52:35 -04:00
nathanmarz
e32ec1c687 remove dead code 2017-03-14 12:21:18 -04:00
Nathan Marz
3be05f2f0a Merge pull request #192 from firesofmay/switch-to-nodejs
Switch to node-js build engine for cljs tests
2017-03-11 14:09:12 -05:00
Mayank Jain
10e80e47ca Switch to node-js build engine for cljs tests 2017-03-11 23:02:12 +05:30
nathanmarz
4f56c6e5e9 revert previous travis fix attempt 2017-03-11 12:24:56 -05:00
nathanmarz
3cdaf2b358 try forcing v2.0 of phantomjs in build 2017-03-11 12:12:46 -05:00
nathanmarz
f426b0db61 disable travis cljs build for now 2017-03-11 08:36:18 -05:00
nathanmarz
cbc6520dbf update cljs testing instructions 2017-03-10 17:09:21 -05:00
Nathan Marz
4fb76c01c3 Merge pull request #189 from firesofmay/add-cljs-to-travis
Add cljs tests to travis
2017-03-10 16:49:07 -05:00
Mayank Jain
4695c96998 Add cljs tests to travis
* Add lein-doo plugin to run cljs tests. This is the new recommended
  approach.
* Update project.clj to add cljsbuild config
* Update .travis.yml file to run cljs tests as well.
* Also to speed up travis builds add ~/.m2 dir in cache

To run do:
$ lein do javac, doo phantom test-build once

Note that you'll need phantomjs installed for above to work.
2017-03-11 02:31:10 +05:30
nathanmarz
6b080a2575 update test.check to 0.9.0 2017-03-10 12:32:02 -05:00
nathanmarz
acf2801cbc update readme 2017-03-01 16:07:21 -05:00
nathanmarz
8afa602a40 update readme 2017-03-01 12:45:23 -05:00
nathanmarz
f4d021f87f update changelog 2017-03-01 12:40:56 -05:00
nathanmarz
b069265866 begin 1.0.1 2017-03-01 12:40:33 -05:00
nathanmarz
b5d65fda55 1.0.0 2017-03-01 12:33:18 -05:00
nathanmarz
35c8a9380e Add with-fresh-collected docstring 2017-03-01 10:46:28 -05:00
nathanmarz
b13b8d3c35 add remove with NONE functionality to FIRST and LAST 2017-03-01 10:40:17 -05:00
nathanmarz
5e56a99163 add more benchmark cases 2017-02-28 16:23:10 -05:00
nathanmarz
f169813b2d update changelog 2017-02-28 15:55:54 -05:00
nathanmarz
2504b7849a add specialized MAP-KEYS navigator 2017-02-28 15:55:25 -05:00
nathanmarz
cdbbd13939 add benchmark for prepending to a vector 2017-02-21 11:18:32 -05:00
nathanmarz
e7b595c4ca fix issue with NONE-ELEM on nil value 2017-02-17 17:54:11 -05:00
nathanmarz
84fdc7f1b4 update readme 2017-02-17 12:50:05 -05:00
nathanmarz
6bb5f5cab4 update changelog 2017-02-17 12:08:32 -05:00
nathanmarz
ef5ad1de6d fix transforms on subvectors to maintain the type as a vector type 2017-02-17 12:05:12 -05:00
nathanmarz
d8cfb649ad clarify changelog 2017-02-16 15:35:20 -05:00
nathanmarz
036144fdaf update changelog 2017-02-15 23:08:49 -05:00
nathanmarz
b79a71decd Add BEFORE-ELEM, AFTER-ELEM, and NONE-ELEM navigators 2017-02-15 20:34:44 -05:00
nathanmarz
7c798c1e3b extend srange, BEGINNING, END, FIRST, and LAST to strings 2017-02-15 18:30:44 -05:00
nathanmarz
ffcba01df7 fix NAMESPACE select 2017-02-15 10:12:08 -05:00
nathanmarz
d595b9f26b change nthpath to richnav for performance 2017-02-14 14:33:46 -05:00
nathanmarz
71ed0ffae9 Add docstrings for keypath, must, and nthpath 2017-02-14 10:46:02 -05:00
nathanmarz
16310d6008 fix docstring 2017-02-14 10:42:54 -05:00
nathanmarz
0855af39e7 update changelog 2017-02-14 08:48:36 -05:00
nathanmarz
48efea55ab Added NAME and NAMESPACE navigators 2017-02-14 08:47:19 -05:00
nathanmarz
3dbc775334 fix if-path/selected?/not-selected? so that vals are passed along to condition paths 2017-02-14 08:25:54 -05:00
nathanmarz
baf658365e add test for select-any with value collection 2017-02-14 08:11:39 -05:00
nathanmarz
646e03a227 Add nthpath navigator 2017-02-12 21:13:08 -05:00
nathanmarz
ebdfc80d8b update changelog 2017-02-12 21:03:16 -05:00
nathanmarz
3f71163454 extend ALL to PersistentHashSet 2017-02-12 21:02:38 -05:00
nathanmarz
e2b6f63f99 fix traverse with early termination to unwrap reduced vals 2017-02-12 15:24:13 -05:00
nathanmarz
76682e64d4 update README 2017-02-12 12:22:05 -05:00
nathanmarz
6d154799f9 add benchmark for getting first element of a vector 2017-02-12 12:13:13 -05:00
nathanmarz
3f3fad0eb7 update changelog 2017-02-12 11:44:36 -05:00
nathanmarz
41ac7790e2 Update changelog 2017-02-12 11:39:41 -05:00
nathanmarz
28ecb90489 add docstrings 2017-02-12 11:38:45 -05:00
23 changed files with 1839 additions and 433 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

3
.gitignore vendored
View file

@ -9,4 +9,7 @@ pom.xml.asc
.lein-repl-history
.lein-plugins/
.lein-failures
.cljs_node_repl
out/
.cpcache
.cache

View file

@ -1,3 +0,0 @@
language: clojure
script:
- lein test

View file

@ -1,17 +1,103 @@
## 0.13.3-SNAPSHOT
## 1.1.4
* Transform to `com.rpl.specter/NONE` to remove elements from data structures. Works with `keypath` (for both sequences and maps), `must`, `ALL`, and `MAP-VALS`
* 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
## 1.1.2 - 2018-11-01
* Eliminate reflection warning
* Bug fix: Fix inline compiler symbol handling so class references can be used as constants within paths
* Bug fix: Fix handling of subvector paths in cljs
## 1.1.1 - 2018-04-23
* ClojureScript 1.10 introduced a change causing the `walker` navigator to fail to walk records. `ALL` has been updated to operate over `MapEntry` in ClojureScript, fixing the issue.
* Change ns form to comply with cljs.core.specs.alpha (thanks @gnl)
* Remove usage of pprint in cljs so advanced optimization doesn't include it
## 1.1.0 - 2018-01-02
* Add `vtransform` variant of `transform` that takes in collected values as a vector in the first argument rather than spliced into argument list.
* Add `vterminal` that takes in collected vals as vector in first argument rather than spliced into argument list.
* Add `compact` navigator. After each step of navigation of its subpath, `compact` removes the collection if it's empty.
* Change `terminal` to be a no-op on select codepath
* Bug fix: `subselect`/`filterer` removes first matched element instead of setting to nil when transformed to empty sequence
## 1.0.5 - 2017-11-16
* Add `regex-nav` navigator for regexes, which navigates to every match in a string and supports replacement with a new substring (thanks @mwfogleman)
* Regexes implicitly convert to `regex-nav` in paths
* Strings, numbers, booleans, characters, and symbols implicitly convert to `keypath` in paths
## 1.0.4 - 2017-10-17
* Add `indexed-vals` navigator, a variant of `INDEXED-VALS` that allows for a customized start index.
* Bug fix: Fix `INDEXED-VALS` invalidly overwriting elements in some transforms involving multiple index changes
## 1.0.3 - 2017-08-14
* Added `before-index` navigator for inserting a single element into a sequence.
* Added `index-nav` navigator for moving an element in a sequence to a new index, shifting other elements in the process.
* Added `INDEXED-VALS` navigator for navigating to every element of a sequence as [index elem] pair. Transform on index portion works the same as `index-nav`.
* Workaround for ClojureScript regression that causes warnings for record fields named "var" or other reserved names
## 1.0.2 - 2017-06-12
* 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 - 2017-04-17
* `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 - 2017-03-01
* 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
* Add `with-fresh-collected` higher order navigator
* Added `traverse-all` which returns a transducer that traverses over all elements matching the given path.
* `select-first` and `select-any` now avoid traversal beyond the first value matched by the path (like when using `ALL`), so they are faster now for those use cases.
* Add `MAP-KEYS` navigator that's more efficient than `[ALL FIRST]`
* Add `NAME` and `NAMESPACE` navigators
* Extend `srange`, `BEGINNING`, `END` to work on strings. Navigates to a substring.
* Extend `FIRST` and `LAST` to work on strings. Navigates to a character.
* Add `BEFORE-ELEM` and `AFTER-ELEM` for prepending or appending a single element to a sequence
* Add `NONE-ELEM` to efficiently add a single element to a set
* Improved `ALL` performance for PersistentHashSet
* Dynamic navs automatically compile sequence returns if completely static
* Eliminate reflection warnings for clj (thanks @mpenet)
* Add `with-fresh-collected` higher order navigator
* Bug fix: Collected vals now properly passed to subpaths for `if-path`, `selected?`, and `not-selected?`
* Bug fix: `LAST`, `FIRST`, `BEGINNING`, and `END` properly transform subvector types to a vector type
## 0.13.2
## 0.13.2 - 2016-12-21
* Bug fix: Fix race condition relating to retrieving path from cache and AOT compilation
* Bug fix: LAST no longer converts lists to vectors
* Bug fix: Workaround issue with aot + uberjar
## 0.13.1
## 0.13.1 - 2016-11-07
* Remove any? in com.rpl.specter.impl to avoid conflict with Clojure 1.9
* Enhanced dynamic navigators to continue expanding if any other dynamic navs are returned
@ -22,7 +108,7 @@
* Bug fix: Fix problems with multi-path and if-path in latest versions of ClojureScript
* Bug fix: Inline compiler no longer flattens and changes the type of sequential params
## 0.13.0
## 0.13.0 - 2016-09-06
* BREAKING CHANGE: `com.rpl.specter.macros` namespace removed and all macros moved into core `com.rpl.specter` namespace
* BREAKING CHANGE: Core protocol `Navigator` changed to `RichNavigator` and functions now have an extra argument.
@ -56,7 +142,7 @@
* Bug fix: ALL and MAP-VALS transforms on PersistentArrayMap above the threshold now output PersistentArrayMap instead of PersistentHashMap
## 0.12.0
## 0.12.0 - 2016-08-05
* BREAKING CHANGE: Changed semantics of `Navigator` protocol `select*` in order to enable very large performance improvements to `select`, `select-one`, `select-first`, and `select-one!`. Custom navigators will need to be updated to conform to the new required semantics. Codebases that do not use custom navigators do not require any changes. See the docstring on the protocol for the details.
* Added `select-any` operation which selects a single element navigated to by the path. Which element returned is undefined. If no elements are navigated to, returns `com.rpl.specter/NONE`. This is the fastest selection operation.
@ -78,11 +164,13 @@ transformations with `transform` one after another when the transformations shar
* Bug fix: Using value collection along with `setval` no longer throws exception
* Bug fix: Fix error when trying to use Specter along with AOT compilation
## 0.11.2
## 0.11.2 - 2016-06-09
* Renamed com.rpl.specter.transient namespace to com.rpl.specter.transients to eliminate ClojureScript compiler warning about reserved keyword
* Eliminated compiler warnings for ClojureScript version
## 0.11.1
## 0.11.1 - 2016-06-08
* More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases
* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aengelberg)
* Huge performance improvement for ALL transform on maps and vectors
@ -96,7 +184,8 @@ transformations with `transform` one after another when the transformations shar
* Bug fix: Fixed nil->val to not replace the val on `false`
* Bug fix: Eliminate reflection when using primitive parameters in an inline cached path
## 0.11.0
## 0.11.0 - 2016-05-31
* New `path` macro does intelligent inline caching of the provided path. The path is factored into a static portion and into params which may change on each usage of the path (e.g. local parameters). The static part is factored and compiled on the first run-through, and then re-used for all subsequent invocations. As an example, `[ALL (keypath k)]` is factored into `[ALL keypath]`, which is compiled and cached, and `[k]`, which is provided on each execution. If it is not possible to precompile the path (e.g. [ALL some-local-variable]), nothing is cached and the path will be compiled on each run-through.
* BREAKING CHANGE: all `select/transform/setval/replace-in` functions changed to macros and moved to com.rpl.specter.macros namespace. The new macros now automatically wrap the provided path in `path` to enable inline caching. Expect up to a 100x performance improvement without using explicit precompilation, and to be within 2% to 15% of the performance of explicitly precompiled usage.
* Added `select*/transform*/setval*/replace-in*/etc.` functions that have the same functionality as the old `select/transform/setval/replace-in` functions.
@ -110,7 +199,8 @@ transformations with `transform` one after another when the transformations shar
* Added "navigator constructors" that can be defined via `defnavconstructor`. These allow defining a flexible function to parameterize a defnav, and the function integrates with inline caching for high performance.
## 0.10.0
## 0.10.0 - 2016-04-26
* Make codebase bootstrap cljs compatible
* Remove usage of reducers in cljs version in favor of transducers (thanks @StephenRudolph)
* ALL now maintains type of queues (thanks @StephenRudolph)
@ -120,7 +210,8 @@ transformations with `transform` one after another when the transformations shar
* Fix filterer to maintain the type of the input sequence in transforms
* Integrated zipper navigation into com.rpl.specter.zipper namespace
## 0.9.3
## 0.9.3 - 2016-04-15
* Change clojure/clojurescript to provided dependencies
* ALL on maps auto-coerces MapEntry to vector, enabling smoother transformation of map keys
* declarepath can now be parameterized
@ -128,7 +219,8 @@ transformations with `transform` one after another when the transformations shar
* Added convenience syntax for defprotocolpath with no params, e.g. (defprotocolpath foo)
* Rename VOID to STOP
## 0.9.2
## 0.9.2 - 2016-01-26
* Added VOID selector which navigates nowhere
* Better syntax checking for defpath
* Fixed bug in protocol paths (#48)
@ -139,33 +231,39 @@ transformations with `transform` one after another when the transformations shar
* Added declarepath and providepath, which enable arbitrary recursive or mutually recursive paths
* Renamed paramspath to path
## 0.9.1
## 0.9.1 - 2016-01-05
* Fixed reflection in protocol path code
* Optimized late-bound parameterization for JVM implementation by directly creating the object array rather than use object-array
* Incorrectly specified function names in defpath will now throw error
## 0.9.0
## 0.9.0 - 2015-12-12
* Fixed bug where comp-paths wouldn't work on lazy seqs in cljs
* Renamed defparamspath and defparamscollector to defpath and defcollector
* For Clojure version only, implemented protocol paths (see #38)
## 0.8.0
## 0.8.0 - 2015-10-10
* Now compatible with Clojure 1.6.0 and 1.5.1 by switching build to cljx (thanks @MerelyAPseudonym)
* Added subset selector (like srange but for sets)
* Added nil->val, NIL->SET, NIL->LIST, and NIL->VECTOR selectors to make it easier to manipulate maps (e.g. (setval [:akey NIL->VECTOR END] [:a :b] amap) to append that vector into that value for the map, even if nothing was at that value at the start)
## 0.7.1
## 0.7.1 - 2015-09-24
* view can now be late-bound parameterized
* Added a late-bound parameterized version of using a function as a selector called "pred"
* Added paramsfn helper macro for defining filter functions that take late-bound parameters
* walker and codewalker can now be late-bound parameterized
## 0.7.0
## 0.7.0 - 2015-09-11
* Added late-bound parameterization feature: allows selectors that require params to be precompiled without the parameters, and the parameters are supplied later in bulk. This effectively enables Specter to be used in any situation with very high performance.
* Converted Specter built-in selectors to use late-bound parameterization when appropriate
* ALL, FIRST, and LAST are now precompiled
## 0.6.2
## 0.6.2 - 2015-07-03
* Added not-selected? selector
* Added transformed selector
* Sped up CLJS implementation for comp-paths by replacing obj-extends? call with satisfies?
@ -173,33 +271,42 @@ transformations with `transform` one after another when the transformations shar
* Used not-native hint to enable direct method invocation to speed up CLJS implementation
## 0.6.1
## 0.6.1 - 2015-07-01
* Huge speedup to ClojureScript implementation by optimizing field access
## 0.6.0
## 0.6.0 - 2015-07-01
* Added ClojureScript compatibility
## 0.5.7
## 0.5.7 - 2015-06-30
* Fix bug in select-one! which wouldn't allow nil result
## 0.5.6
## 0.5.6 - 2015-06-29
* Add multi-path implementation
* change FIRST/LAST to select nothing on an empty sequence
* Allow sets to be used directly as selectors (acts as filter)
## 0.5.5
## 0.5.5 - 2015-06-22
* Change filterer to accept a selector (that acts like selected? to determine whether or not to select value)
## 0.5.4
## 0.5.4 - 2015-06-19
* Change cond-path and if-path to take in a selector for conditionals (same idea as selected?)
## 0.5.3
## 0.5.3 - 2015-06-18
* Added cond-path and if-path selectors for choosing paths depending on value of structure at that location
## 0.5.2
## 0.5.2 - 2015-06-01
* Fix error for selectors with one element defined using comp-paths, e.g. [:a (comp-paths :b)]
## 0.5.1
## 0.5.1 - 2015-05-31
* Added putval for adding external values to collected values list
* nil is now interpreted as identity selector
* empty selector is now interpreted as identity selector instead of producing error

9
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,9 @@
# Contributing
We welcome external contributions to this project. For ideas for new functionality, we recommend first opening an issue so we can discuss whether it makes sense for the project.
## Contribution process
1. Open a pull request. If possible, please include thorough tests of the new code.
2. If you have not already signed a contributor agreement, we will request that you sign one. We use Adobe Sign for this so it's very quick and easy. Note that we cannot look at your pull request until a contributor agreement is signed.
3. We will then review your pull request and possibly ask for changes.

View file

@ -1,14 +1,25 @@
# Running Clojure tests
```
lein cleantest
lein do clean, test
```
# 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
```
# 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,8 +1,12 @@
# Specter [![Build Status](https://secure.travis-ci.org/nathanmarz/specter.png?branch=master)](http://travis-ci.org/nathanmarz/specter)
# Specter
Clojure has fantastic facilities for doing immutable programming, with a rich library of persistent data structures and efficient mechanisms for manipulating and traversing them. However, Clojure's story is incomplete. Once you nest data structures which is extremely common Clojure becomes cumbersome and complex. Clojure even lacks a facility for a basic task like transforming every value in a generic sequence without changing the type or order of that sequence.
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.
Specter, available for both Clojure and ClojureScript, provides a high performance abstraction called navigators which complete the story around immutable programming and make it easy to transform and query nested data structures. It allows you to concisely specify what you want to change within a data structure, and get a new data structure back with only your changes applied  everything else is reconstructed and the types of data structures throughout don't unexpectedly change.
Specter has an extremely simple core, just a single abstraction called "navigator". Queries and transforms are done by composing navigators into a "path" precisely targeting what you want to retrieve or change. Navigators can be composed with any other navigators, allowing sophisticated manipulations to be expressed very concisely.
In addition, Specter has performance rivaling hand-optimized code (see [this benchmark](https://gist.github.com/nathanmarz/b7c612b417647db80b9eaab618ff8d83)). Clojure's only comparable built-in operations are `get-in` and `update-in`, and the Specter equivalents are 30% and 85% faster respectively (while being just as concise). Under the hood, Specter uses [advanced dynamic techniques](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition.
There are some key differences between the Clojure approach to data manipulation and the Specter approach. Unlike Clojure, Specter always uses the most efficient method possible to implement an operation for a datatype (e.g. `last` vs. `LAST`). Clojure intentionally leaves out many operations, such as prepending to a vector or inserting into the middle of a sequence. Specter has navigators that cover these use cases (`BEFORE-ELEM` and `before-index`) and many more. Finally, Specter transforms always target precise parts of a data structure, leaving everything else the same. For instance, `ALL` targets every value within a sequence, and the resulting transform is always the same type as the input (e.g. a vector stays a vector, a sorted map stays a sorted map).
Consider these examples:
@ -15,7 +19,7 @@ Consider these examples:
;; Manual Clojure
(defn map-vals [m afn]
(->> m (map (fn [[k v]] [k (afn v)])) (into {})))
(->> m (map (fn [[k v]] [k (afn v)])) (into (empty m))))
(map-vals data
(fn [v]
@ -67,10 +71,6 @@ Consider these examples:
```
Specter has performance rivaling hand-optimized code (see [this benchmark](https://gist.github.com/nathanmarz/b7c612b417647db80b9eaab618ff8d83)). Clojure's only comparable built-in operations are `get-in` and `update-in`, and the Specter equivalents are 30% and 85% faster respectively (while being just as concise). Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition. Additionally, the built-in navigators use the most efficient means possible of accessing data structures. For example, `ALL` uses `mapv` on vectors, the `IMapIterable` interface on small maps, and `reduce-kv` in conjunction with transients on larger maps.
The most important aspect of Specter is its composability. Specter navigators can be composed with any other navigators, so the supported use cases grow combinatorially. And because Specter is completely extensible, it can be used to navigate any data structure or object you have.
# Latest Version
@ -80,23 +80,27 @@ 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)
- 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.
- Core operations and defining new navigators: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
- [API docs](http://nathanmarz.github.io/specter/)
- Performance guide: [This post](https://github.com/nathanmarz/specter/wiki/Specter's-inline-caching-implementation) provides an overview of how Specter achieves its performance.
- 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.
- Screencast on Specter: [Understanding Specter](https://www.youtube.com/watch?v=rh5J4vacG98)
- List of navigators with examples: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
- Core operations and defining new navigators: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-Recursively) explains how to do precise and efficient recursive navigation with Specter.
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-With-Zippers) provides a comprehensive overview of how to use Specter's zipper navigators. Zippers are a much slower navigation method but can perform certain tasks that are not possible with Specter's regular navigators. Note that zippers are rarely needed.
- [Cheat Sheet](https://github.com/redplanetlabs/specter/wiki/Cheat-Sheet)
- [API docs](http://redplanetlabs.github.io/specter/)
- Performance guide: [This post](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) provides an overview of how Specter achieves its performance.
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.`
- [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.
- [specter.cljc](https://github.com/redplanetlabs/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/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
- [zipper.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
# Questions?
You can ask questions about Specter by [opening an issue](https://github.com/nathanmarz/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
You can ask questions about Specter by [opening an issue](https://github.com/redplanetlabs/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
You can also find help in the #specter channel on [Clojurians](http://clojurians.net/).
@ -143,6 +147,20 @@ user> (setval [:a ALL nil?] NONE {:a [1 2 nil 3 nil]})
{:a [1 2 3]}
```
Remove key/value pair from nested map:
```clojure
user> (setval [:a :b :c] NONE {:a {:b {:c 1}}})
{:a {:b {}}}
```
Remove key/value pair from nested map, removing maps that become empty along the way:
```clojure
user> (setval [:a (compact :b :c)] NONE {:a {:b {:c 1}}})
{}
```
Increment all the odd numbers between indices 1 (inclusive) and 4 (exclusive):
```clojure
@ -172,10 +190,10 @@ user> (select (walker number?)
[2 1 2 1 2 6 7 4]
```
Navigate via non-keyword keys:
Navigate with string keys:
```clojure
user> (select (keypath "a" "b")
user> (select ["a" "b"]
{"a" {"b" 10}})
[10]
```
@ -192,7 +210,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] []])
@ -292,11 +310,34 @@ 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
- Any connection to transducers?
# 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-2017 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 @@
0.13.3-SNAPSHOT
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,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.10.7"]
[lein-doo "0.1.7"]]
:codox {:source-paths ["target/classes" "src/clj"]
:namespaces [com.rpl.specter
com.rpl.specter.zipper
@ -20,11 +21,23 @@
#".*" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}#L{line}"}}
:profiles {:dev {:dependencies
[[org.clojure/test.check "0.7.0"]
[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.229"]]}
: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.9.0"]
[org.clojure/clojure "1.9.0"]
[org.clojure/clojurescript "1.10.439"]]}
:bench {:dependencies [[org.clojure/clojure "1.9.0"]
[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

@ -1,61 +1,35 @@
(ns com.rpl.specter.benchmarks
(:use [com.rpl.specter]
[com.rpl.specter.transients]
[com.rpl.specter.impl :only [benchmark]])
(:require [clojure.walk :as walk]))
;; run via `lein repl` with `(load-file "scripts/benchmarks.clj")`
(defn pretty-float5 [anum]
(format "%.5g" anum))
[com.rpl.specter.transients])
(:require [clojure.walk :as walk]
[com.rpl.specter.impl :as i]
[criterium.core :as bench]))
(defn pretty-float3 [anum]
(format "%.3g" anum))
(defn time-ms [amt afn]
(let [start (System/nanoTime)
_ (dotimes [_ amt] (afn))
end (System/nanoTime)]
(/ (- end start) 1000000.0)))
(defn mean [a-fn]
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
(defn avg [numbers]
(/ (reduce + numbers)
(count numbers)
1.0))
(defn average-time-ms [iters amt-per-iter afn]
(avg
;; treat 1st run as warmup
(next
(for [i (range (inc iters))]
(time-ms amt-per-iter afn)))))
(defn compare-benchmark [amt-per-iter afn-map]
(System/runFinalization)
(System/gc)
(let [results (transform MAP-VALS
(fn [afn]
(average-time-ms 8 amt-per-iter afn))
afn-map)
(defn compare-benchmark [afn-map]
(let [results (transform MAP-VALS mean afn-map)
[[_ best-time] & _ :as sorted] (sort-by last results)]
(println "\nAvg(ms)\t\tvs best\t\tCode")
(println "\nMean(us)\tvs best\t\tCode")
(doseq [[k t] sorted]
(println (pretty-float5 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
(defmacro run-benchmark [name amt-per-iter & exprs]
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
`(do
(println "Benchmark:" ~name (str "(" ~amt-per-iter " iterations)"))
(compare-benchmark ~amt-per-iter ~afn-map)
(println "\n********************************\n"))))
(defmacro run-benchmark [name & exprs]
(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) (keypath b) (keypath c)] data))
(select-any (keypath a b c) data))
(defn get-k [k] (fn [m next] (next (get m k))))
@ -67,10 +41,9 @@
(afn structure curr)))
[identity (get-k :c) (get-k :b) (get-k :a)]))
(let [data {:a {:b {:c 1}}}
p (comp-paths :a :b :c)]
(run-benchmark "get value in nested map" 2500000
(run-benchmark "get value in nested map"
(select-any [:a :b :c] data)
(select-any (keypath :a :b :c) data)
(select-one [:a :b :c] data)
@ -87,7 +60,7 @@
(let [data {:a {:b {:c 1}}}]
(run-benchmark "set value in nested map" 2500000
(run-benchmark "set value in nested map"
(assoc-in data [:a :b :c] 1)
(setval [:a :b :c] 1 data)))
@ -104,7 +77,7 @@
(my-update m3 :c afn))))))
(let [data {:a {:b {:c 1}}}]
(run-benchmark "update value in nested map" 500000
(run-benchmark "update value in nested map"
(update-in data [:a :b :c] inc)
(transform [:a :b :c] inc data)
(manual-transform data inc)))
@ -135,8 +108,15 @@
ret)))))
(let [data '(1 2 3 4 5)]
(run-benchmark "transform values of a list"
(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
(run-benchmark "transform values of a small map"
(into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
@ -145,12 +125,14 @@
(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 {}))]
(run-benchmark "transform values of large map" 600
(run-benchmark "transform values of large map"
(into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
@ -160,12 +142,21 @@
(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)))
(let [data [1 2 3 4 5 6 7 8 9 10]]
(run-benchmark "first value of a size 10 vector"
(first data)
(select-any ALL data)
(select-any FIRST data)
(select-first ALL data)
))
(let [data [1 2 3 4 5]]
(run-benchmark "map a function over a vector" 1000000
(run-benchmark "map a function over a vector"
(vec (map inc data))
(mapv inc data)
(transform ALL inc data)
@ -173,7 +164,7 @@
(let [data [1 2 3 4 5 6 7 8 9 10]]
(run-benchmark "filter a sequence" 500000
(run-benchmark "filter a sequence"
(doall (filter even? data))
(filterv even? data)
(select [ALL even?] data)
@ -183,7 +174,7 @@
(let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}]
xf (comp (map :a) (filter even?))]
(run-benchmark "even :a values from sequence of maps" 500000
(run-benchmark "even :a values from sequence of maps"
(select [ALL :a even?] data)
(->> data (mapv :a) (filter even?) doall)
(into [] (comp (map :a) (filter even?)) data)
@ -191,12 +182,18 @@
(let [v (vec (range 1000))]
(run-benchmark "END on large vector"
2000000
(run-benchmark "Append to a large vector"
(setval END [1] v)
(setval AFTER-ELEM 1 v)
(reduce conj v [1])
(conj v 1)))
(let [data [1 2 3 4 5 6 7 8 9 10]]
(run-benchmark "prepend to a vector"
(vec (cons 0 data))
(setval BEFORE-ELEM 0 data)
(into [0] data)
))
(declarepath TreeValues)
@ -220,7 +217,6 @@
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
(run-benchmark "update every value in a tree (represented with vectors)"
50000
(walk/postwalk (fn [e] (if (and (number? e) (even? e)) (inc e) e)) data)
(transform [(walker number?) even?] inc data)
(transform [TreeValues even?] inc data)
@ -230,7 +226,6 @@
(let [toappend (range 1000)]
(run-benchmark "transient comparison: building up vectors"
8000
(reduce (fn [v i] (conj v i)) [] toappend)
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
(setval END toappend [])
@ -238,7 +233,6 @@
(let [toappend (range 1000)]
(run-benchmark "transient comparison: building up vectors one at a time"
7000
(reduce (fn [v i] (conj v i)) [] toappend)
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
(reduce (fn [v i] (setval END [i] v)) [] toappend)
@ -249,7 +243,6 @@
tdata (transient data)
tdata2 (transient data)]
(run-benchmark "transient comparison: assoc'ing in vectors"
2500000
(assoc data 600 0)
(assoc! tdata 600 0)
(setval (keypath 600) 0 data)
@ -260,7 +253,6 @@
tdata (transient data)
tdata2 (transient data)]
(run-benchmark "transient comparison: assoc'ing in maps"
1500000
(assoc data 600 0)
(assoc! tdata 600 0)
(setval (keypath 600) 0 data)
@ -274,31 +266,27 @@
[k (rand)]))
tdata (transient data)]
(run-benchmark "transient comparison: submap"
150000
(transform (submap [600 700]) modify-submap data)
(transform (submap! [600 700]) modify-submap tdata)))
(let [data {:x 1}
meta-map {:my :metadata}]
(run-benchmark "set metadata"
1500000
(with-meta data meta-map)
(setval META meta-map data)))
(let [data (with-meta {:x 1} {:my :metadata})]
(run-benchmark "get metadata"
15000000
(meta data)
(select-any META data)))
(let [data (with-meta {:x 1} {:my :metadata})]
(run-benchmark "vary metadata"
800000
(vary-meta data assoc :y 2)
(setval [META :y] 2 data)))
(let [data (range 1000)]
(run-benchmark "Traverse into a set" 5000
(run-benchmark "Traverse into a set"
(set data)
(set (select ALL data))
(into #{} (traverse ALL data))
@ -310,12 +298,60 @@
(defn mult-10 [v] (* 10 v))
(let [data [1 2 3 4 5 6 7 8 9]]
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav" 300000
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav"
(->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
(let [data [[1 2 3 4 :a] [5] [6 7 :b 8 9] [10 11 12 13]]]
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs" 150000
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs"
(->> data (transform [ALL ALL number? even?] mult-10) (transform [ALL ALL number? odd?] dec))
(multi-transform [ALL ALL number? (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
(let [data {:a 1 :b 2 :c 3 :d 4}]
(run-benchmark "namespace qualify keys of a small map"
(into {}
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
data)
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
))
(let [data (->> (for [i (range 1000)] [(keyword (str i)) i]) (into {}))]
(run-benchmark "namespace qualify keys of a large map"
(into {}
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
data)
(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"
(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)))

4
scripts/cljs-repl.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
rlwrap java -cp `lein classpath` clojure.main scripts/repl.clj

View file

@ -1,4 +1,8 @@
#!/bin/bash
`lein javac`
java -server -XX:MaxPermSize=128m -XX:MaxInlineSize=100 -cp `lein classpath` clojure.main scripts/benchmarks.clj
lein javac
lein version
echo
lein show-profiles bench
echo
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj "$@"

View file

@ -10,10 +10,19 @@
defdynamicnav
dynamicnav
richnav
defrichnav]]
defrichnav
recursive-path
select
transform
setval
select-any]]
[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 +41,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 +83,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))
@ -101,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
@ -112,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))
@ -152,7 +202,10 @@
embed (i/maybe-direct-nav path (-> s meta :direct-nav))]
`(com.rpl.specter.impl/->LocalSym ~path (quote ~embed)))
;; var-get doesn't work in cljs, so capture the val in the macro instead
`(com.rpl.specter.impl/->VarUse ~path (var ~path) (quote ~path)))
`(com.rpl.specter.impl/->VarUse
~path
~(if-not (instance? Class (resolve path)) `(var ~path))
(quote ~path)))
(i/fn-invocation? path)
@ -186,7 +239,7 @@
[e]
(sequential? e)
(ic-possible-params e)))
(concat (if (vector? e) [e]) (ic-possible-params e))))
path)))
@ -238,7 +291,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")
@ -335,10 +388,16 @@
[apath transform-fn structure]
`(i/compiled-transform* (path ~apath) ~transform-fn ~structure))
(defmacro vtransform
"Navigates to each value specified by the path and replaces it by the result of running
the transform-fn on two arguments: the collected values as a vector, and the navigated value."
[apath transform-fn structure]
`(i/compiled-vtransform* (path ~apath) ~transform-fn ~structure))
(defmacro multi-transform
"Just like `transform` but expects transform functions to be specified
inline in the path using `terminal`. Error is thrown if navigation finishes
at a non-`terminal` navigator. `terminal-val` is a wrapper around `terminal` and is
inline in the path using `terminal` or `vterminal`. Error is thrown if navigation finishes
at a non-terminal navigator. `terminal-val` is a wrapper around `terminal` and is
the `multi-transform` equivalent of `setval`.
This macro will do inline caching of the path."
[apath structure]
@ -425,12 +484,16 @@
(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)]
(doseq [[atype path-code] extensions]
(extend atype protpath-prot
{m (eval `(fn ~params (path ~path-code)))}))))
{m (binding [*compile-files* false]
(eval `(fn ~params (path ~path-code))))}))))
(defmacro extend-protocolpath
"Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]."
@ -439,7 +502,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)))
))
@ -517,15 +585,23 @@
[apath structure]
(compiled-traverse (i/comp-paths* apath) structure))
(def compiled-traverse-all i/compiled-traverse-all*)
(def ^{:doc "Version of traverse-all that takes in a path precompiled with comp-paths"}
compiled-traverse-all i/compiled-traverse-all*)
(defn traverse-all* [apath] (compiled-traverse-all (i/comp-paths* apath)))
(defn traverse-all*
"Returns a transducer that traverses over each element with the given path."
[apath]
(compiled-traverse-all (i/comp-paths* apath)))
;; Transformation functions
(def ^{:doc "Version of transform that takes in a path precompiled with comp-paths"}
compiled-transform i/compiled-transform*)
(def ^{:doc "Version of vtransform that takes in a path precompiled with comp-paths"}
compiled-vtransform i/compiled-vtransform*)
(defn transform*
"Navigates to each value specified by the path and replaces it by the result of running
the transform-fn on it"
@ -538,8 +614,8 @@
(defn multi-transform*
"Just like `transform` but expects transform functions to be specified
inline in the path using `terminal`. Error is thrown if navigation finishes
at a non-`terminal` navigator. `terminal-val` is a wrapper around `terminal` and is
inline in the path using `terminal` or `vterminal`. Error is thrown if navigation finishes
at a non-terminal navigator. `terminal-val` is a wrapper around `terminal` and is
the `multi-transform` equivalent of `setval`."
[path structure]
(compiled-multi-transform (i/comp-paths* path) structure))
@ -607,17 +683,28 @@
i/STAY*)
(def
^{:doc "For usage with `multi-transform`, defines an endpoint in the navigation
that will have the parameterized transform function run. The transform
^{:doc "Defines an endpoint in the navigation the transform function run. The transform
function works just like it does in `transform`, with collected values
given as the first arguments"}
terminal
(richnav [afn]
(select* [this vals structure next-fn]
(i/throw-illegal "'terminal' should only be used in multi-transform"))
NONE)
(transform* [this vals structure next-fn]
(i/terminal* afn vals structure))))
(def
^{:doc "Defines an endpoint in the navigation the transform function run.The transform
function works differently than it does in `transform`. Rather than receive
collected vals spliced in as the first arguments to the function, this function
always takes two arguemnts. The first is all collected vals in a vector, and
the second is the navigated value."}
vterminal
(richnav [afn]
(select* [this vals structure next-fn]
NONE)
(transform* [this vals structure next-fn]
(afn vals structure))))
(defn ^:direct-nav terminal-val
"Like `terminal` but specifies a val to set at the location regardless of
@ -635,6 +722,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]"}
@ -646,6 +746,16 @@
(transform* [this structure next-fn]
(n/map-vals-transform structure next-fn)))
(defnav
^{:doc "Navigate to each key of the map. This is more efficient than
navigating via [ALL FIRST]"}
MAP-KEYS
[]
(select* [this structure next-fn]
(doseqres NONE [k (keys structure)]
(next-fn k)))
(transform* [this structure next-fn]
(n/map-keys-transform structure next-fn)))
(defcollector VAL []
@ -665,14 +775,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
@ -702,10 +815,12 @@
BEGINNING
[]
(select* [this structure next-fn]
(next-fn []))
(next-fn (if (string? structure) "" [])))
(transform* [this structure next-fn]
(let [to-prepend (next-fn [])]
(n/prepend-all structure to-prepend))))
(if (string? structure)
(str (next-fn "") structure)
(let [to-prepend (next-fn [])]
(n/prepend-all structure to-prepend)))))
(defnav
@ -713,11 +828,59 @@
END
[]
(select* [this structure next-fn]
(next-fn []))
(next-fn (if (string? structure) "" [])))
(transform* [this structure next-fn]
(let [to-append (next-fn [])]
(n/append-all structure to-append))))
(if (string? structure)
(str structure (next-fn ""))
(let [to-append (next-fn [])]
(n/append-all structure to-append)))))
(defnav
^{:doc "Navigate to 'void' elem in the set.
For transformations - if result is not `NONE`,
then add that value to the set."}
NONE-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(if (nil? structure)
#{newe}
(conj structure newe)
)))))
(defnav
^{:doc "Navigate to 'void' element before the sequence.
For transformations if result is not `NONE`,
then prepend that value."}
BEFORE-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(n/prepend-one structure newe)
))))
(defnav
^{:doc "Navigate to 'void' element after the sequence.
For transformations if result is not `NONE`,
then append that value."}
AFTER-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(n/append-one structure newe)
))))
(defnav
^{:doc "Navigates to the specified subset (by taking an intersection).
@ -750,32 +913,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]
@ -783,16 +932,168 @@
(transform* [this structure next-fn]
(let [select-result (compiled-select late structure)
transformed (next-fn select-result)
values-to-insert (i/mutable-cell transformed)]
values-to-insert (i/mutable-cell (seq 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)))))
(def keypath (eachnav n/keypath*))
(def must (eachnav n/must*))
(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
(eachnav n/keypath*))
(def ^{:doc "Navigate to the specified keys one after another, only if they exist
in the data structure. If navigate to NONE, that element is removed
from the map or vector."}
must
(eachnav n/must*))
(def ^{:doc "Navigate to the specified indices one after another. If navigate to
NONE, that element is removed from the sequence."}
nthpath
(eachnav n/nthpath*))
(defrichnav
^{:doc "Navigates to the empty space between the index and the prior index. For select
navigates to NONE, and transforms to non-NONE insert at that position."}
before-index
[index]
(select* [this vals structure next-fn]
NONE)
(transform* [this vals structure next-fn]
(let [v (next-fn vals NONE)]
(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
at that index to the new index, shifting other elements in the sequence."}
index-nav
[i]
(select* [this vals structure next-fn]
(if (and (>= i 0) (< i (count structure)))
(next-fn vals i)
NONE
))
(transform* [this vals structure next-fn]
(if (and (>= i 0) (< i (count structure)))
(let [newi (next-fn vals i)]
(if (= newi i)
structure
(let [v (nth structure i)]
(if (vector? structure)
(let [shifted (if (< newi i)
(loop [j (dec i)
s structure]
(if (< j newi)
s
(recur (dec j) (assoc s (inc j) (nth s j)))
))
(loop [j (inc i)
s structure]
(if (> j newi)
s
(recur (inc j) (assoc s (dec j) (nth s j)))
)))]
(assoc shifted newi v)
)
(->> structure
(setval (nthpath i) NONE)
(setval (before-index newi) v)
)))))
structure
)))
(defnav
^{:doc "Navigate to [index elem] pairs for each element in a sequence. The sequence will be indexed
starting from `start`. Changing index in transform has same effect as `index-nav`. Indices seen
during transform take into account any shifting from prior sequence elements changing indices."}
indexed-vals
[start]
(select* [this structure next-fn]
;; could be more efficient with a primitive mutable field
(let [i (i/mutable-cell (dec start))]
(doseqres NONE [e structure]
(i/update-cell! i inc)
(next-fn [(i/get-cell i) e])
)))
(transform* [this structure next-fn]
(let [indices (i/mutable-cell (-> structure count range))]
(reduce
(fn [s e]
(let [curri (first (i/get-cell indices))
[newi* newe] (next-fn [(+ start curri) e])
newi (- newi* start)]
(i/update-cell!
indices
(fn [ii]
(let [ii2 (next ii)]
(if (> newi curri)
(transform [ALL #(>= % (inc curri)) #(<= % newi)] dec ii2)
ii2
))))
(->> s
(setval (nthpath curri) newe)
(setval (index-nav curri) newi)
)))
structure
structure
))))
(def
^{:doc "`indexed-vals` with a starting index of 0."}
INDEXED-VALS
(indexed-vals 0))
(defrichnav
^{:doc "Navigates to result of running `afn` on the currently navigated value."}
@ -827,6 +1128,13 @@
(swap! structure next-fn)
structure)))
(defnav regex-nav [re]
(select* [this structure next-fn]
(doseqres NONE [s (re-seq re structure)]
(next-fn s)))
(transform* [this structure next-fn]
(clojure.string/replace structure re next-fn)))
(defdynamicnav selected?
"Filters the current value based on whether a path finds anything.
e.g. (selected? :vals ALL even?) keeps the current element only if an
@ -834,37 +1142,45 @@
[& path]
(if-let [afn (n/extract-basic-filter-fn path)]
afn
(late-bound-nav [late (late-path path)]
(select* [this structure next-fn]
(late-bound-richnav [late (late-path path)]
(select* [this vals structure next-fn]
(i/filter-select
#(n/selected?* late %)
#(n/selected?* late vals %)
vals
structure
next-fn))
(transform* [this structure next-fn]
(transform* [this vals structure next-fn]
(i/filter-transform
#(n/selected?* late %)
#(n/selected?* late vals %)
vals
structure
next-fn)))))
(defdynamicnav not-selected? [& path]
(if-let [afn (n/extract-basic-filter-fn path)]
(fn [s] (not (afn s)))
(late-bound-nav [late (late-path path)]
(select* [this structure next-fn]
(late-bound-richnav [late (late-path path)]
(select* [this vals structure next-fn]
(i/filter-select
#(n/not-selected?* late %)
#(n/not-selected?* late vals %)
vals
structure
next-fn))
(transform* [this structure next-fn]
(transform* [this vals structure next-fn]
(i/filter-transform
#(n/not-selected?* late %)
#(n/not-selected?* late vals %)
vals
structure
next-fn)))))
(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)))
@ -892,11 +1208,19 @@
)))
(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."}
^{:doc "Keeps the element only if it matches the supplied predicate. Functions in paths
implicitly convert to this navigator."
: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))
@ -905,6 +1229,26 @@
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj clojure.lang.Symbol :cljs cljs.core/Symbol)
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj String :cljs string)
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj Number :cljs number)
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj Character :cljs char)
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj Boolean :cljs boolean)
ImplicitNav
(implicit-nav [this] (n/keypath* this)))
(extend-type #?(:clj clojure.lang.AFn :cljs function)
ImplicitNav
(implicit-nav [this] (pred this)))
@ -913,6 +1257,10 @@
ImplicitNav
(implicit-nav [this] (pred this)))
(extend-type #?(:clj java.util.regex.Pattern :cljs js/RegExp)
ImplicitNav
(implicit-nav [this] (regex-nav this)))
(defnav
^{:doc "Navigates to the provided val if the structure is nil. Otherwise it stays
navigated at the structure."}
@ -950,6 +1298,33 @@
(transform* [this structure next-fn]
(with-meta structure (next-fn (meta structure)))))
(defnav ^{:doc "Navigates to the name portion of the keyword or symbol"}
NAME
[]
(select* [this structure next-fn]
(next-fn (name structure)))
(transform* [this structure next-fn]
(let [new-name (next-fn (name structure))
ns (namespace structure)]
(cond (keyword? structure) (keyword ns new-name)
(symbol? structure) (symbol ns new-name)
: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"}
NAMESPACE
[]
(select* [this structure next-fn]
(next-fn (namespace structure)))
(transform* [this structure next-fn]
(let [name (name structure)
new-ns (next-fn (namespace structure))]
(cond (keyword? structure) (keyword new-ns name)
(symbol? structure) (symbol new-ns name)
:else (throw (ex-info "NAMESPACE can only be used on symbols or keywords"
{:structure structure}))
))))
(defdynamicnav
^{:doc "Adds the result of running select with the given path on the
current value to the collected vals."}
@ -983,7 +1358,12 @@
(collect-val [this structure]
val))
(defdynamicnav with-fresh-collected
(defdynamicnav
^{:doc
"Continues navigating on the given path with the collected vals reset to []. Once
navigation leaves the scope of with-fresh-collected, the collected vals revert
to what they were before."}
with-fresh-collected
[& path]
(late-bound-richnav [late (late-path path)]
(select* [this vals structure next-fn]
@ -1034,7 +1414,7 @@
vals
structure
next-fn
#(n/selected?* late-cond %)
#(n/selected?* late-cond vals %)
late-then
late-else))
(transform* [this vals structure next-fn]
@ -1042,7 +1422,7 @@
vals
structure
next-fn
#(n/selected?* late-cond %)
#(n/selected?* late-cond vals %)
late-then
late-else))))))
@ -1052,11 +1432,7 @@
Tests the structure if selecting with cond-path returns anything.
If so, it uses the following path for this portion of the navigation.
Otherwise, it tries the next cond-path. If nothing matches, then the structure
is not selected.
The input paths may be parameterized, in which case the result of cond-path
will be parameterized in the order of which the parameterized navigators
were declared."
is not selected."
[& conds]
(let [pairs (reverse (partition 2 conds))]
(reduce
@ -1101,3 +1477,30 @@
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]
)))
(let [empty->NONE (if-path empty? (terminal-val NONE))
compact* (fn [nav] (multi-path nav empty->NONE))]
(defdynamicnav compact
"During transforms, after each step of navigation in subpath check if the
value is empty. If so, remove that value by setting it to NONE."
[& path]
(map compact* path)
))

View file

@ -1,20 +1,25 @@
(ns com.rpl.specter.impl
#?(:cljs (:require-macros
[com.rpl.specter.util-macros
:refer [doseqres mk-comp-navs mk-late-fn mk-late-fn-records]]))
: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]]))
(:require [com.rpl.specter.protocols :as p]
#?(:clj [clojure.pprint :as pp] :cljs [cljs.pprint :as pp])
#?(: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)
@ -46,21 +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]
`(throw* IllegalArgumentException ~@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
@ -82,7 +72,7 @@
:cljs
(defn clj-macroexpand-all [form]
(throw-illegal "not implemented")))
(throw (ex-info "not implemented" {}))))
#?(
@ -91,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)))
@ -112,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)))
@ -126,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]
@ -145,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
@ -174,7 +170,7 @@
(coerce-path (vec this))))
#?(:cljs cljs.core/Subvec)
#?(:cljs (coerce-path [this]
(coerce-path (vec this))))
(coerce-path (into [] this))))
#?(:clj Object :cljs default)
(coerce-path [this]
@ -223,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))
@ -241,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))
@ -251,7 +256,10 @@
(set_cell cell val)))
#?(
#?(:bb
(defn get-cell [^MutableCell c]
@(:x c))
:clj
(defn get-cell [^MutableCell c]
(.get c))
@ -271,10 +279,10 @@
#?(
:clj
(defmacro compiled-traverse* [path result-fn structure]
(defmacro compiled-traverse-with-vals* [path result-fn vals structure]
`(exec-select*
~path
[]
~vals
~structure
(fn [vals# structure#]
(if (identical? vals# [])
@ -282,10 +290,10 @@
(~result-fn (conj vals# structure#))))))
:cljs
(defn compiled-traverse* [path result-fn structure]
(defn compiled-traverse-with-vals* [path result-fn vals structure]
(exec-select*
path
[]
vals
structure
(fn [vals structure]
(if (identical? vals [])
@ -293,11 +301,16 @@
(result-fn (conj vals structure)))))))
(defn compiled-traverse* [path result-fn structure]
(compiled-traverse-with-vals* path result-fn [] structure))
(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)]
@ -313,9 +326,33 @@
(get-cell cell)
))))
(defn do-compiled-traverse [apath structure]
(unreduced (do-compiled-traverse* apath structure)))
#?(
: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)
)
:cljs
(defn- call-reduce-interface [^cljs.core/IReduce traverser afn start]
(-reduce traverser afn start)
))
(defn do-compiled-traverse [apath structure]
(let [traverser (do-compiled-traverse* apath structure)]
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
(#?(:clj reduce :cljs -reduce)
[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)]
(unreduced res)
)))))
(defn compiled-traverse-all* [path]
(fn [xf]
@ -347,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)]
@ -362,17 +400,20 @@
(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)))
(defn compiled-select-any* [path structure]
(unreduced (compiled-traverse* path reduced structure)))
(defn compiled-select-any*
([path structure] (compiled-select-any* path [] structure))
([path vals structure]
(unreduced (compiled-traverse-with-vals* path reduced vals structure))))
(defn compiled-select-first* [path structure]
(let [ret (compiled-select-any* path structure)]
@ -396,6 +437,9 @@
(fn [vals structure]
(terminal* transform-fn vals structure))))
(defn compiled-vtransform* [nav transform-fn structure]
(exec-transform* nav [] structure transform-fn))
(defn fn-invocation? [f]
(or #?(:clj (instance? clojure.lang.Cons f))
#?(:clj (instance? clojure.lang.LazySeq f))
@ -405,8 +449,10 @@
(defrecord LocalSym
[val sym])
;; needs to be named "avar" instead of "var" due to regression in cljs circa
;; 6/26/2017. See https://github.com/nathanmarz/specter/issues/215
(defrecord VarUse
[val var sym])
[val avar sym])
(defrecord SpecialFormUse
[val code])
@ -422,7 +468,7 @@
[path])
(defrecord DynamicFunction
[op params])
[op params code])
(defn dynamic-param? [o]
(contains? #{DynamicPath DynamicVal DynamicFunction} (type o)))
@ -453,14 +499,14 @@
(.-dynamic? c))
(defn filter-select [afn structure next-fn]
(defn filter-select [afn vals structure next-fn]
(if (afn structure)
(next-fn structure)
(next-fn vals structure)
NONE))
(defn filter-transform [afn structure next-fn]
(defn filter-transform [afn vals structure next-fn]
(if (afn structure)
(next-fn structure)
(next-fn vals structure)
structure))
(defn ^:direct-nav pred* [afn]
@ -508,14 +554,21 @@
(mk-comp-navs)
(defn srange-transform* [structure start end next-fn]
(let [structurev (vec structure)
newpart (next-fn (-> structurev (subvec start end)))
res (concat (subvec structurev 0 start)
newpart
(subvec structurev end (count structure)))]
(if (vector? structure)
(vec res)
res)))
(if (string? structure)
(let [newss (next-fn (subs structure start end))]
(str (subs structure 0 start)
newss
(subs structure end (count structure))
))
(let [structurev (vec structure)
newpart (next-fn (-> structurev (subvec start end)))
res (concat (subvec structurev 0 start)
newpart
(subvec structurev end (count structure)))]
(if (vector? structure)
(vec res)
res
))))
(defn- matching-indices [aseq p]
(keep-indexed (fn [i e] (if (p e) i)) aseq))
@ -552,6 +605,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
@ -625,32 +697,37 @@
(-> 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))
(->DynamicVal (maybe-direct-nav
(:sym o)
(or (-> o :var direct-nav?)
(-> o :sym direct-nav?))))
(maybe-direct-nav
(:val o)
(or (-> o :var direct-nav?)
(-> o :sym direct-nav?)
(-> o :val direct-nav?))))
(let [v (:avar o)]
;; v can be nil if the symbol referred to an imported class
(if (and v (dynamic-var? v))
(->DynamicVal (maybe-direct-nav
(:sym o)
(or (direct-nav? v)
(-> o :sym direct-nav?))))
(maybe-direct-nav
(:val o)
(or (and v (direct-nav? v))
(-> o :sym direct-nav?)
(-> o :val direct-nav?)))))
(instance? LocalSym o)
(->DynamicVal (:sym o))
@ -664,7 +741,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
@ -674,22 +751,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)))
@ -747,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)))))
@ -765,6 +842,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)
@ -780,7 +861,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
@ -831,6 +919,8 @@
(if (fn? e) (re-find #" .*" (pr-str e)) e))
o)))
(def ^:dynamic *path-compile-files* false)
#?(:clj
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-param]
(let [code `(fn [~@used-locals-list] ~resolved-code)
@ -839,7 +929,11 @@
(println "Produced code:")
(pp/pprint code)
(println))
(binding [*ns* ns] (eval+ code))))
(binding [*ns* ns
*compile-files* (if *path-compile-files*
*compile-files*
false)]
(eval+ code))))
:cljs
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-params]
@ -847,7 +941,7 @@
(println "Possible params:")
(println possible-params)
(println "\nProduced dynamic object:")
(pp/pprint (mk-fn-name-strs resolved-code))
(println (mk-fn-name-strs resolved-code))
(println))
(fn [dynamic-params]
(late-resolve resolved-code dynamic-params))))
@ -902,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

@ -5,43 +5,22 @@
[defnav defrichnav]]
[com.rpl.specter.util-macros :refer
[doseqres]]))
(:use #?(:clj [com.rpl.specter.macros :only [defnav defrichnav]])
#?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
[com.rpl.specter.util-macros :only [doseqres]]))
(:require [com.rpl.specter.impl :as i]
[clojure.walk :as walk]
#?(:clj [clojure.core.reducers :as r])))
#?@(:bb []
:clj [[clojure.core.reducers :as r]])))
(defn not-selected?*
[compiled-path structure]
[compiled-path vals structure]
(->> structure
(i/compiled-select-any* compiled-path)
(i/compiled-select-any* compiled-path vals)
(identical? i/NONE)))
(defn selected?*
[compiled-path structure]
(not (not-selected?* compiled-path 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))))
[compiled-path vals structure]
(not (not-selected?* compiled-path vals structure)))
(defn all-select [structure next-fn]
@ -61,11 +40,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 +59,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
@ -90,7 +74,6 @@
nil)
;; in cljs they're PersistentVector so don't need a special case
#?(:clj clojure.lang.MapEntry)
#?(:clj
(all-transform [structure next-fn]
@ -99,15 +82,39 @@
(clojure.lang.MapEntry. newk newv))))
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
#?(:cljs cljs.core/MapEntry)
#?(:cljs
(all-transform [structure next-fn]
(let [newk (next-fn (key structure))
newv (next-fn (val structure))]
(cljs.core/->MapEntry newk newv nil))))
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(all-transform [structure next-fn]
(into []
(comp (map next-fn)
(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 #{}
(comp (map next-fn)
(filter not-NONE?))
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)
@ -120,7 +127,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))
@ -146,6 +153,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]
@ -153,7 +164,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)))))
@ -176,7 +187,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)))))
@ -185,26 +196,49 @@
: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)
#?(: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)))))))
(defprotocol MapValsTransformProtocol
(map-vals-transform [structure next-fn]))
(defprotocol MapTransformProtocol
(map-vals-transform [structure next-fn])
(map-keys-transform [structure next-fn])
)
(defn map-vals-non-transient-transform [structure empty-map next-fn]
(reduce-kv
@ -216,14 +250,29 @@
empty-map
structure))
(extend-protocol MapValsTransformProtocol
(defn map-keys-non-transient-transform [structure empty-map next-fn]
(reduce-kv
(fn [m k v]
(let [newk (next-fn k)]
(if (identical? newk i/NONE)
m
(assoc m newk v))))
empty-map
structure))
(extend-protocol MapTransformProtocol
nil
(map-vals-transform [structure next-fn]
nil)
(map-keys-transform [structure next-fn]
nil)
#?(: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)
@ -250,17 +299,51 @@
array
)]
(clojure.lang.PersistentArrayMap. array)))))
#?(: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)
none-cell (i/mutable-cell 0)
len (.count structure)
array (i/fast-object-array (* 2 len))]
(loop [i 0
j 0]
(if (.hasNext k-it)
(let [k (.next k-it)
v (.next v-it)
newk (next-fn k)]
(if (identical? newk i/NONE)
(do
(i/update-cell! none-cell inc)
(recur (+ i 2) j))
(do
(aset array j newk)
(aset array (inc j) v)
(recur (+ i 2) (+ j 2)))))))
(let [none-count (i/get-cell none-cell)
array (if (not= 0 none-count)
(java.util.Arrays/copyOf array (int (* 2 (- len none-count))))
array
)]
(clojure.lang.PersistentArrayMap/createAsIfByAssoc array)))))
#?(:cljs cljs.core/PersistentArrayMap)
#?(:cljs
(map-vals-transform [structure next-fn]
(map-vals-non-transient-transform structure {} next-fn)))
#?(:cljs
(map-keys-transform [structure next-fn]
(map-keys-non-transient-transform structure {} next-fn)))
#?(:clj clojure.lang.PersistentTreeMap :cljs cljs.core/PersistentTreeMap)
(map-vals-transform [structure next-fn]
(map-vals-non-transient-transform structure (empty structure) next-fn))
(map-keys-transform [structure next-fn]
(map-keys-non-transient-transform structure (empty structure) next-fn))
#?(:clj clojure.lang.PersistentHashMap :cljs cljs.core/PersistentHashMap)
@ -276,7 +359,18 @@
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
structure)))
(map-keys-transform [structure next-fn]
(persistent!
(reduce-kv
(fn [m k v]
(let [newk (next-fn k)]
(if (identical? newk i/NONE)
m
(assoc! m newk v))))
(transient
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
structure)))
#?(:clj Object :cljs default)
(map-vals-transform [structure next-fn]
@ -287,11 +381,23 @@
m
(assoc m k newv))))
(empty structure)
structure))
(map-keys-transform [structure next-fn]
(reduce-kv
(fn [m k v]
(let [newk (next-fn k)]
(if (identical? newk i/NONE)
m
(assoc m newk v))))
(empty structure)
structure)))
(defn srange-select [structure start end next-fn]
(next-fn (-> structure vec (subvec start end))))
(next-fn
(if (string? structure)
(subs structure start end)
(-> structure vec (subvec start end))
)))
(def srange-transform i/srange-transform*)
@ -333,7 +439,10 @@
(defprotocol AddExtremes
(append-all [structure elements])
(prepend-all [structure elements]))
(prepend-all [structure elements])
(append-one [structure elem])
(prepend-one [structure elem])
)
(extend-protocol AddExtremes
nil
@ -341,8 +450,12 @@
elements)
(prepend-all [_ elements]
elements)
(append-one [_ elem]
(list elem))
(prepend-one [_ elem]
(list elem))
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(append-all [structure elements]
(reduce conj structure elements))
(prepend-all [structure elements]
@ -351,13 +464,40 @@
(reduce conj! <> elements)
(reduce conj! <> structure)
(persistent! <>))))
(append-one [structure elem]
(conj structure elem))
(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]
(concat structure elements))
(prepend-all [structure elements]
(concat elements structure)))
(concat elements structure))
(append-one [structure elem]
(concat structure [elem]))
(prepend-one [structure elem]
(cons elem structure))
)
@ -372,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)
@ -382,13 +525,10 @@
structure
(updater structure next-fn))))
(defn- update-first-list [l afn]
(cons (afn (first l)) (rest l)))
#?(:bb
(defn vec-count [v]
(count v))
(defn- update-last-list [l afn]
(concat (butlast l) [(afn (last l))]))
#?(
:clj
(defn vec-count [^clojure.lang.IPersistentVector v]
(.length v))
@ -397,8 +537,53 @@
(defn vec-count [v]
(count v)))
(defn- update-first-list [l afn]
(let [newf (afn (first l))
restl (rest l)]
(if (identical? i/NONE newf)
restl
(cons newf restl))))
(defn- update-last-list [l afn]
(let [lastl (afn (last l))
bl (butlast l)]
(if (identical? i/NONE lastl)
(if (nil? bl) '() bl)
(concat bl [lastl]))))
(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)
)))
(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))
@ -409,19 +594,45 @@
(extend-protocol UpdateExtremes
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(update-first [v afn]
(let [val (nth v 0)]
(assoc v 0 (afn val))))
(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] [(afn e)])
2 (let [[e1 e2] v] [e1 (afn e2)])
(let [i (dec c)]
(assoc v i (afn (nth v i)))))))
(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]
(let [rests (subs s 1 (count s))
newb (afn (nth s 0))]
(if (identical? i/NONE newb)
rests
(str newb rests))))
(update-last [s afn]
(let [last-idx (-> s count dec)
newl (afn (nth s last-idx))
begins (subs s 0 last-idx)]
(if (identical? i/NONE newl)
begins
(str begins newl)
)))
#?(:cljs cljs.core/MapEntry)
#?(:cljs
(update-first [e afn]
(cljs.core/->MapEntry (-> e key afn) (val e) nil)))
#?(:cljs
(update-last [e afn]
(cljs.core/->MapEntry (key e) (-> e val afn) nil)))
#?(:clj Object :cljs default)
(update-first [l val]
@ -436,11 +647,27 @@
(nth v 0))
(get-last [v]
(peek v))
#?(:clj Object :cljs default)
(get-first [s]
(first s))
(get-last [s]
(last s)))
(last s))
#?(:cljs cljs.core/MapEntry)
#?(:cljs
(get-first [e]
(key e)))
#?(:cljs
(get-last [e]
(val e)))
#?(:clj String :cljs string)
(get-first [s]
(nth s 0))
(get-last [s]
(nth s (-> s count dec))
))
@ -459,12 +686,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)
@ -474,7 +695,8 @@
(assoc structure key newv))))
(defrichnav
^{:doc "Navigates to the specified key, navigating to nil if it does not exist."}
^{:doc "Navigates to the specified key, navigating to nil if it does not exist.
Setting the value to NONE will remove it from the collection."}
keypath*
[key]
(select* [this vals structure next-fn]
@ -485,7 +707,8 @@
(defrichnav
^{:doc "Navigates to the key only if it exists in the map."}
^{:doc "Navigates to the key only if it exists in the map. Setting the value to NONE
will remove it from the collection."}
must*
[k]
(select* [this vals structure next-fn]
@ -496,3 +719,68 @@
(if (contains? structure k)
(do-keypath-transform vals structure k next-fn)
structure)))
(defrichnav nthpath*
^{:doc "Navigates to the given position in the sequence. Setting the value to NONE
will remove it from the sequence. Works for all sequence types."}
[i]
(select* [this vals structure next-fn]
(next-fn vals (nth structure i)))
(transform* [this vals structure next-fn]
(if (vector? structure)
(let [newv (next-fn vals (nth structure i))]
(if (identical? newv i/NONE)
(i/srange-transform* structure i (inc i) (fn [_] []))
(assoc structure i newv)))
(i/srange-transform* ; can make this much more efficient with alternate impl
structure
i
(inc i)
(fn [[e]]
(let [v (next-fn vals e)]
(if (identical? v i/NONE)
[]
[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)
))
(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

@ -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)))

View file

@ -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)

View file

@ -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,8 @@
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
vtransform]]))
(:use
#?(:clj [clojure.test :only [deftest is]])
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
@ -21,15 +22,16 @@
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
vtransform]]))
(: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]))
@ -1318,13 +1320,25 @@
(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])))
(is (predand= vector? [1 2 4] (setval (s/nthpath 2) s/NONE [1 2 3 4])))
(is (predand= (complement vector?) '(1 -2 3 4) (transform (s/nthpath 1) - '(1 2 3 4))))
(is (predand= (complement vector?) '(1 2 4) (setval (s/nthpath 2) s/NONE '(1 2 3 4))))
(is (= [0 1 [2 4 4]] (transform (s/nthpath 2 1) inc [0 1 [2 3 4]])))
)
(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])))
@ -1375,3 +1389,325 @@
(is (= [1 2]
(into [] (traverse-all :a) [{:a 1} {:a 2}])))
)
(deftest early-terminate-traverse-test
(is (= 6
(reduce
(completing (fn [r i] (if (> r 5) (reduced r) (+ r i))))
0
(traverse [s/ALL s/ALL]
[[1 2] [3 4] [5]])))))
(deftest select-any-vals-test
(is (= [1 1] (select-any s/VAL 1))))
(deftest conditional-vals-test
(is (= 2 (select-any (s/with-fresh-collected
(s/collect-one (s/keypath 0))
(s/if-path (collected? [n] (even? n))
(s/keypath 1)
(s/keypath 2)))
[4 2 3])))
(is (= [4 2 3]
(select-any (s/with-fresh-collected
(s/collect-one (s/keypath 0))
(s/selected? (collected? [n] (even? n))))
[4 2 3])))
)
(deftest name-namespace-test
(is (= :a (setval s/NAME "a" :e)))
(is (= :a/b (setval s/NAME "b" :a/e)))
(is (= 'a (setval s/NAME "a" 'e)))
(is (= 'a/b (setval s/NAME "b" 'a/e)))
(is (= :a/e (setval s/NAMESPACE "a" :e)))
(is (= :a/e (setval s/NAMESPACE "a" :f/e)))
(is (= 'a/e (setval s/NAMESPACE "a" 'e)))
(is (= 'a/e (setval s/NAMESPACE "a" 'f/e)))
)
(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")))
(is (= "" (select-any s/BEGINNING "abc")))
(is (= "" (select-any s/END "abc")))
(is (= \a (select-any s/FIRST "abc")))
(is (= \c (select-any s/LAST "abc")))
(is (= "qbc" (setval s/FIRST \q "abc")))
(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"]))
(is (= (select [:a (s/regex-nav #"t")] {:a "test"}) ["t" "t"]))
(is (= (transform (s/regex-nav #"t") clojure.string/capitalize "test") "TesT"))
;; also test regexes as implicit navs
(is (= (transform [:a #"t"] clojure.string/capitalize {:a "test"}) {:a "TesT"}))
(is (= (transform (s/regex-nav #"\s+\w") clojure.string/triml "Hello World!") "HelloWorld!"))
(is (= (setval (s/regex-nav #"t") "z" "test") "zesz"))
(is (= (setval [:a (s/regex-nav #"t")] "z" {:a "test"}) {:a "zesz"}))
(is (= (transform (s/regex-nav #"aa*") (fn [s] (-> s count str)) "aadt") "2dt"))
(is (= (transform (s/regex-nav #"[Aa]+") (fn [s] (apply str (take (count s) (repeat "@")))) "Amsterdam Aardvarks") "@msterd@m @@rdv@rks"))
(is (= (select [(s/regex-nav #"(\S+):\ (\d+)") (s/nthpath 2)] "Mary: 1st George: 2nd Arthur: 3rd") ["1" "2" "3"]))
(is (= (transform (s/subselect (s/regex-nav #"\d\w+")) reverse "Mary: 1st George: 2nd Arthur: 3rd") "Mary: 3rd George: 2nd Arthur: 1st"))
)
(deftest single-value-none-navigators-test
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2])))
(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= 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)))
)
(deftest subvec-test
(let [v (subvec [1] 0)]
(is (predand= vector? [2] (transform s/FIRST inc v)))
(is (predand= vector? [2] (transform s/LAST inc v)))
(is (predand= vector? [2] (transform s/ALL inc v)))
(is (predand= vector? [0 1] (setval s/BEGINNING [0] v)))
(is (predand= vector? [1 0] (setval s/END [0] v)))
(is (predand= vector? [0 1] (setval s/BEFORE-ELEM 0 v)))
(is (predand= vector? [1 0] (setval s/AFTER-ELEM 0 v)))
(is (predand= vector? [1 0] (setval (s/srange 1 1) [0] v)))
))
(defspec map-keys-all-first-equivalence-transform
(for-all+
[m (limit-size 10 (gen/map gen/int gen/keyword))]
(= (transform s/MAP-KEYS inc m)
(transform [s/ALL s/FIRST] inc m )
)))
(defspec map-keys-all-first-equivalence-select
(for-all+
[m (limit-size 10 (gen/map gen/int gen/keyword))]
(= (select s/MAP-KEYS m)
(select [s/ALL s/FIRST] m)
)))
(defspec remove-first-vector
(for-all+
[v (limit-size 10 (gen/not-empty (gen/vector gen/int)))]
(let [newv (setval s/FIRST s/NONE v)]
(and (= newv (vec (rest v)))
(vector? newv)
))))
(defspec remove-first-list
(for-all+
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
(let [newl (setval s/FIRST s/NONE l)]
(and (= newl (rest l))
(listlike? newl)
))))
(defspec remove-last-vector
(for-all+
[v (limit-size 10 (gen/not-empty (gen/vector gen/int)))]
(let [newv (setval s/LAST s/NONE v)]
(and (= newv (vec (butlast v)))
(vector? newv)
))))
(defspec remove-last-list
(for-all+
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
(let [newl (setval s/LAST s/NONE l)
bl (butlast l)]
(and (or (= newl bl) (and (nil? bl) (= '() newl)))
(seq? newl)
))))
(deftest remove-extreme-string
(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])))
(is (= [1 3 5] (setval (s/filterer even?) [] [1 2 3 4 5])))
(is (= [1 3 5] (setval (s/filterer even?) nil [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])))
)
(def ^:dynamic *dvar* :a)
(defn dvar-tester []
(select-any *dvar* {:a 1 :b 2}))
(deftest dynamic-var-ic-test
(is (= 1 (dvar-tester)))
(is (= 2 (binding [*dvar* :b] (dvar-tester))))
)
(deftest before-index-test
(let [data [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)))
; 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
(let [data [1 2 3 4 5 6]
datal '(1 2 3 4 5 6)]
(is (predand= vector? [3 1 2 4 5 6] (setval (s/index-nav 2) 0 data)))
(is (predand= vector? [1 3 2 4 5 6] (setval (s/index-nav 2) 1 data)))
(is (predand= vector? [1 2 3 4 5 6] (setval (s/index-nav 2) 2 data)))
(is (predand= vector? [1 2 4 5 3 6] (setval (s/index-nav 2) 4 data)))
(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= 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
(let [data [:a :b :c :d :e]]
(is (= [[0 :a] [1 :b] [2 :c] [3 :d] [4 :e]] (select s/INDEXED-VALS data)))
(is (= [:e :d :c :b :a] (setval [s/INDEXED-VALS s/FIRST] 0 data)))
(is (= [:a :b :e :d :c] (setval [s/INDEXED-VALS s/FIRST] 2 data)))
(is (= [:b :a :d :c :e] (transform [s/INDEXED-VALS s/FIRST odd?] dec data)))
(is (= [:a :b :c :d :e] (transform [s/INDEXED-VALS s/FIRST odd?] inc data)))
(is (= [0 2 2 4] (transform [s/INDEXED-VALS s/LAST odd?] inc [0 1 2 3])))
(is (= [0 1 2 3] (transform [s/INDEXED-VALS (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [2 1 3 0])))
(is (= [-1 0 1 2 3] (transform [(s/indexed-vals -1) (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [3 -1 0 2 1])))
(is (= [[1 :a] [2 :b] [3 :c]] (select (s/indexed-vals 1) [:a :b :c])))
))
(deftest other-implicit-navs-test
(is (= 1 (select-any ["a" true \c 10 'd] {"a" {true {\c {10 {'d 1}}}}})))
)
(deftest vterminal-test
(is (= {:a {:b [[1 2] 3]}}
(multi-transform [(s/putval 1) :a (s/putval 2) :b (s/vterminal (fn [vs v] [vs v]))]
{:a {:b 3}})))
)
(deftest vtransform-test
(is (= {:a 6} (vtransform [:a (s/putval 2) (s/putval 3)] (fn [vs v] (+ v (reduce + vs))) {:a 1})))
)
(deftest compact-test
(is (= {} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1}}})))
(is (= {:a {:d 2}} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1} :d 2}})))
(let [TREE-VALUES (recursive-path [] p (s/if-path vector? [(s/compact s/ALL) p] s/STAY))
tree [1 [2 3] [] [4 [[5] [[6]]]]]]
(is (= [2 4 6] (select [TREE-VALUES even?] tree)))
(is (= [1 [3] [[[5]]]] (setval [TREE-VALUES even?] s/NONE tree)))
)
(is (= [{:a [{:c 1}]}]
(setval [s/ALL (s/compact :a s/ALL :b)]
s/NONE
[{:a [{:b 3}]}
{:a [{:b 2 :c 1}]}])))
)
(deftest class-constant-test
(let [f (fn [p] (fn [v] (str p (inc v))))]
(is (= (str #?(:clj String :cljs js/String) 2)
(multi-transform (s/terminal (f #?(:clj String :cljs js/String))) 1)))
))
#?(: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)))
)))

View file

@ -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]))