Compare commits
627 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a615ff22c | ||
|
|
67e8680602 | ||
|
|
05ae730896 | ||
|
|
a91aaaa34a | ||
|
|
efeefdfacd | ||
|
|
a64209d582 | ||
|
|
d0d6fdf581 | ||
|
|
e8225f0e58 | ||
|
|
a379893598 | ||
|
|
efaf35558a | ||
|
|
083376f904 | ||
|
|
de48f24d70 | ||
|
|
c54a46c686 | ||
|
|
2e002c1270 | ||
|
|
789881b3ad | ||
|
|
edd7429281 | ||
|
|
e222ba2a6c | ||
|
|
40add561b6 | ||
|
|
123af0937e | ||
|
|
6e7d772755 | ||
|
|
134beac2a7 | ||
|
|
86f8412ee4 | ||
|
|
28ef42152a | ||
|
|
ffe8130bf0 | ||
|
|
9c7f6fb65e | ||
|
|
029a33427f | ||
|
|
925e2e91d6 | ||
|
|
29ee91d01e | ||
|
|
b7ff05bf54 | ||
|
|
ae24856ec3 | ||
|
|
23f4e1a370 | ||
|
|
e38a2561d8 | ||
|
|
fe1deb5195 | ||
|
|
7790213b16 | ||
|
|
8764a4b2b6 | ||
|
|
c65b31181a | ||
|
|
f13b5db08a | ||
|
|
6c3253f16a | ||
|
|
e7abb2b538 | ||
|
|
3536e3c461 | ||
|
|
350c8b857f | ||
|
|
798cda211f | ||
|
|
4580de8cc6 | ||
|
|
79a3610a64 | ||
|
|
1590b2c2d4 | ||
|
|
efe94b6539 | ||
|
|
eea5fcb48b | ||
|
|
ac48127871 | ||
|
|
01617b6264 | ||
|
|
38a27ecc43 | ||
|
|
d4887c2090 | ||
|
|
4a589c3074 | ||
|
|
1a05546f27 | ||
|
|
d5143f136c | ||
|
|
f7b7b0cba2 | ||
|
|
05e354a9a2 | ||
|
|
b4a3c4f601 | ||
|
|
c81a4b1a7b | ||
|
|
e92fd674b1 | ||
|
|
c2f669db71 | ||
|
|
09d0d071ef | ||
|
|
70d9fef5cc | ||
|
|
b1050b910c | ||
|
|
7b646ca566 | ||
|
|
6a5054feea | ||
|
|
632e710b07 | ||
|
|
7bd119aa52 | ||
|
|
0ceda21151 | ||
|
|
9515582a19 | ||
|
|
680c36ae5b | ||
|
|
d54aa28d49 | ||
|
|
3478e5b6d7 | ||
|
|
5b60eb17e3 | ||
|
|
349e03342f | ||
|
|
844050545a | ||
|
|
5efafd2d9b | ||
|
|
98c7510d1c | ||
|
|
efaeff4fc5 | ||
|
|
9a8f79774c | ||
|
|
fdfaecd0d0 | ||
|
|
4cf7ee965f | ||
|
|
f593753a68 | ||
|
|
0e88c57a87 | ||
|
|
14dad51fd4 | ||
|
|
5aed3b254e | ||
|
|
d7ee2f7c6a | ||
|
|
7dbe4dc524 | ||
|
|
dc9e2205c3 | ||
|
|
6e90ceadea | ||
|
|
b5f840db22 | ||
|
|
796ad700f7 | ||
|
|
3a6d8620d7 | ||
|
|
92caabbb32 | ||
|
|
49eed0079c | ||
|
|
915c0a9471 | ||
|
|
86033f3df1 | ||
|
|
ac3fe713ac | ||
|
|
efd123d17e | ||
|
|
c233fb7e9d | ||
|
|
8d5f39a861 | ||
|
|
09d00ac7e7 | ||
|
|
e2308e0cda | ||
|
|
31935fca2e | ||
|
|
85d3f14de9 | ||
|
|
43fd7ab2e2 | ||
|
|
1805094df8 | ||
|
|
1b4a7d3d59 | ||
|
|
1b7f987eaa | ||
|
|
03e686cd9c | ||
|
|
dbae629472 | ||
|
|
40c883c1f3 | ||
|
|
b7c62e444c | ||
|
|
179b705211 | ||
|
|
b18733b14e | ||
|
|
179d818503 | ||
|
|
cfb191e1cd | ||
|
|
3fffee336e | ||
|
|
d36b4340c3 | ||
|
|
e8ba45e22b | ||
|
|
891a067bbd | ||
|
|
4325abab9d | ||
|
|
abc5d2bf63 | ||
|
|
cfe5780ada | ||
|
|
5259e5df55 | ||
|
|
c2e3b779d9 | ||
|
|
7523e43823 | ||
|
|
3e4730b44f | ||
|
|
8f30918d59 | ||
|
|
4b23bd9392 | ||
|
|
dbf3f60167 | ||
|
|
edf9d8c544 | ||
|
|
0b88411f88 | ||
|
|
b2589e00a6 | ||
|
|
b66db48a84 | ||
|
|
0841aa1587 | ||
|
|
d0ff9bbc35 | ||
|
|
340d6d3065 | ||
|
|
e8f15c4137 | ||
|
|
550d486954 | ||
|
|
5d185ec9f4 | ||
|
|
1411ac495e | ||
|
|
af029ac149 | ||
|
|
a924c75ac6 | ||
|
|
4f5376450a | ||
|
|
0608ca6396 | ||
|
|
426873da98 | ||
|
|
2dd6432c69 | ||
|
|
82314f50ba | ||
|
|
2423127877 | ||
|
|
5dea8919be | ||
|
|
af17c4617e | ||
|
|
d3010c0af8 | ||
|
|
e25c400778 | ||
|
|
e3abd1ef9a | ||
|
|
be30e46960 | ||
|
|
ee56ddc1ab | ||
|
|
a113946076 | ||
|
|
b18a249623 | ||
|
|
a46ff5e8d9 | ||
|
|
c52f7671a7 | ||
|
|
c3164d722d | ||
|
|
0bb3ac8440 | ||
|
|
06d3ba548e | ||
|
|
6f6fa98680 | ||
|
|
b6ef861338 | ||
|
|
3916c0dcf4 | ||
|
|
37441c149d | ||
|
|
fedef396eb | ||
|
|
468dcb05f9 | ||
|
|
70f41a635a | ||
|
|
e32ec1c687 | ||
|
|
3be05f2f0a | ||
|
|
10e80e47ca | ||
|
|
4f56c6e5e9 | ||
|
|
3cdaf2b358 | ||
|
|
f426b0db61 | ||
|
|
cbc6520dbf | ||
|
|
4fb76c01c3 | ||
|
|
4695c96998 | ||
|
|
6b080a2575 | ||
|
|
acf2801cbc | ||
|
|
8afa602a40 | ||
|
|
f4d021f87f | ||
|
|
b069265866 | ||
|
|
b5d65fda55 | ||
|
|
35c8a9380e | ||
|
|
b13b8d3c35 | ||
|
|
5e56a99163 | ||
|
|
f169813b2d | ||
|
|
2504b7849a | ||
|
|
cdbbd13939 | ||
|
|
e7b595c4ca | ||
|
|
84fdc7f1b4 | ||
|
|
6bb5f5cab4 | ||
|
|
ef5ad1de6d | ||
|
|
d8cfb649ad | ||
|
|
036144fdaf | ||
|
|
b79a71decd | ||
|
|
7c798c1e3b | ||
|
|
ffcba01df7 | ||
|
|
d595b9f26b | ||
|
|
71ed0ffae9 | ||
|
|
16310d6008 | ||
|
|
0855af39e7 | ||
|
|
48efea55ab | ||
|
|
3dbc775334 | ||
|
|
baf658365e | ||
|
|
646e03a227 | ||
|
|
ebdfc80d8b | ||
|
|
3f71163454 | ||
|
|
e2b6f63f99 | ||
|
|
76682e64d4 | ||
|
|
6d154799f9 | ||
|
|
3f3fad0eb7 | ||
|
|
41ac7790e2 | ||
|
|
28ecb90489 | ||
|
|
3693d307fc | ||
|
|
62c998a472 | ||
|
|
80b3857b08 | ||
|
|
f521409482 | ||
|
|
a1929d9eb7 | ||
|
|
a1073275c3 | ||
|
|
c48ab3bf8e | ||
|
|
d966ed8ca1 | ||
|
|
15600981fd | ||
|
|
39e8c758fc | ||
|
|
f8d3ad5167 | ||
|
|
66ebd8c536 | ||
|
|
9505ac6dd1 | ||
|
|
1346cbc222 | ||
|
|
3dfda1e293 | ||
|
|
ef9d82e61d | ||
|
|
b127a6c428 | ||
|
|
00ab106dd6 | ||
|
|
6b500a6aef | ||
|
|
9617aa1931 | ||
|
|
fca11410b4 | ||
|
|
4774c2e30a | ||
|
|
6a509ca478 | ||
|
|
72e004df23 | ||
|
|
e2ec017c6b | ||
|
|
655e1b5e7a | ||
|
|
4ad0f79643 | ||
|
|
3766a3fcc1 | ||
|
|
23e58ae223 | ||
|
|
9d859e0fae | ||
|
|
9178f38465 | ||
|
|
b19ceb8c0c | ||
|
|
fdd74ea224 | ||
|
|
013c4d6d0a | ||
|
|
0ddb811326 | ||
|
|
390f00063c | ||
|
|
508e1d08ed | ||
|
|
dfedd30b29 | ||
|
|
728926c4ea | ||
|
|
ce38883e64 | ||
|
|
8199c0ee36 | ||
|
|
6b3ea32799 | ||
|
|
d9d6c8772e | ||
|
|
ea1f851d94 | ||
|
|
2a6ef8d0c0 | ||
|
|
03493cf9ca | ||
|
|
5162a23607 | ||
|
|
a06cb47bd8 | ||
|
|
6a5d0f7560 | ||
|
|
2673004b5b | ||
|
|
c39b41fea1 | ||
|
|
bafab264f5 | ||
|
|
0901a24377 | ||
|
|
2b9655b382 | ||
|
|
472952e21c | ||
|
|
97e8341fe5 | ||
|
|
d7b20f6d0b | ||
|
|
e87551e2d5 | ||
|
|
7ee838319c | ||
|
|
e2d927f274 | ||
|
|
843a569c25 | ||
|
|
ec43a1b28e | ||
|
|
aa1c68901e | ||
|
|
ef41cd341e | ||
|
|
55e30b3420 | ||
|
|
2235b593b4 | ||
|
|
3afbafd38b | ||
|
|
b4ddfd776f | ||
|
|
23eb18826b | ||
|
|
bbab9f8e92 | ||
|
|
2e85cedcc4 | ||
|
|
d3e5463f1c | ||
|
|
e18d2b3b34 | ||
|
|
e33daed812 | ||
|
|
18d7cc0f3d | ||
|
|
c44a380092 | ||
|
|
18791c6b82 | ||
|
|
d7d1d264ef | ||
|
|
8a71d5241d | ||
|
|
5400e3fd65 | ||
|
|
16446373f1 | ||
|
|
194396f0f4 | ||
|
|
bb77fb8f9d | ||
|
|
e27deb071a | ||
|
|
9d2ffb907b | ||
|
|
0046e23bfe | ||
|
|
37d985e5f0 | ||
|
|
0c50e1859d | ||
|
|
f925e1814c | ||
|
|
0a45b45e68 | ||
|
|
984e3cdcd2 | ||
|
|
1d52e970a0 | ||
|
|
dc6740d9dc | ||
|
|
939378fcd1 | ||
|
|
23299457db | ||
|
|
e910f32931 | ||
|
|
e33ecb2c33 | ||
|
|
c80bf573a5 | ||
|
|
6549be1be5 | ||
|
|
e057ee8d22 | ||
|
|
e571df5832 | ||
|
|
f8d74d5884 | ||
|
|
479cb7d023 | ||
|
|
4c570e5de4 | ||
|
|
c80a2d3c50 | ||
|
|
eb5de0bdd5 | ||
|
|
f511cd4fca | ||
|
|
a7b743c75d | ||
|
|
a35cacae67 | ||
|
|
a765d1af50 | ||
|
|
21f117503e | ||
|
|
1b5b19c7c8 | ||
|
|
bf3d8a826d | ||
|
|
ac3f604211 | ||
|
|
5393b8a8be | ||
|
|
87137c633d | ||
|
|
6ddc237632 | ||
|
|
e08f12d944 | ||
|
|
9db244ebe5 | ||
|
|
9a35c0666d | ||
|
|
6024195229 | ||
|
|
0ab72d62ff | ||
|
|
ea12393efa | ||
|
|
74f38d93a9 | ||
|
|
fb6cd38b1d | ||
|
|
c4f5a1b02b | ||
|
|
5e1f596e60 | ||
|
|
645ea6f1aa | ||
|
|
d45f3eb34c | ||
|
|
23a501f4ac | ||
|
|
b9cd024c38 | ||
|
|
56da47aca5 | ||
|
|
7bfad80e00 | ||
|
|
073dca1e98 | ||
|
|
cbd3db5a75 | ||
|
|
e7db1803b1 | ||
|
|
e7dc940cd0 | ||
|
|
ff2f36ea17 | ||
|
|
184c14df4c | ||
|
|
33d19ebd1d | ||
|
|
4005aca92e | ||
|
|
4460ee313e | ||
|
|
53682de1eb | ||
|
|
423da1e03f | ||
|
|
73312bffd7 | ||
|
|
3ba0926251 | ||
|
|
9c9b19af26 | ||
|
|
b7191b11c2 | ||
|
|
9a36f9ff00 | ||
|
|
b060339573 | ||
|
|
930724b85b | ||
|
|
1497aacf59 | ||
|
|
7dcc589495 | ||
|
|
e3a259c2b4 | ||
|
|
18680437aa | ||
|
|
898d7489a6 | ||
|
|
2f5d601ccf | ||
|
|
a8f48baced | ||
|
|
d1e32be13b | ||
|
|
50d2aa48f5 | ||
|
|
858b0b488d | ||
|
|
9ff9ef6650 | ||
|
|
96ad0ff68c | ||
|
|
d0331c9afe | ||
|
|
4c9c4b0001 | ||
|
|
ba908284b9 | ||
|
|
e4ee703a09 | ||
|
|
66f555ab73 | ||
|
|
36f0e63d56 | ||
|
|
bb058a24b9 | ||
|
|
c709b16eab | ||
|
|
e8f0a873fa | ||
|
|
4778500e03 | ||
|
|
ef8039cacc | ||
|
|
356dd1d03e | ||
|
|
8018d0e330 | ||
|
|
f5baf819c0 | ||
|
|
6707ae33c9 | ||
|
|
db08499eaf | ||
|
|
85b91d19c4 | ||
|
|
ecda5e2cff | ||
|
|
aedb7235f9 | ||
|
|
2dff143070 | ||
|
|
8c128816f5 | ||
|
|
1f21fa5a9d | ||
|
|
c82c3b71d8 | ||
|
|
1697938587 | ||
|
|
a687f4a0bc | ||
|
|
66d1ce65f3 | ||
|
|
fd180d2e2c | ||
|
|
eaa1220af9 | ||
|
|
f26aa001eb | ||
|
|
589e6aa471 | ||
|
|
59d6e4d319 | ||
|
|
3dc7ad25ff | ||
|
|
e20278cf97 | ||
|
|
911dab5e52 | ||
|
|
f9e6b621a9 | ||
|
|
f605167a53 | ||
|
|
3af11575d7 | ||
|
|
9ac9771a0e | ||
|
|
0c4c4369a2 | ||
|
|
e7aae0cb0f | ||
|
|
02a4bf09ca | ||
|
|
ff03dbc834 | ||
|
|
14c404c552 | ||
|
|
b0d8fe700c | ||
|
|
615553cf3e | ||
|
|
c6781ae5fc | ||
|
|
bd3162f19b | ||
|
|
2a5782bde6 | ||
|
|
b22ff2bb2a | ||
|
|
11029a4285 | ||
|
|
4565a7e7d6 | ||
|
|
82144f6e4c | ||
|
|
90bbc2c39f | ||
|
|
98343784bf | ||
|
|
9045e13386 | ||
|
|
65da1056d5 | ||
|
|
7fa477f197 | ||
|
|
29c3e1dc06 | ||
|
|
21e6289c64 | ||
|
|
fbb7a17197 | ||
|
|
294582e589 | ||
|
|
779fd72226 | ||
|
|
9b70adb07b | ||
|
|
bd131246d8 | ||
|
|
ab38b2db62 | ||
|
|
3d84d288d1 | ||
|
|
85f14e8398 | ||
|
|
839d92da14 | ||
|
|
3a9de5b70f | ||
|
|
50c176ba48 | ||
|
|
8231cd654f | ||
|
|
4cdc7a47a3 | ||
|
|
82321d7370 | ||
|
|
49957f2536 | ||
|
|
2147584dca | ||
|
|
bafe10036f | ||
|
|
577aa25e50 | ||
|
|
2477d2d84c | ||
|
|
0f475ddba3 | ||
|
|
5161f6dfbf | ||
|
|
c28245b420 | ||
|
|
ec05c14225 | ||
|
|
5087e500b7 | ||
|
|
e698f5f06b | ||
|
|
205b6a1319 | ||
|
|
6580f4df4b | ||
|
|
81ec559e69 | ||
|
|
e76363c532 | ||
|
|
88a79e3d77 | ||
|
|
a4c941b744 | ||
|
|
68ac32ef56 | ||
|
|
f3bf935509 | ||
|
|
4379a3dc9c | ||
|
|
1efb3df8de | ||
|
|
ae98aa48ba | ||
|
|
16063a6714 | ||
|
|
5d5ed2b8de | ||
|
|
bc4d1d0051 | ||
|
|
9a9f425b7f | ||
|
|
12ce91c94b | ||
|
|
5b949f9a52 | ||
|
|
c1a31bb2fe | ||
|
|
571ba316da | ||
|
|
c81ba0196f | ||
|
|
399e5661f1 | ||
|
|
25ba21d9ee | ||
|
|
cb0dc261cf | ||
|
|
2412d90f71 | ||
|
|
067ce9edee | ||
|
|
b3e581f737 | ||
|
|
64afc6835a | ||
|
|
78a84959d2 | ||
|
|
b6cd3b227b | ||
|
|
ff903cd236 | ||
|
|
f82ab31b36 | ||
|
|
18e736e5d8 | ||
|
|
2d3902f478 | ||
|
|
94d5d2021a | ||
|
|
a4857a9d57 | ||
|
|
86f05b3cbe | ||
|
|
0bc26c950e | ||
|
|
97f7ba618c | ||
|
|
ff2853381c | ||
|
|
59423c358e | ||
|
|
b6e1f5f20f | ||
|
|
57a2e967d4 | ||
|
|
58865ee10d | ||
|
|
d9443232d8 | ||
|
|
e1138747d9 | ||
|
|
91599baf00 | ||
|
|
00c234c967 | ||
|
|
f2865615bd | ||
|
|
42b751dd45 | ||
|
|
d85f21722b | ||
|
|
19eb5923c3 | ||
|
|
fd01d355aa | ||
|
|
52740d56ac | ||
|
|
8a0ba0b3b0 | ||
|
|
c1d9dff75e | ||
|
|
187299b3d9 | ||
|
|
2fe16769ff | ||
|
|
39a3755b49 | ||
|
|
d8473993c4 | ||
|
|
edfcc92a85 | ||
|
|
a4dabfd7a9 | ||
|
|
1c21be2262 | ||
|
|
6b7d18d874 | ||
|
|
2aff955a99 | ||
|
|
fbca7ab99c | ||
|
|
a583540f21 | ||
|
|
ad3baacf5f | ||
|
|
cb3b33f63b | ||
|
|
d162c2b49f | ||
|
|
637f7fc819 | ||
|
|
f49f371eea | ||
|
|
2c52355f8d | ||
|
|
e5db7252c3 | ||
|
|
bb5fcbe7b3 | ||
|
|
e2e8fa091e | ||
|
|
009a9c93f6 | ||
|
|
197dcb07ec | ||
|
|
22f0b7e5e1 | ||
|
|
b927d8e241 | ||
|
|
73e460df80 | ||
|
|
b8bcfd6054 | ||
|
|
799c6578b8 | ||
|
|
d7db4190dd | ||
|
|
e70cfb3623 | ||
|
|
cd7b759c3a | ||
|
|
b2cb9f1940 | ||
|
|
33f8ed3e73 | ||
|
|
29fc629899 | ||
|
|
8dd0f7e168 | ||
|
|
4f3990c239 | ||
|
|
218cbcb933 | ||
|
|
1e0a43c903 | ||
|
|
b156727f3c | ||
|
|
ac8119dbbe | ||
|
|
d30af6e972 | ||
|
|
2071059695 | ||
|
|
50b02c17f0 | ||
|
|
fcb5e013d4 | ||
|
|
cdcdbbbaa4 | ||
|
|
c567045fb5 | ||
|
|
c2fa922717 | ||
|
|
d3a462aa06 | ||
|
|
ac5efb2eb9 | ||
|
|
1d77d295fa | ||
|
|
4a5f0b79b8 | ||
|
|
c280b40b12 | ||
|
|
7b33c93132 | ||
|
|
03f6a12baa | ||
|
|
3602a08e5d | ||
|
|
b4b2200377 | ||
|
|
9d2a5ed46f | ||
|
|
f5c5284ae1 | ||
|
|
48ad46d5e5 | ||
|
|
9a3a19bb9b | ||
|
|
96d5e94a5b | ||
|
|
64700208f6 | ||
|
|
a4e1f1267b | ||
|
|
70523ac38f | ||
|
|
747b080909 | ||
|
|
b68d59ca90 | ||
|
|
9c87a0ebab | ||
|
|
db8451a47e | ||
|
|
4e38d0c3b3 | ||
|
|
a9aafc3eb8 | ||
|
|
0014e413b0 | ||
|
|
4df4603762 | ||
|
|
2cbb49de48 | ||
|
|
6894578569 | ||
|
|
82f591a5d3 | ||
|
|
5552c8fc0d | ||
|
|
d481f72c52 | ||
|
|
7a29e3f056 | ||
|
|
643385d350 | ||
|
|
0088239fe6 | ||
|
|
eb20e86f9c | ||
|
|
b3c707092e | ||
|
|
c7252a2b90 | ||
|
|
d797e1aec1 | ||
|
|
4c8aeebafc | ||
|
|
fbacd49817 | ||
|
|
96f6bbc9f4 | ||
|
|
e1c63e51d3 | ||
|
|
cd713f1439 | ||
|
|
c861756836 | ||
|
|
1ddd8c22f6 | ||
|
|
a74f03158a | ||
|
|
0134656d0a | ||
|
|
f048d23cda | ||
|
|
c962971d7a | ||
|
|
7c510c1f9d | ||
|
|
703e25e1fe | ||
|
|
39b08bd9d5 | ||
|
|
60bf33ffab | ||
|
|
c34e900427 | ||
|
|
585637b4fe | ||
|
|
9576b882ef | ||
|
|
ddea0a223d | ||
|
|
0f2118d939 | ||
|
|
dd906aad95 | ||
|
|
095fc00319 | ||
|
|
1b26aaff1b | ||
|
|
a12222eac5 | ||
|
|
157cc70407 | ||
|
|
a8db31754d |
35 changed files with 6464 additions and 2320 deletions
37
.github/workflows/ci.yml
vendored
Normal file
37
.github/workflows/ci.yml
vendored
Normal 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
3
.gitignore
vendored
|
|
@ -9,4 +9,7 @@ pom.xml.asc
|
||||||
.lein-repl-history
|
.lein-repl-history
|
||||||
.lein-plugins/
|
.lein-plugins/
|
||||||
.lein-failures
|
.lein-failures
|
||||||
|
.cljs_node_repl
|
||||||
out/
|
out/
|
||||||
|
.cpcache
|
||||||
|
.cache
|
||||||
|
|
|
||||||
268
CHANGES.md
268
CHANGES.md
|
|
@ -1,11 +1,226 @@
|
||||||
## 0.9.3 (unreleased)
|
## 1.1.4
|
||||||
|
|
||||||
|
* Add arglist metadata to navs (thanks @phronmophobic)
|
||||||
|
* Improve before-index performance by 150x on lists and 5x on vectors (thanks @jeff303)
|
||||||
|
* Bug fix: BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs
|
||||||
|
* Add compatibility with [babashka](https://babashka.org/)
|
||||||
|
|
||||||
|
## 1.1.3 - 2019-10-13
|
||||||
|
|
||||||
|
* Better AOT behavior: path functions for inline caching and protpath extensions no longer write eval'd class files. You can force path functions to not override `*compile-files*` by binding `com.rpl.specter.impl/*path-compile-files*` to `true`.
|
||||||
|
* Bug fix: fix throw-illegal in cljs
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
* 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 - 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 - 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
|
||||||
|
* Added `eachnav` to turn any 1-argument navigator into a navigator that accepts any number of arguments, navigating by each argument in order
|
||||||
|
* `keypath` and `must` enhanced to take in multiple arguments for concisely specifying multiple steps
|
||||||
|
* Added `traversed`
|
||||||
|
* Bug fix: Fix regression from 0.13.0 where [ALL FIRST] on a PersistentArrayMap that created duplicate keys would create an invalid PersistentArrayMap
|
||||||
|
* 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 - 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.
|
||||||
|
* BREAKING CHANGE: All navigators must be defined with `defnav` and its variations from `com.rpl.specter`. The core protocols may no longer be extended. Existing types can be turned into navigators with the new `IndirectNav` protocol.
|
||||||
|
* BREAKING CHANGE: Removed `fixed-pathed-nav` and `variable-pathed-nav` and replaced with much more generic `late-bound-nav`. `late-bound-nav` can have normal values be late-bound parameterized (not just paths). Use `late-path` function to indicate which parameters are paths. If all bindings given to `late-bound-nav` are static, the navigator will be resolved and cached immediately. See `transformed` and `selected?` for examples.
|
||||||
|
* BREAKING CHANGE: Paths can no longer be compiled without their parameters. Instead, use the `path` macro to handle the parameterization.
|
||||||
|
* BREAKING CHANGE: Parameterized protocol paths now work differently since paths cannot be specified without their parameters. Instead, use the parameter names from the declaration in the extension to specify where the parameters should go. For example:
|
||||||
|
```clojure
|
||||||
|
(defprotocolpath MyProtPath [a])
|
||||||
|
(extend-protocolpath MyProtPath
|
||||||
|
clojure.lang.PersistentArrayMap
|
||||||
|
(must a))
|
||||||
|
```
|
||||||
|
* BREAKING CHANGE: Removed `defpathedfn` and replaced with much more generic `defdynamicnav`. `defdynamicnav` works similar to a macro and takes as input the parameters seen during inline caching. Use `dynamic-param?` to distinguish which parameters are statically specified and which are dynamic. `defdynamicnav` is typically used in conjunction with `late-bound-nav` – see implementation of `selected?` for an example.
|
||||||
|
* Inline caching now works with locals, dynamic vars, and special forms used in the nav position. When resolved at runtime, those values will be coerced to a navigator if a vector or implicit nav (e.g. keyword). Can hint with ^:direct-nav metadata to remove this coercion if know for sure those values will be implementations of `RichNavigator` interface.
|
||||||
|
* Redesigned internals so navigators use interface dispatch rather than storing transform/selection functions as separate fields.
|
||||||
|
* Added `local-declarepath` to assist in making local recursive or mutually recursive paths. Use with `providepath`.
|
||||||
|
* Added `recursive-path` to assist in making recursive paths, both parameterized and unparameterized. Example:
|
||||||
|
```clojure
|
||||||
|
(let [tree-walker (recursive-path [] p (if-path vector? [ALL p] STAY))]
|
||||||
|
(select tree-walker [1 [2 [3 4] 5] [[6]]]))
|
||||||
|
```
|
||||||
|
* Significantly improved performance of paths that use dynamic parameters.
|
||||||
|
* Inline factoring now parameterizes navigators immediately when all parameters are constants (rather than factoring it to use late-bound parameterization). This creates leaner, faster code.
|
||||||
|
* Added `IndirectNav` protocol for turning a value type into a navigator.
|
||||||
|
* Removed `must-cache-paths!`. No longer relevant since all paths can now be compiled and cached.
|
||||||
|
* Added `with-inline-debug` macro that prints information about the code being analyzed and produced by the inline compiler / cacher.
|
||||||
|
* Switched codebase from cljx to cljc
|
||||||
|
* Improved performance of ALL and MAP-VALS on PersistentArrayMap by about 2x
|
||||||
|
* `defnav` now generates helper functions for every method. For example, `keypath` now has helpers `keypath-select*` and `keypath-transform*`. These functions take parameters `[key structure next-fn]`
|
||||||
|
* Bug fix: ALL and MAP-VALS transforms on PersistentArrayMap above the threshold now output PersistentArrayMap instead of PersistentHashMap
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
* Added `selected-any?` operation that returns true if any element is navigated to.
|
||||||
|
* Added `traverse` operation which returns a reducible object of all the elements navigated to by the path. Very efficient.
|
||||||
|
* Added `multi-transform` operation which can be used to perform multiple transformations in a single traversal. Much more efficient than doing the
|
||||||
|
transformations with `transform` one after another when the transformations share a lot of navigation. `multi-transform` is used in conjunction with `terminal` and `terminal-val` – see the docstring for details.
|
||||||
|
* Huge performance improvements to `select`, `select-one`, `select-first`, and `select-one!`
|
||||||
|
* Huge performance improvement to `multi-path`
|
||||||
|
* Added META navigator (thanks @aengelberg)
|
||||||
|
* Added DISPENSE navigator to drop all collected values for subsequent navigation
|
||||||
|
* Added `collected?` macro to create a filter function which operates on the collected values.
|
||||||
|
* Error now thrown if a pathedfn (like filterer) is used without being parameterized
|
||||||
|
* Performance improvement for ALL and MAP-VALS on small maps for Clojure by leveraging IMapIterable interface
|
||||||
|
* Added low-level `richnav` macro for creating navigators with full flexibility
|
||||||
|
* Bug fix: multi-path and if-path now work properly with value collection
|
||||||
|
* Bug fix: END, BEGINNING, FIRST, LAST, and MAP-VALS now work properly on nil
|
||||||
|
* Bug fix: ALL and MAP-VALS now maintain the comparator of sorted maps
|
||||||
|
* 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 - 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 - 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
|
||||||
|
* Significant performance improvements for FIRST/LAST for vectors
|
||||||
|
* Huge performance improvements for `if-path`, `cond-path`, `selected?`, and `not-selected?`, especially for condition path containing only static functions
|
||||||
|
* Huge performance improvement for `END` on vectors
|
||||||
|
* Added specialized MAP-VALS navigator that is twice as fast as using [ALL LAST]
|
||||||
|
* Dropped support for Clojurescript below v1.7.10
|
||||||
|
* Added :notpath metadata to signify pathedfn arguments that should be treated as regular arguments during inline factoring. If one of these arguments is not a static var reference or non-collection value, the path will not factor.
|
||||||
|
* Bug fix: `transformed` transform-fn no longer factors into `pred` when an anonymous function during inline factoring
|
||||||
|
* 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 - 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.
|
||||||
|
* Added `must-cache-paths!` function to throw an error if it is not possible to factor a path into a static portion and dynamic parameters.
|
||||||
|
* BREAKING CHANGE: `defpath` renamed to `defnav`
|
||||||
|
* BREAKING CHANGE: `path` renamed to `nav`
|
||||||
|
* BREAKING CHANGE: `fixed-pathed-path` and `variable-pathed-path` renamed to `fixed-pathed-nav` and `variabled-pathed-nav`
|
||||||
|
* Added `must` navigator to navigate to a key if and only if it exists in the structure
|
||||||
|
* Added `continuous-subseqs` navigator
|
||||||
|
* Added `ATOM` navigator (thanks @rakeshp)
|
||||||
|
* 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 - 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)
|
||||||
|
* Added `parser` path (thanks @thomasathorne)
|
||||||
|
* Added `submap` path (thanks @bfabry)
|
||||||
|
* Added `subselect` path (thanks @aengelberg)
|
||||||
|
* Fix filterer to maintain the type of the input sequence in transforms
|
||||||
|
* Integrated zipper navigation into com.rpl.specter.zipper namespace
|
||||||
|
|
||||||
|
## 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
|
* ALL on maps auto-coerces MapEntry to vector, enabling smoother transformation of map keys
|
||||||
* declarepath can now be parameterized
|
* declarepath can now be parameterized
|
||||||
* Added params-reset which calls its path with the params index walked back by the number of params needed by its path. This enables recursive parameterized paths
|
* Added params-reset which calls its path with the params index walked back by the number of params needed by its path. This enables recursive parameterized paths
|
||||||
* Added convenience syntax for defprotocolpath with no params, e.g. (defprotocolpath foo)
|
* Added convenience syntax for defprotocolpath with no params, e.g. (defprotocolpath foo)
|
||||||
* Rename VOID to STOP
|
* Rename VOID to STOP
|
||||||
|
|
||||||
## 0.9.2
|
## 0.9.2 - 2016-01-26
|
||||||
|
|
||||||
* Added VOID selector which navigates nowhere
|
* Added VOID selector which navigates nowhere
|
||||||
* Better syntax checking for defpath
|
* Better syntax checking for defpath
|
||||||
* Fixed bug in protocol paths (#48)
|
* Fixed bug in protocol paths (#48)
|
||||||
|
|
@ -16,67 +231,82 @@
|
||||||
* Added declarepath and providepath, which enable arbitrary recursive or mutually recursive paths
|
* Added declarepath and providepath, which enable arbitrary recursive or mutually recursive paths
|
||||||
* Renamed paramspath to path
|
* Renamed paramspath to path
|
||||||
|
|
||||||
## 0.9.1
|
## 0.9.1 - 2016-01-05
|
||||||
|
|
||||||
* Fixed reflection in protocol path code
|
* Fixed reflection in protocol path code
|
||||||
* Optimized late-bound parameterization for JVM implementation by directly creating the object array rather than use object-array
|
* 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
|
* 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
|
* Fixed bug where comp-paths wouldn't work on lazy seqs in cljs
|
||||||
* Renamed defparamspath and defparamscollector to defpath and defcollector
|
* Renamed defparamspath and defparamscollector to defpath and defcollector
|
||||||
* For Clojure version only, implemented protocol paths (see #38)
|
* 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)
|
* 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 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)
|
* 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
|
* view can now be late-bound parameterized
|
||||||
* Added a late-bound parameterized version of using a function as a selector called "pred"
|
* 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
|
* Added paramsfn helper macro for defining filter functions that take late-bound parameters
|
||||||
* walker and codewalker can now be late-bound parameterized
|
* 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.
|
* 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
|
* Converted Specter built-in selectors to use late-bound parameterization when appropriate
|
||||||
* ALL, FIRST, and LAST are now precompiled
|
* ALL, FIRST, and LAST are now precompiled
|
||||||
|
|
||||||
## 0.6.2
|
## 0.6.2 - 2015-07-03
|
||||||
|
|
||||||
* Added not-selected? selector
|
* Added not-selected? selector
|
||||||
* Added transformed selector
|
* Added transformed selector
|
||||||
* Sped up CLJS implementation for comp-paths by replacing obj-extends? call with satisfies?
|
* Sped up CLJS implementation for comp-paths by replacing obj-extends? call with satisfies?
|
||||||
* Fixed CLJS implementation to extend core types appropriately
|
* Fixed CLJS implementation to extend core types appropriately
|
||||||
* Used not-native hint to enable direct method invocation to speed up CLJS implementation
|
* 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
|
* Huge speedup to ClojureScript implementation by optimizing field access
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0 - 2015-07-01
|
||||||
|
|
||||||
* Added ClojureScript compatibility
|
* Added ClojureScript compatibility
|
||||||
|
|
||||||
## 0.5.7
|
## 0.5.7 - 2015-06-30
|
||||||
|
|
||||||
* Fix bug in select-one! which wouldn't allow nil result
|
* Fix bug in select-one! which wouldn't allow nil result
|
||||||
|
|
||||||
## 0.5.6
|
## 0.5.6 - 2015-06-29
|
||||||
|
|
||||||
* Add multi-path implementation
|
* Add multi-path implementation
|
||||||
* change FIRST/LAST to select nothing on an empty sequence
|
* change FIRST/LAST to select nothing on an empty sequence
|
||||||
* Allow sets to be used directly as selectors (acts as filter)
|
* 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)
|
* 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?)
|
* 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
|
* 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)]
|
* 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
|
* Added putval for adding external values to collected values list
|
||||||
* nil is now interpreted as identity selector
|
* nil is now interpreted as identity selector
|
||||||
* empty selector is now interpreted as identity selector instead of producing error
|
* empty selector is now interpreted as identity selector instead of producing error
|
||||||
|
|
|
||||||
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal 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.
|
||||||
20
DEVELOPER.md
20
DEVELOPER.md
|
|
@ -1,13 +1,25 @@
|
||||||
# Running Clojure tests
|
# Running Clojure tests
|
||||||
|
|
||||||
```
|
```
|
||||||
lein cleantest
|
lein do clean, test
|
||||||
```
|
```
|
||||||
|
|
||||||
# Running ClojureScript tests
|
# Running ClojureScript tests
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rm -rf out/
|
lein javac
|
||||||
$ rlwrap java -cp `lein classpath` clojure.main repl.clj
|
lein doo node test-build once
|
||||||
cljs.user=> (require 'com.rpl.specter.cljs-test-runner)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
||||||
224
README.md
224
README.md
|
|
@ -1,12 +1,76 @@
|
||||||
# Specter
|
# Specter
|
||||||
|
|
||||||
Most of Clojure programming involves creating, manipulating, and transforming immutable values. However, as soon as your values become more complicated than a simple map or list – like a list of maps of maps – transforming these data structures becomes extremely cumbersome.
|
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 is a library (for both Clojure and ClojureScript) for doing these queries and transformations concisely, elegantly, and efficiently. These kinds of manipulations are so common when using Clojure – and so cumbersome without Specter – that Specter is in many ways Clojure's missing piece.
|
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:
|
||||||
|
|
||||||
|
**Example 1: Increment every even number nested within map of vector of maps**
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def data {:a [{:aa 1 :bb 2}
|
||||||
|
{:cc 3}]
|
||||||
|
:b [{:dd 4}]})
|
||||||
|
|
||||||
|
;; Manual Clojure
|
||||||
|
(defn map-vals [m afn]
|
||||||
|
(->> m (map (fn [[k v]] [k (afn v)])) (into (empty m))))
|
||||||
|
|
||||||
|
(map-vals data
|
||||||
|
(fn [v]
|
||||||
|
(mapv
|
||||||
|
(fn [m]
|
||||||
|
(map-vals
|
||||||
|
m
|
||||||
|
(fn [v] (if (even? v) (inc v) v))))
|
||||||
|
v)))
|
||||||
|
|
||||||
|
;; Specter
|
||||||
|
(transform [MAP-VALS ALL MAP-VALS even?] inc data)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Append a sequence of elements to a nested vector**
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def data {:a [1 2 3]})
|
||||||
|
|
||||||
|
;; Manual Clojure
|
||||||
|
(update data :a (fn [v] (into (if v v []) [4 5])))
|
||||||
|
|
||||||
|
;; Specter
|
||||||
|
(setval [:a END] [4 5] data)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 3: Increment the last odd number in a sequence**
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def data [1 2 3 4 5 6 7 8])
|
||||||
|
|
||||||
|
;; Manual Clojure
|
||||||
|
(let [idx (reduce-kv (fn [res i v] (if (odd? v) i res)) nil data)]
|
||||||
|
(if idx (update data idx inc) data))
|
||||||
|
|
||||||
|
;; Specter
|
||||||
|
(transform [(filterer odd?) LAST] inc data)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 4: Map a function over a sequence without changing the type or order of the sequence**
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Manual Clojure
|
||||||
|
(map inc data) ;; doesn't work, becomes a lazy sequence
|
||||||
|
(into (empty data) (map inc data)) ;; doesn't work, reverses the order of lists
|
||||||
|
|
||||||
|
;; Specter
|
||||||
|
(transform ALL inc data) ;; works for all Clojure datatypes with near-optimal efficiency
|
||||||
|
```
|
||||||
|
|
||||||
Specter is fully extensible. At its core, its just a protocol for how to navigate within a data structure. By extending this protocol, you can use Specter to navigate any data structure or object you have.
|
|
||||||
|
|
||||||
Specter does not sacrifice performance to achieve its elegance. Actually, Specter is faster than the limited facilities Clojure provides for doing nested operations. For example: the Specter equivalent to get-in runs 30% faster than get-in, and the Specter equivalent to update-in runs 5x faster than update-in. In each case the Specter code is equally as convenient.
|
|
||||||
|
|
||||||
# Latest Version
|
# Latest Version
|
||||||
|
|
||||||
|
|
@ -16,35 +80,58 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
|
||||||
|
|
||||||
# Learn Specter
|
# 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: Clojure's missing piece](https://www.youtube.com/watch?v=mXZxkpX5nt8)
|
- Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk)
|
||||||
|
- Note that this presentation was given before Specter's inline compilation/caching system was developed. You no longer need to do anything special to get near-optimal performance.
|
||||||
|
- 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 a single, well-documented file: [specter.cljx](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljx)
|
Specter's API is contained in these files:
|
||||||
|
|
||||||
|
- [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?
|
# 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/).
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
Here's how to increment all the even values for :a keys in a sequence of maps:
|
Increment all the values in maps of maps:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (use 'com.rpl.specter)
|
user> (use 'com.rpl.specter)
|
||||||
|
user> (transform [MAP-VALS MAP-VALS]
|
||||||
|
inc
|
||||||
|
{:a {:aa 1} :b {:ba -1 :bb 2}})
|
||||||
|
{:a {:aa 2}, :b {:ba 0, :bb 3}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Increment all the even values for :a keys in a sequence of maps:
|
||||||
|
|
||||||
|
```clojure
|
||||||
user> (transform [ALL :a even?]
|
user> (transform [ALL :a even?]
|
||||||
inc
|
inc
|
||||||
[{:a 1} {:a 2} {:a 4} {:a 3}])
|
[{:a 1} {:a 2} {:a 4} {:a 3}])
|
||||||
[{:a 1} {:a 3} {:a 5} {:a 3}]
|
[{:a 1} {:a 3} {:a 5} {:a 3}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to retrieve every number divisible by 3 out of a sequence of sequences:
|
Retrieve every number divisible by 3 out of a sequence of sequences:
|
||||||
```clojure
|
```clojure
|
||||||
user> (select [ALL ALL #(= 0 (mod % 3))]
|
user> (select [ALL ALL #(= 0 (mod % 3))]
|
||||||
[[1 2 3 4] [] [5 3 2 18] [2 4 6] [12]])
|
[[1 2 3 4] [] [5 3 2 18] [2 4 6] [12]])
|
||||||
[3 3 18 6 12]
|
[3 3 18 6 12]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to increment the last odd number in a sequence:
|
Increment the last odd number in a sequence:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (transform [(filterer odd?) LAST]
|
user> (transform [(filterer odd?) LAST]
|
||||||
|
|
@ -53,28 +140,49 @@ user> (transform [(filterer odd?) LAST]
|
||||||
[2 1 3 6 10 4 8]
|
[2 1 3 6 10 4 8]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to increment all the odd numbers between indexes 1 (inclusive) and 4 (exclusive):
|
Remove nils from a nested sequence:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
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
|
```clojure
|
||||||
user> (transform [(srange 1 4) ALL odd?] inc [0 1 2 3 4 5 6 7])
|
user> (transform [(srange 1 4) ALL odd?] inc [0 1 2 3 4 5 6 7])
|
||||||
[0 2 2 4 4 5 6 7]
|
[0 2 2 4 4 5 6 7]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to replace the subsequence from index 2 to 4 with [:a :b :c :d :e]:
|
Replace the subsequence from indices 2 to 4 with [:a :b :c :d :e]:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (setval (srange 2 4) [:a :b :c :d :e] [0 1 2 3 4 5 6 7 8 9])
|
user> (setval (srange 2 4) [:a :b :c :d :e] [0 1 2 3 4 5 6 7 8 9])
|
||||||
[0 1 :a :b :c :d :e 4 5 6 7 8 9]
|
[0 1 :a :b :c :d :e 4 5 6 7 8 9]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to concatenate the sequence [:a :b] to every nested sequence of a sequence:
|
Concatenate the sequence [:a :b] to every nested sequence of a sequence:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (setval [ALL END] [:a :b] [[1] '(1 2) [:c]])
|
user> (setval [ALL END] [:a :b] [[1] '(1 2) [:c]])
|
||||||
[[1 :a :b] (1 2 :a :b) [:c :a :b]]
|
[[1 :a :b] (1 2 :a :b) [:c :a :b]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to get all the numbers out of a map, no matter how they're nested:
|
Get all the numbers out of a data structure, no matter how they're nested:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (select (walker number?)
|
user> (select (walker number?)
|
||||||
|
|
@ -82,15 +190,15 @@ user> (select (walker number?)
|
||||||
[2 1 2 1 2 6 7 4]
|
[2 1 2 1 2 6 7 4]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's now to navigate via non-keyword keys:
|
Navigate with string keys:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (select [(keypath "a") (keypath "b")]
|
user> (select ["a" "b"]
|
||||||
{"a" {"b" 10}})
|
{"a" {"b" 10}})
|
||||||
[10]
|
[10]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to reverse the positions of all even numbers between indexes 4 and 11:
|
Reverse the positions of all even numbers between indices 4 and 11:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (transform [(srange 4 11) (filterer even?)]
|
user> (transform [(srange 4 11) (filterer even?)]
|
||||||
|
|
@ -99,19 +207,10 @@ user> (transform [(srange 4 11) (filterer even?)]
|
||||||
[0 1 2 3 10 5 8 7 6 9 4 11 12 13 14 15]
|
[0 1 2 3 10 5 8 7 6 9 4 11 12 13 14 15]
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how to decrement every value in a map:
|
Append [:c :d] to every subsequence that has at least two even numbers:
|
||||||
|
|
||||||
```clojure
|
|
||||||
user> (transform [ALL LAST]
|
|
||||||
dec
|
|
||||||
{:a 1 :b 3})
|
|
||||||
{:b 2 :a 0}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here's how to append [:c :d] to every subsequence that has at least two even numbers:
|
|
||||||
```clojure
|
```clojure
|
||||||
user> (setval [ALL
|
user> (setval [ALL
|
||||||
(selected? (filterer even?) (view count) #(>= % 2))
|
(selected? (filterer even?) (view count) (pred>= 2))
|
||||||
END]
|
END]
|
||||||
[:c :d]
|
[:c :d]
|
||||||
[[1 2 3 4 5 6] [7 0 -1] [8 8] []])
|
[[1 2 3 4 5 6] [7 0 -1] [8 8] []])
|
||||||
|
|
@ -127,12 +226,12 @@ user> (transform [ALL (collect-one :b) :a even?]
|
||||||
[{:b 3, :a 1} {:b -10, :a -8} {:b 10, :a 14} {:a 3}]
|
[{:b 3, :a 1} {:b -10, :a -8} {:b 10, :a 14} {:a 3}]
|
||||||
```
|
```
|
||||||
|
|
||||||
The transform function receives as arguments all the collected values followed by the navigated to value. So in this case `+` receives the value of the :b key followed by the value of the :a key, and the transform is performed to :a's value.
|
The transform function receives as arguments all the collected values followed by the navigated to value. So in this case `+` receives the value of the :b key followed by the value of the :a key, and the transform is performed to :a's value.
|
||||||
|
|
||||||
The four built-in ways for collecting values are `VAL`, `collect`, `collect-one`, and `putval`. `VAL` just adds whatever element it's currently on to the value list, while `collect` and `collect-one` take in a selector to navigate to the desired value. `collect` works just like `select` by finding a sequence of values, while `collect-one` expects to only navigate to a single value. Finally, `putval` adds an external value into the collected values list.
|
The four built-in ways for collecting values are `VAL`, `collect`, `collect-one`, and `putval`. `VAL` just adds whatever element it's currently on to the value list, while `collect` and `collect-one` take in a selector to navigate to the desired value. `collect` works just like `select` by finding a sequence of values, while `collect-one` expects to only navigate to a single value. Finally, `putval` adds an external value into the collected values list.
|
||||||
|
|
||||||
|
|
||||||
Here's how to increment the value for :a key by 10:
|
Increment the value for :a key by 10:
|
||||||
```clojure
|
```clojure
|
||||||
user> (transform [:a (putval 10)]
|
user> (transform [:a (putval 10)]
|
||||||
+
|
+
|
||||||
|
|
@ -162,7 +261,6 @@ You can make an "AccountPath" that dynamically chooses its path based on the typ
|
||||||
|
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(use 'com.rpl.specter.macros)
|
|
||||||
(defprotocolpath AccountPath [])
|
(defprotocolpath AccountPath [])
|
||||||
(extend-protocolpath AccountPath
|
(extend-protocolpath AccountPath
|
||||||
User :account
|
User :account
|
||||||
|
|
@ -181,7 +279,7 @@ user> (select [ALL AccountPath :funds]
|
||||||
[50 51 1 2]
|
[50 51 1 2]
|
||||||
```
|
```
|
||||||
|
|
||||||
The next example demonstrates recursive navigation. Here's how to double all the even numbers in a tree:
|
The next examples demonstrate recursive navigation. Here's one way to double all the even numbers in a tree:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defprotocolpath TreeWalker [])
|
(defprotocolpath TreeWalker [])
|
||||||
|
|
@ -194,42 +292,52 @@ The next example demonstrates recursive navigation. Here's how to double all the
|
||||||
;; => [:a 1 [4 [[[3]]] :e] [8 5 [12 7]]]
|
;; => [:a 1 [4 [[[3]]] :e] [8 5 [12 7]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
You can make `select` and `transform` work much faster by precompiling your selectors using the `comp-paths` function. There's about a 3x speed difference between the following two invocations of transform:
|
Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(def precompiled (comp-paths ALL :a even?))
|
(def TreeValues
|
||||||
|
(recursive-path [] p
|
||||||
|
(if-path vector?
|
||||||
|
[ALL p]
|
||||||
|
STAY
|
||||||
|
)))
|
||||||
|
|
||||||
(transform [ALL :a even?] inc structure)
|
|
||||||
(compiled-transform precompiled inc structure)
|
(transform (subselect TreeValues even?)
|
||||||
|
reverse
|
||||||
|
[1 2 [3 [[4]] 5] [6 [7 8] 9 [[10]]]]
|
||||||
|
)
|
||||||
|
;; => [1 10 [3 [[8]] 5] [6 [7 4] 9 [[2]]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on the details of the selector and the data being transformed, precompiling can sometimes provide more than a 10x speedup. Using Specter with precompilation generally gets the speed within a few percentage points of hand-optimized code.
|
# ClojureScript
|
||||||
|
|
||||||
You can even precompile selectors that require parameters! For example, `keypath` can be used to navigate into a map by any arbitrary key, such as numbers, strings, or your own types. One way to use `keypath` would be to parameterize it at the time you use it, like so:
|
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
|
```clojure
|
||||||
(defn foo [data k]
|
(:require [com.rpl.specter :as s])
|
||||||
(select [(keypath k) ALL odd?] data))
|
(:require [com.rpl.specter :as s :refer-macros [select transform]]) ;; add in the Specter macros that you need
|
||||||
```
|
```
|
||||||
|
|
||||||
It seems difficult to precompile the entire path because it is dependent on the argument `k` of `foo`. Specter gets around this by allowing you to precompile a path without its parameters and bind the parameters to the selector later, like so:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(def foo-path (comp-paths keypath ALL odd?))
|
|
||||||
(defn foo [data k]
|
|
||||||
(compiled-select (foo-path k) data))
|
|
||||||
```
|
|
||||||
|
|
||||||
This code will execute extremely efficiently.
|
|
||||||
|
|
||||||
When `comp-paths` is used on selectors that require parameters, the result of `comp-paths` will require parameters equal to the sum of the number of parameters required by each selector. It expects to receive those parameters in the order in which the selectors were declared. This feature, called "late-bound parameterization", also works on selectors which themselves take in selector paths, such as `selected?`, `filterer`, and `transformed`.
|
|
||||||
|
|
||||||
|
|
||||||
# Future work
|
# 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.
|
- 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
|
# License
|
||||||
|
|
||||||
Copyright 2015-2016 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.
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.9.3-SNAPSHOT
|
1.1.5-SNAPSHOT
|
||||||
|
|
|
||||||
19
bb.edn
Normal file
19
bb.edn
Normal 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*)}}}
|
||||||
59
project.clj
59
project.clj
|
|
@ -1,34 +1,43 @@
|
||||||
(def VERSION (.trim (slurp "VERSION")))
|
(def VERSION (.trim (slurp "VERSION")))
|
||||||
|
|
||||||
(defproject com.rpl/specter VERSION
|
(defproject com.rpl/specter VERSION
|
||||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
|
||||||
[org.clojure/clojurescript "0.0-3211"]
|
|
||||||
]
|
|
||||||
:jvm-opts ["-XX:-OmitStackTraceInFastThrow"] ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions
|
:jvm-opts ["-XX:-OmitStackTraceInFastThrow"] ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions
|
||||||
|
;"-agentpath:/Applications/YourKit_Java_Profiler_2015_build_15056.app/Contents/Resources/bin/mac/libyjpagent.jnilib"]
|
||||||
|
|
||||||
:source-paths ["src/clj"]
|
:source-paths ["src/clj"]
|
||||||
:java-source-paths ["src/java"]
|
:java-source-paths ["src/java"]
|
||||||
:test-paths ["test", "target/test-classes"]
|
:test-paths ["test", "target/test-classes"]
|
||||||
:jar-exclusions [#"\.cljx"]
|
|
||||||
:auto-clean false
|
:auto-clean false
|
||||||
|
:dependencies [[riddley "0.1.12"]]
|
||||||
|
: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
|
||||||
|
com.rpl.specter.protocols
|
||||||
|
com.rpl.specter.transients]
|
||||||
|
:source-uri
|
||||||
|
{#"target/classes" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}x#L{line}"
|
||||||
|
#".*" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}#L{line}"}}
|
||||||
|
|
||||||
|
|
||||||
|
:cljsbuild {:builds [{:id "test-build"
|
||||||
|
:source-paths ["src/clj" "target/classes" "test"]
|
||||||
|
:compiler {:output-to "out/testable.js"
|
||||||
|
:main 'com.rpl.specter.cljs-test-runner
|
||||||
|
:target :nodejs
|
||||||
|
:optimizations :none}}]}
|
||||||
|
|
||||||
:profiles {:dev {:dependencies
|
:profiles {:dev {:dependencies
|
||||||
[[org.clojure/test.check "0.7.0"]]
|
[[org.clojure/test.check "0.9.0"]
|
||||||
:plugins
|
[org.clojure/clojure "1.9.0"]
|
||||||
[[com.keminglabs/cljx "0.6.0"]]
|
[org.clojure/clojurescript "1.10.439"]]}
|
||||||
:cljx {:builds [{:source-paths ["src/clj"]
|
:bench {:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
:output-path "target/classes"
|
[criterium "0.4.4"]]}
|
||||||
:rules :clj}
|
:test {:dependencies [[org.clojure/clojure "1.7.0"]]}}
|
||||||
{:source-paths ["src/clj"]
|
|
||||||
:output-path "target/classes"
|
:deploy-repositories
|
||||||
:rules :cljs}
|
[["clojars" {:url "https://repo.clojars.org"
|
||||||
{:source-paths ["test"]
|
:sign-releases false}]]
|
||||||
:output-path "target/test-classes"
|
|
||||||
:rules :clj}
|
:aliases {"deploy" ["do" "clean," "deploy" "clojars"]})
|
||||||
{:source-paths ["test"]
|
|
||||||
:output-path "target/test-classes"
|
|
||||||
:rules :cljs}]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:aliases {"cleantest" ["do" "clean,"
|
|
||||||
"cljx" "once,"
|
|
||||||
"test"]
|
|
||||||
"deploy" ["do" "clean," "cljx" "once," "deploy" "clojars"]})
|
|
||||||
|
|
|
||||||
12
repl.clj
12
repl.clj
|
|
@ -1,12 +0,0 @@
|
||||||
(require 'cljs.repl)
|
|
||||||
(require 'cljs.build.api)
|
|
||||||
(require 'cljs.repl.node)
|
|
||||||
|
|
||||||
(cljs.build.api/build "target/classes/com/rpl"
|
|
||||||
{:output-to "out/main.js"
|
|
||||||
:verbose true})
|
|
||||||
|
|
||||||
(cljs.repl/repl (cljs.repl.node/repl-env)
|
|
||||||
:watch "target/classes/com/rpl"
|
|
||||||
:output-dir "out"
|
|
||||||
:static-fns true)
|
|
||||||
357
scripts/benchmarks.clj
Normal file
357
scripts/benchmarks.clj
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
(ns com.rpl.specter.benchmarks
|
||||||
|
(:use [com.rpl.specter]
|
||||||
|
[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 mean [a-fn]
|
||||||
|
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
|
||||||
|
|
||||||
|
(defn compare-benchmark [afn-map]
|
||||||
|
(let [results (transform MAP-VALS mean afn-map)
|
||||||
|
[[_ best-time] & _ :as sorted] (sort-by last results)]
|
||||||
|
(println "\nMean(us)\tvs best\t\tCode")
|
||||||
|
(doseq [[k t] sorted]
|
||||||
|
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
||||||
|
|
||||||
|
(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 b c) data))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-k [k] (fn [m next] (next (get m k))))
|
||||||
|
|
||||||
|
(def get-a-b-c
|
||||||
|
(reduce
|
||||||
|
(fn [curr afn]
|
||||||
|
(fn [structure]
|
||||||
|
(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"
|
||||||
|
(select-any [:a :b :c] data)
|
||||||
|
(select-any (keypath :a :b :c) data)
|
||||||
|
(select-one [:a :b :c] data)
|
||||||
|
(select-first [:a :b :c] data)
|
||||||
|
(select-one! [:a :b :c] data)
|
||||||
|
(compiled-select-any p data)
|
||||||
|
(specter-dynamic-nested-get data :a :b :c)
|
||||||
|
(get-in data [:a :b :c])
|
||||||
|
(get-a-b-c data)
|
||||||
|
(-> data :a :b :c identity)
|
||||||
|
(-> data (get :a) (get :b) (get :c))
|
||||||
|
(-> data :a :b :c)
|
||||||
|
(select-any [(keypath :a) (keypath :b) (keypath :c)] data)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [data {:a {:b {:c 1}}}]
|
||||||
|
(run-benchmark "set value in nested map"
|
||||||
|
(assoc-in data [:a :b :c] 1)
|
||||||
|
(setval [:a :b :c] 1 data)))
|
||||||
|
|
||||||
|
|
||||||
|
;; because below 1.7 there is no update function
|
||||||
|
(defn- my-update [m k afn]
|
||||||
|
(assoc m k (afn (get m k))))
|
||||||
|
|
||||||
|
(defn manual-transform [m afn]
|
||||||
|
(my-update m :a
|
||||||
|
(fn [m2]
|
||||||
|
(my-update m2 :b
|
||||||
|
(fn [m3]
|
||||||
|
(my-update m3 :c afn))))))
|
||||||
|
|
||||||
|
(let [data {:a {:b {:c 1}}}]
|
||||||
|
(run-benchmark "update value in nested map"
|
||||||
|
(update-in data [:a :b :c] inc)
|
||||||
|
(transform [:a :b :c] inc data)
|
||||||
|
(manual-transform data inc)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn map-vals-map-iterable [^clojure.lang.IMapIterable m afn]
|
||||||
|
(let [k-it (.keyIterator m)
|
||||||
|
v-it (.valIterator m)]
|
||||||
|
(loop [ret {}]
|
||||||
|
(if (.hasNext k-it)
|
||||||
|
(let [k (.next k-it)
|
||||||
|
v (.next v-it)]
|
||||||
|
(recur (assoc ret k (afn v))))
|
||||||
|
|
||||||
|
ret))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn map-vals-map-iterable-transient [^clojure.lang.IMapIterable m afn]
|
||||||
|
(persistent!
|
||||||
|
(let [k-it (.keyIterator m)
|
||||||
|
v-it (.valIterator m)]
|
||||||
|
(loop [ret (transient {})]
|
||||||
|
(if (.hasNext k-it)
|
||||||
|
(let [k (.next k-it)
|
||||||
|
v (.next v-it)]
|
||||||
|
(recur (assoc! ret k (afn v))))
|
||||||
|
|
||||||
|
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"
|
||||||
|
(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))
|
||||||
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) (empty data) data)
|
||||||
|
(transform [ALL LAST] inc data)
|
||||||
|
(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 (->> (for [i (range 1000)] [i i]) (into {}))]
|
||||||
|
(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))
|
||||||
|
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient clojure.lang.PersistentHashMap/EMPTY) data))
|
||||||
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) (empty data) data)
|
||||||
|
(transform [ALL LAST] inc data)
|
||||||
|
(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"
|
||||||
|
(vec (map inc data))
|
||||||
|
(mapv inc data)
|
||||||
|
(transform ALL inc data)
|
||||||
|
(into [] (map inc) data)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||||
|
(run-benchmark "filter a sequence"
|
||||||
|
(doall (filter even? data))
|
||||||
|
(filterv even? data)
|
||||||
|
(select [ALL even?] data)
|
||||||
|
(select-any (filterer even?) data)
|
||||||
|
(into [] (filter even?) data)))
|
||||||
|
|
||||||
|
|
||||||
|
(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"
|
||||||
|
(select [ALL :a even?] data)
|
||||||
|
(->> data (mapv :a) (filter even?) doall)
|
||||||
|
(into [] (comp (map :a) (filter even?)) data)
|
||||||
|
(into [] xf data)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [v (vec (range 1000))]
|
||||||
|
(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)
|
||||||
|
|
||||||
|
(providepath TreeValues
|
||||||
|
(if-path vector?
|
||||||
|
[ALL TreeValues]
|
||||||
|
STAY))
|
||||||
|
|
||||||
|
(defprotocolpath TreeValuesProt)
|
||||||
|
|
||||||
|
(extend-protocolpath TreeValuesProt
|
||||||
|
clojure.lang.PersistentVector [ALL TreeValuesProt]
|
||||||
|
Object STAY)
|
||||||
|
|
||||||
|
|
||||||
|
(defn tree-value-transform [afn atree]
|
||||||
|
(if (vector? atree)
|
||||||
|
(mapv #(tree-value-transform afn %) atree)
|
||||||
|
(afn atree)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
|
||||||
|
(run-benchmark "update every value in a tree (represented with vectors)"
|
||||||
|
(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)
|
||||||
|
(transform [TreeValuesProt even?] inc data)
|
||||||
|
(tree-value-transform (fn [e] (if (even? e) (inc e) e)) data)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [toappend (range 1000)]
|
||||||
|
(run-benchmark "transient comparison: building up vectors"
|
||||||
|
(reduce (fn [v i] (conj v i)) [] toappend)
|
||||||
|
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
||||||
|
(setval END toappend [])
|
||||||
|
(setval END! toappend (transient []))))
|
||||||
|
|
||||||
|
(let [toappend (range 1000)]
|
||||||
|
(run-benchmark "transient comparison: building up vectors one at a time"
|
||||||
|
(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)
|
||||||
|
(reduce (fn [v i] (setval END! [i] v)) (transient []) toappend)))
|
||||||
|
|
||||||
|
|
||||||
|
(let [data (vec (range 1000))
|
||||||
|
tdata (transient data)
|
||||||
|
tdata2 (transient data)]
|
||||||
|
(run-benchmark "transient comparison: assoc'ing in vectors"
|
||||||
|
(assoc data 600 0)
|
||||||
|
(assoc! tdata 600 0)
|
||||||
|
(setval (keypath 600) 0 data)
|
||||||
|
(setval (keypath! 600) 0 tdata2)))
|
||||||
|
|
||||||
|
(let [data (into {} (for [k (range 1000)]
|
||||||
|
[k (rand)]))
|
||||||
|
tdata (transient data)
|
||||||
|
tdata2 (transient data)]
|
||||||
|
(run-benchmark "transient comparison: assoc'ing in maps"
|
||||||
|
(assoc data 600 0)
|
||||||
|
(assoc! tdata 600 0)
|
||||||
|
(setval (keypath 600) 0 data)
|
||||||
|
(setval (keypath! 600) 0 tdata2)))
|
||||||
|
|
||||||
|
(defn modify-submap
|
||||||
|
[m]
|
||||||
|
(assoc m 0 1 458 89))
|
||||||
|
|
||||||
|
(let [data (into {} (for [k (range 1000)]
|
||||||
|
[k (rand)]))
|
||||||
|
tdata (transient data)]
|
||||||
|
(run-benchmark "transient comparison: submap"
|
||||||
|
(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"
|
||||||
|
(with-meta data meta-map)
|
||||||
|
(setval META meta-map data)))
|
||||||
|
|
||||||
|
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||||
|
(run-benchmark "get metadata"
|
||||||
|
(meta data)
|
||||||
|
(select-any META data)))
|
||||||
|
|
||||||
|
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||||
|
(run-benchmark "vary metadata"
|
||||||
|
(vary-meta data assoc :y 2)
|
||||||
|
(setval [META :y] 2 data)))
|
||||||
|
|
||||||
|
(let [data (range 1000)]
|
||||||
|
(run-benchmark "Traverse into a set"
|
||||||
|
(set data)
|
||||||
|
(set (select ALL data))
|
||||||
|
(into #{} (traverse ALL data))
|
||||||
|
(persistent!
|
||||||
|
(reduce conj! (transient #{}) (traverse ALL data)))
|
||||||
|
(reduce conj #{} (traverse ALL data))))
|
||||||
|
|
||||||
|
|
||||||
|
(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"
|
||||||
|
(->> 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"
|
||||||
|
(->> 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
4
scripts/cljs-repl.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
rlwrap java -cp `lein classpath` clojure.main scripts/repl.clj
|
||||||
|
|
||||||
5
scripts/repl.clj
Normal file
5
scripts/repl.clj
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
(require
|
||||||
|
'[cljs.repl :as repl]
|
||||||
|
'[cljs.repl.node :as node])
|
||||||
|
|
||||||
|
(repl/repl (node/repl-env))
|
||||||
8
scripts/run-benchmarks
Executable file
8
scripts/run-benchmarks
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
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 "$@"
|
||||||
1506
src/clj/com/rpl/specter.cljc
Normal file
1506
src/clj/com/rpl/specter.cljc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,450 +0,0 @@
|
||||||
(ns com.rpl.specter
|
|
||||||
#+cljs (:require-macros
|
|
||||||
[com.rpl.specter.macros
|
|
||||||
:refer
|
|
||||||
[pathed-collector
|
|
||||||
variable-pathed-path
|
|
||||||
fixed-pathed-path
|
|
||||||
defcollector
|
|
||||||
defpath
|
|
||||||
]]
|
|
||||||
)
|
|
||||||
(:use [com.rpl.specter.protocols :only [StructurePath]]
|
|
||||||
#+clj [com.rpl.specter.macros :only
|
|
||||||
[pathed-collector
|
|
||||||
variable-pathed-path
|
|
||||||
fixed-pathed-path
|
|
||||||
defcollector
|
|
||||||
defpath]]
|
|
||||||
)
|
|
||||||
(:require [com.rpl.specter.impl :as i]
|
|
||||||
[clojure.set :as set])
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn comp-paths [& paths]
|
|
||||||
(i/comp-paths* (vec paths)))
|
|
||||||
|
|
||||||
;; Selector functions
|
|
||||||
|
|
||||||
(def ^{:doc "Version of select that takes in a selector pre-compiled with comp-paths"}
|
|
||||||
compiled-select i/compiled-select*)
|
|
||||||
|
|
||||||
(defn select
|
|
||||||
"Navigates to and returns a sequence of all the elements specified by the selector."
|
|
||||||
[selector structure]
|
|
||||||
(compiled-select (i/comp-paths* selector)
|
|
||||||
structure))
|
|
||||||
|
|
||||||
(defn compiled-select-one
|
|
||||||
"Version of select-one that takes in a selector pre-compiled with comp-paths"
|
|
||||||
[selector structure]
|
|
||||||
(let [res (compiled-select selector structure)]
|
|
||||||
(when (> (count res) 1)
|
|
||||||
(i/throw-illegal "More than one element found for params: " selector structure))
|
|
||||||
(first res)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn select-one
|
|
||||||
"Like select, but returns either one element or nil. Throws exception if multiple elements found"
|
|
||||||
[selector structure]
|
|
||||||
(compiled-select-one (i/comp-paths* selector) structure))
|
|
||||||
|
|
||||||
(defn compiled-select-one!
|
|
||||||
"Version of select-one! that takes in a selector pre-compiled with comp-paths"
|
|
||||||
[selector structure]
|
|
||||||
(let [res (compiled-select selector structure)]
|
|
||||||
(when (not= 1 (count res)) (i/throw-illegal "Expected exactly one element for params: " selector structure))
|
|
||||||
(first res)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn select-one!
|
|
||||||
"Returns exactly one element, throws exception if zero or multiple elements found"
|
|
||||||
[selector structure]
|
|
||||||
(compiled-select-one! (i/comp-paths* selector) structure))
|
|
||||||
|
|
||||||
(defn compiled-select-first
|
|
||||||
"Version of select-first that takes in a selector pre-compiled with comp-paths"
|
|
||||||
[selector structure]
|
|
||||||
(first (compiled-select selector structure)))
|
|
||||||
|
|
||||||
(defn select-first
|
|
||||||
"Returns first element found. Not any more efficient than select, just a convenience"
|
|
||||||
[selector structure]
|
|
||||||
(compiled-select-first (i/comp-paths* selector) structure))
|
|
||||||
|
|
||||||
|
|
||||||
;; Transform functions
|
|
||||||
|
|
||||||
(def ^{:doc "Version of transform that takes in a selector pre-compiled with comp-paths"}
|
|
||||||
compiled-transform i/compiled-transform*)
|
|
||||||
|
|
||||||
(defn transform
|
|
||||||
"Navigates to each value specified by the selector and replaces it by the result of running
|
|
||||||
the transform-fn on it"
|
|
||||||
[selector transform-fn structure]
|
|
||||||
(compiled-transform (i/comp-paths* selector) transform-fn structure))
|
|
||||||
|
|
||||||
(defn compiled-setval
|
|
||||||
"Version of setval that takes in a selector pre-compiled with comp-paths"
|
|
||||||
[selector val structure]
|
|
||||||
(compiled-transform selector (fn [_] val) structure))
|
|
||||||
|
|
||||||
(defn setval
|
|
||||||
"Navigates to each value specified by the selector and replaces it by val"
|
|
||||||
[selector val structure]
|
|
||||||
(compiled-setval (i/comp-paths* selector) val structure))
|
|
||||||
|
|
||||||
(defn compiled-replace-in
|
|
||||||
"Version of replace-in that takes in a selector pre-compiled with comp-paths"
|
|
||||||
[selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
|
|
||||||
(let [state (i/mutable-cell nil)]
|
|
||||||
[(compiled-transform selector
|
|
||||||
(fn [& args]
|
|
||||||
(let [res (apply transform-fn args)]
|
|
||||||
(if res
|
|
||||||
(let [[ret user-ret] res]
|
|
||||||
(->> user-ret
|
|
||||||
(merge-fn (i/get-cell state))
|
|
||||||
(i/set-cell! state))
|
|
||||||
ret)
|
|
||||||
(last args)
|
|
||||||
)))
|
|
||||||
structure)
|
|
||||||
(i/get-cell state)]
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn replace-in
|
|
||||||
"Similar to transform, except returns a pair of [transformed-structure sequence-of-user-ret].
|
|
||||||
The transform-fn in this case is expected to return [ret user-ret]. ret is
|
|
||||||
what's used to transform the data structure, while user-ret will be added to the user-ret sequence
|
|
||||||
in the final return. replace-in is useful for situations where you need to know the specific values
|
|
||||||
of what was transformed in the data structure."
|
|
||||||
[selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
|
|
||||||
(compiled-replace-in (i/comp-paths* selector) transform-fn structure :merge-fn merge-fn))
|
|
||||||
|
|
||||||
;; Helpers for defining selectors and collectors with late-bound params
|
|
||||||
|
|
||||||
(def ^{:doc "Takes a compiled selector that needs late-bound params and supplies it with
|
|
||||||
an array of params and a position in the array from which to begin reading
|
|
||||||
params. The return value is an executable selector."}
|
|
||||||
bind-params* i/bind-params*)
|
|
||||||
|
|
||||||
(defn params-reset [params-path]
|
|
||||||
;; TODO: error if not paramsneededpath
|
|
||||||
(let [s (i/params-needed-selector params-path)
|
|
||||||
t (i/params-needed-transformer params-path)
|
|
||||||
needed (i/num-needed-params params-path)]
|
|
||||||
(i/->ParamsNeededPath
|
|
||||||
(i/->TransformFunctions
|
|
||||||
i/RichPathExecutor
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(s params (- params-idx needed) vals structure next-fn)
|
|
||||||
)
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(t params (- params-idx needed) vals structure next-fn)
|
|
||||||
))
|
|
||||||
0)))
|
|
||||||
|
|
||||||
;; Built-in pathing and context operations
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{:doc "Stops navigation at this point. For selection returns nothing and for
|
|
||||||
transformation returns the structure unchanged"}
|
|
||||||
STOP
|
|
||||||
[]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
nil )
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
structure
|
|
||||||
))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{:doc "Stays navigated at the current point. Essentially a no-op selector."}
|
|
||||||
STAY
|
|
||||||
[]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn structure))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(next-fn structure)))
|
|
||||||
|
|
||||||
(def ALL (comp-paths (i/->AllStructurePath)))
|
|
||||||
|
|
||||||
(def VAL (i/->ValCollect))
|
|
||||||
|
|
||||||
(def LAST (comp-paths (i/->PosStructurePath last i/set-last)))
|
|
||||||
|
|
||||||
(def FIRST (comp-paths (i/->PosStructurePath first i/set-first)))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{: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."}
|
|
||||||
srange-dynamic
|
|
||||||
[start-fn end-fn]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/srange-select structure (start-fn structure) (end-fn structure) next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/srange-transform structure (start-fn structure) (end-fn structure) next-fn)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{:doc "Navigates to the subsequence bound by the indexes start (inclusive)
|
|
||||||
and end (exclusive)"}
|
|
||||||
srange
|
|
||||||
[start end]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/srange-select structure start end next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/srange-transform structure start end next-fn)
|
|
||||||
))
|
|
||||||
|
|
||||||
(def BEGINNING (srange 0 0))
|
|
||||||
|
|
||||||
(def END (srange-dynamic count count))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{:doc "Navigates to the specified subset (by taking an intersection).
|
|
||||||
In a transform, that subset in the original set is changed to the
|
|
||||||
new value of the subset."}
|
|
||||||
subset
|
|
||||||
[aset]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (set/intersection structure aset)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(let [subset (set/intersection structure aset)
|
|
||||||
newset (next-fn subset)]
|
|
||||||
(-> structure
|
|
||||||
(set/difference subset)
|
|
||||||
(set/union newset))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
walker
|
|
||||||
[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)))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
codewalker
|
|
||||||
[afn]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/walk-select afn next-fn structure))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/codewalk-until afn next-fn structure)))
|
|
||||||
|
|
||||||
(defn filterer
|
|
||||||
"Navigates to a view of the current sequence that only contains elements that
|
|
||||||
match the given selector path. An element matches the selector path if calling select
|
|
||||||
on that element with the selector path yields anything other than an empty sequence.
|
|
||||||
|
|
||||||
The input path may be parameterized, in which case the result of filterer
|
|
||||||
will be parameterized in the order of which the parameterized selectors
|
|
||||||
were declared."
|
|
||||||
[& path]
|
|
||||||
(fixed-pathed-path [late path]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(->> structure (filter #(i/selected?* late %)) doall next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(let [[filtered ancestry] (i/filter+ancestry late structure)
|
|
||||||
;; the vec is necessary so that we can get by index later
|
|
||||||
;; (can't get by index for cons'd lists)
|
|
||||||
next (vec (next-fn filtered))]
|
|
||||||
(reduce (fn [curr [newi oldi]]
|
|
||||||
(assoc curr oldi (get next newi)))
|
|
||||||
(vec structure)
|
|
||||||
ancestry))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defpath keypath [key]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (get structure key)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(assoc structure key (next-fn (get structure key)))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defpath view [afn]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (afn structure)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(next-fn (afn structure))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn selected?
|
|
||||||
"Filters the current value based on whether a selector finds anything.
|
|
||||||
e.g. (selected? :vals ALL even?) keeps the current element only if an
|
|
||||||
even number exists for the :vals key.
|
|
||||||
|
|
||||||
The input path may be parameterized, in which case the result of selected?
|
|
||||||
will be parameterized in the order of which the parameterized selectors
|
|
||||||
were declared."
|
|
||||||
[& path]
|
|
||||||
(fixed-pathed-path [late path]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/filter-select
|
|
||||||
#(i/selected?* late %)
|
|
||||||
structure
|
|
||||||
next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/filter-transform
|
|
||||||
#(i/selected?* late %)
|
|
||||||
structure
|
|
||||||
next-fn))))
|
|
||||||
|
|
||||||
(defn not-selected? [& path]
|
|
||||||
(fixed-pathed-path [late path]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/filter-select
|
|
||||||
#(i/not-selected?* late %)
|
|
||||||
structure
|
|
||||||
next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/filter-transform
|
|
||||||
#(i/not-selected?* late %)
|
|
||||||
structure
|
|
||||||
next-fn))))
|
|
||||||
|
|
||||||
(defn transformed
|
|
||||||
"Navigates to a view of the current value by transforming it with the
|
|
||||||
specified selector and update-fn.
|
|
||||||
|
|
||||||
The input path may be parameterized, in which case the result of transformed
|
|
||||||
will be parameterized in the order of which the parameterized selectors
|
|
||||||
were declared."
|
|
||||||
[path update-fn]
|
|
||||||
(fixed-pathed-path [late path]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (compiled-transform late update-fn structure)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(next-fn (compiled-transform late update-fn structure)))))
|
|
||||||
|
|
||||||
(extend-type #+clj clojure.lang.Keyword #+cljs cljs.core/Keyword
|
|
||||||
StructurePath
|
|
||||||
(select* [kw structure next-fn]
|
|
||||||
(next-fn (get structure kw)))
|
|
||||||
(transform* [kw structure next-fn]
|
|
||||||
(assoc structure kw (next-fn (get structure kw)))
|
|
||||||
))
|
|
||||||
|
|
||||||
(extend-type #+clj clojure.lang.AFn #+cljs function
|
|
||||||
StructurePath
|
|
||||||
(select* [afn structure next-fn]
|
|
||||||
(i/filter-select afn structure next-fn))
|
|
||||||
(transform* [afn structure next-fn]
|
|
||||||
(i/filter-transform afn structure next-fn)))
|
|
||||||
|
|
||||||
(extend-type #+clj clojure.lang.PersistentHashSet #+cljs cljs.core/PersistentHashSet
|
|
||||||
StructurePath
|
|
||||||
(select* [aset structure next-fn]
|
|
||||||
(i/filter-select aset structure next-fn))
|
|
||||||
(transform* [aset structure next-fn]
|
|
||||||
(i/filter-transform aset structure next-fn)))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{: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."}
|
|
||||||
pred
|
|
||||||
[afn]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(i/filter-select afn structure next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(i/filter-transform afn structure next-fn)))
|
|
||||||
|
|
||||||
(defpath
|
|
||||||
^{:doc "Navigates to the provided val if the structure is nil. Otherwise it stays
|
|
||||||
navigated at the structure."}
|
|
||||||
nil->val
|
|
||||||
[v]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (if structure structure v)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(next-fn (if structure structure v))))
|
|
||||||
|
|
||||||
(def NIL->SET (nil->val #{}))
|
|
||||||
(def NIL->LIST (nil->val '()))
|
|
||||||
(def NIL->VECTOR (nil->val []))
|
|
||||||
|
|
||||||
(defn collect [& path]
|
|
||||||
(pathed-collector [late path]
|
|
||||||
(collect-val [this structure]
|
|
||||||
(compiled-select late structure)
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defn collect-one [& path]
|
|
||||||
(pathed-collector [late path]
|
|
||||||
(collect-val [this structure]
|
|
||||||
(compiled-select-one late structure)
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defcollector
|
|
||||||
^{:doc
|
|
||||||
"Adds an external value to the collected vals. Useful when additional arguments
|
|
||||||
are required to the transform function that would otherwise require partial
|
|
||||||
application or a wrapper function.
|
|
||||||
|
|
||||||
e.g., incrementing val at path [:a :b] by 3:
|
|
||||||
(transform [:a :b (putval 3)] + some-map)"}
|
|
||||||
putval
|
|
||||||
[val]
|
|
||||||
(collect-val [this structure]
|
|
||||||
val ))
|
|
||||||
|
|
||||||
(defn cond-path
|
|
||||||
"Takes in alternating cond-path selector cond-path selector...
|
|
||||||
Tests the structure if selecting with cond-path returns anything.
|
|
||||||
If so, it uses the following selector 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 selectors
|
|
||||||
were declared."
|
|
||||||
[& conds]
|
|
||||||
(variable-pathed-path [compiled-paths conds]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(if-let [selector (i/retrieve-cond-selector compiled-paths structure)]
|
|
||||||
(->> (compiled-select selector structure)
|
|
||||||
(mapcat next-fn)
|
|
||||||
doall)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(if-let [selector (i/retrieve-cond-selector compiled-paths structure)]
|
|
||||||
(compiled-transform selector next-fn structure)
|
|
||||||
structure
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn if-path
|
|
||||||
"Like cond-path, but with if semantics."
|
|
||||||
([cond-p if-path] (cond-path cond-p if-path))
|
|
||||||
([cond-p if-path else-path]
|
|
||||||
(cond-path cond-p if-path nil else-path)))
|
|
||||||
|
|
||||||
(defn multi-path
|
|
||||||
"A path that branches on multiple paths. For updates,
|
|
||||||
applies updates to the paths in order."
|
|
||||||
[& paths]
|
|
||||||
(variable-pathed-path [compiled-paths paths]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(->> compiled-paths
|
|
||||||
(mapcat #(compiled-select % structure))
|
|
||||||
(mapcat next-fn)
|
|
||||||
doall
|
|
||||||
))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(reduce
|
|
||||||
(fn [structure selector]
|
|
||||||
(compiled-transform selector next-fn structure))
|
|
||||||
structure
|
|
||||||
compiled-paths
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn stay-then-continue
|
|
||||||
"Navigates to the current element and then navigates via the provided path.
|
|
||||||
This can be used to implement pre-order traversal."
|
|
||||||
[& path]
|
|
||||||
(multi-path STAY path))
|
|
||||||
|
|
||||||
(defn continue-then-stay
|
|
||||||
"Navigates to the provided path and then to the current element. This can be used
|
|
||||||
to implement post-order traversal."
|
|
||||||
[& path]
|
|
||||||
(multi-path path STAY))
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
(ns com.rpl.specter.defhelpers)
|
|
||||||
|
|
||||||
(defn gensyms [amt]
|
|
||||||
(vec (repeatedly amt gensym)))
|
|
||||||
|
|
||||||
(defmacro define-ParamsNeededPath [clj? fn-type invoke-name var-arity-impl]
|
|
||||||
(let [a (with-meta (gensym "array") {:tag 'objects})
|
|
||||||
impls (for [i (range 21)
|
|
||||||
:let [args (vec (gensyms i))
|
|
||||||
setters (for [j (range i)] `(aset ~a ~j ~(get args j)))]]
|
|
||||||
`(~invoke-name [this# ~@args]
|
|
||||||
(let [~a (~(if clj? 'com.rpl.specter.impl/fast-object-array 'object-array) ~i)]
|
|
||||||
~@setters
|
|
||||||
(com.rpl.specter.impl/bind-params* this# ~a 0)
|
|
||||||
)))]
|
|
||||||
`(defrecord ~'ParamsNeededPath [~'transform-fns ~'num-needed-params]
|
|
||||||
~fn-type
|
|
||||||
~@impls
|
|
||||||
~var-arity-impl
|
|
||||||
)))
|
|
||||||
1004
src/clj/com/rpl/specter/impl.cljc
Normal file
1004
src/clj/com/rpl/specter/impl.cljc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,622 +0,0 @@
|
||||||
(ns com.rpl.specter.impl
|
|
||||||
#+cljs (:require-macros
|
|
||||||
[com.rpl.specter.prot-opt-invoke
|
|
||||||
:refer [mk-optimized-invocation]]
|
|
||||||
[com.rpl.specter.defhelpers :refer [define-ParamsNeededPath]]
|
|
||||||
)
|
|
||||||
(:use [com.rpl.specter.protocols :only
|
|
||||||
[select* transform* collect-val]])
|
|
||||||
(:require [com.rpl.specter.protocols :as p]
|
|
||||||
[clojure.walk :as walk]
|
|
||||||
[clojure.core.reducers :as r]
|
|
||||||
[clojure.string :as s]
|
|
||||||
#+clj [com.rpl.specter.defhelpers :as dh]
|
|
||||||
)
|
|
||||||
#+clj
|
|
||||||
(:import [com.rpl.specter Util])
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn spy [e]
|
|
||||||
(println e)
|
|
||||||
e)
|
|
||||||
|
|
||||||
(defprotocol PathComposer
|
|
||||||
(comp-paths* [paths]))
|
|
||||||
|
|
||||||
(defn- smart-str* [o]
|
|
||||||
(if (coll? o)
|
|
||||||
(pr-str o)
|
|
||||||
(str o)))
|
|
||||||
|
|
||||||
(defn smart-str [& elems]
|
|
||||||
(apply str (map smart-str* elems)))
|
|
||||||
|
|
||||||
#+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)))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn benchmark [iters afn]
|
|
||||||
(time
|
|
||||||
(dotimes [_ iters]
|
|
||||||
(afn))))
|
|
||||||
|
|
||||||
(deftype ExecutorFunctions [type select-executor transform-executor])
|
|
||||||
|
|
||||||
(def RichPathExecutor
|
|
||||||
(->ExecutorFunctions
|
|
||||||
:richpath
|
|
||||||
(fn [params params-idx selector structure]
|
|
||||||
(selector params params-idx [] structure
|
|
||||||
(fn [_ _ vals structure]
|
|
||||||
(if-not (empty? vals) [(conj vals structure)] [structure]))))
|
|
||||||
(fn [params params-idx transformer transform-fn structure]
|
|
||||||
(transformer params params-idx [] structure
|
|
||||||
(fn [_ _ vals structure]
|
|
||||||
(if (empty? vals)
|
|
||||||
(transform-fn structure)
|
|
||||||
(apply transform-fn (conj vals structure))))))
|
|
||||||
))
|
|
||||||
|
|
||||||
(def StructurePathExecutor
|
|
||||||
(->ExecutorFunctions
|
|
||||||
:spath
|
|
||||||
(fn [params params-idx selector structure]
|
|
||||||
(selector structure (fn [structure] [structure])))
|
|
||||||
(fn [params params-idx transformer transform-fn structure]
|
|
||||||
(transformer structure transform-fn))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defrecord TransformFunctions [executors selector transformer])
|
|
||||||
|
|
||||||
(defrecord CompiledPath [transform-fns params params-idx])
|
|
||||||
|
|
||||||
(defn compiled-path? [o]
|
|
||||||
(instance? CompiledPath o))
|
|
||||||
|
|
||||||
(defn no-params-compiled-path [transform-fns]
|
|
||||||
(->CompiledPath transform-fns nil 0))
|
|
||||||
|
|
||||||
|
|
||||||
(declare bind-params*)
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(defmacro fast-object-array [i]
|
|
||||||
`(com.rpl.specter.Util/makeObjectArray ~i))
|
|
||||||
|
|
||||||
#+cljs
|
|
||||||
(defn fast-object-array [i]
|
|
||||||
(object-array i))
|
|
||||||
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(dh/define-ParamsNeededPath
|
|
||||||
true
|
|
||||||
clojure.lang.IFn
|
|
||||||
invoke
|
|
||||||
(applyTo [this args]
|
|
||||||
(let [a (object-array args)]
|
|
||||||
(com.rpl.specter.impl/bind-params* this a 0))))
|
|
||||||
|
|
||||||
#+cljs
|
|
||||||
(define-ParamsNeededPath
|
|
||||||
false
|
|
||||||
cljs.core/IFn
|
|
||||||
-invoke
|
|
||||||
(-invoke [this p01 p02 p03 p04 p05 p06 p07 p08 p09 p10
|
|
||||||
p11 p12 p13 p14 p15 p16 p17 p18 p19 p20
|
|
||||||
rest]
|
|
||||||
(let [a (object-array
|
|
||||||
(concat
|
|
||||||
[p01 p02 p03 p04 p05 p06 p07 p08 p09 p10
|
|
||||||
p11 p12 p13 p14 p15 p16 p17 p18 p19 p20]
|
|
||||||
rest))]
|
|
||||||
(com.rpl.specter.impl/bind-params* this a 0))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn params-needed-path? [o]
|
|
||||||
(instance? ParamsNeededPath o))
|
|
||||||
|
|
||||||
(defn bind-params* [^ParamsNeededPath params-needed-path params idx]
|
|
||||||
(->CompiledPath
|
|
||||||
(.-transform-fns params-needed-path)
|
|
||||||
params
|
|
||||||
idx))
|
|
||||||
|
|
||||||
(defn- seq-contains? [aseq val]
|
|
||||||
(->> aseq
|
|
||||||
(filter (partial = val))
|
|
||||||
empty?
|
|
||||||
not))
|
|
||||||
|
|
||||||
(defn no-prot-error-str [obj]
|
|
||||||
(str "Protocol implementation cannot be found for object.
|
|
||||||
Extending Specter protocols should not be done inline in a deftype definition
|
|
||||||
because that prevents Specter from finding the protocol implementations for
|
|
||||||
optimized performance. Instead, you should extend the protocols via an
|
|
||||||
explicit extend-protocol call. \n" obj))
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
|
|
||||||
(defn find-protocol-impl! [prot obj]
|
|
||||||
(let [ret (find-protocol-impl prot obj)]
|
|
||||||
(if (= ret obj)
|
|
||||||
(throw-illegal (no-prot-error-str obj))
|
|
||||||
ret
|
|
||||||
)))
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(defn structure-path-impl [this]
|
|
||||||
(if (fn? this)
|
|
||||||
;;TODO: this isn't kosher, it uses knowledge of internals of protocols
|
|
||||||
(-> p/StructurePath :impls (get clojure.lang.AFn))
|
|
||||||
(find-protocol-impl! p/StructurePath this)))
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(defn collector-impl [this]
|
|
||||||
(find-protocol-impl! p/Collector this))
|
|
||||||
|
|
||||||
|
|
||||||
#+cljs
|
|
||||||
(defn structure-path-impl [obj]
|
|
||||||
{:select* (mk-optimized-invocation p/StructurePath obj select* 2)
|
|
||||||
:transform* (mk-optimized-invocation p/StructurePath obj transform* 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
#+cljs
|
|
||||||
(defn collector-impl [obj]
|
|
||||||
{:collect-val (mk-optimized-invocation p/Collector obj collect-val 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
(defn coerce-collector [this]
|
|
||||||
(let [cfn (->> this
|
|
||||||
collector-impl
|
|
||||||
:collect-val
|
|
||||||
)
|
|
||||||
afn (fn [params params-idx vals structure next-fn]
|
|
||||||
(next-fn params params-idx (conj vals (cfn this structure)) structure)
|
|
||||||
)]
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions RichPathExecutor afn afn)
|
|
||||||
)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn coerce-structure-path [this]
|
|
||||||
(let [pimpl (structure-path-impl this)
|
|
||||||
selector (:select* pimpl)
|
|
||||||
transformer (:transform* pimpl)]
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
StructurePathExecutor
|
|
||||||
(fn [structure next-fn]
|
|
||||||
(selector this structure next-fn))
|
|
||||||
(fn [structure next-fn]
|
|
||||||
(transformer this structure next-fn)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defn coerce-structure-path-rich [this]
|
|
||||||
(let [pimpl (structure-path-impl this)
|
|
||||||
selector (:select* pimpl)
|
|
||||||
transformer (:transform* pimpl)]
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(selector this structure (fn [structure] (next-fn params params-idx vals structure))))
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(transformer this structure (fn [structure] (next-fn params params-idx vals structure)))))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defn structure-path? [obj]
|
|
||||||
(or (fn? obj) (satisfies? p/StructurePath obj)))
|
|
||||||
|
|
||||||
(defprotocol CoercePath
|
|
||||||
(coerce-path [this]))
|
|
||||||
|
|
||||||
(extend-protocol CoercePath
|
|
||||||
nil ; needs its own path because it doesn't count as an Object
|
|
||||||
(coerce-path [this]
|
|
||||||
(coerce-structure-path nil))
|
|
||||||
|
|
||||||
CompiledPath
|
|
||||||
(coerce-path [this]
|
|
||||||
this)
|
|
||||||
|
|
||||||
ParamsNeededPath
|
|
||||||
(coerce-path [this]
|
|
||||||
this)
|
|
||||||
|
|
||||||
#+clj java.util.List #+cljs cljs.core/PersistentVector
|
|
||||||
(coerce-path [this]
|
|
||||||
(comp-paths* this))
|
|
||||||
|
|
||||||
#+cljs cljs.core/IndexedSeq
|
|
||||||
#+cljs (coerce-path [this]
|
|
||||||
(coerce-path (vec this)))
|
|
||||||
#+cljs cljs.core/EmptyList
|
|
||||||
#+cljs (coerce-path [this]
|
|
||||||
(coerce-path (vec this)))
|
|
||||||
#+cljs cljs.core/List
|
|
||||||
#+cljs (coerce-path [this]
|
|
||||||
(coerce-path (vec this)))
|
|
||||||
#+cljs cljs.core/LazySeq
|
|
||||||
#+cljs (coerce-path [this]
|
|
||||||
(coerce-path (vec this)))
|
|
||||||
|
|
||||||
#+clj Object #+cljs default
|
|
||||||
(coerce-path [this]
|
|
||||||
(cond (structure-path? this) (coerce-structure-path this)
|
|
||||||
(satisfies? p/Collector this) (coerce-collector this)
|
|
||||||
:else (throw-illegal (no-prot-error-str this))
|
|
||||||
)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn extype [^TransformFunctions f]
|
|
||||||
(let [^ExecutorFunctions exs (.-executors f)]
|
|
||||||
(.-type exs)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn- combine-same-types [[^TransformFunctions f & _ :as all]]
|
|
||||||
(let [^ExecutorFunctions exs (.-executors f)
|
|
||||||
|
|
||||||
t (.-type exs)
|
|
||||||
|
|
||||||
combiner
|
|
||||||
(if (= t :richpath)
|
|
||||||
(fn [curr next]
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(curr params params-idx vals structure
|
|
||||||
(fn [params-next params-idx-next vals-next structure-next]
|
|
||||||
(next params-next params-idx-next vals-next structure-next next-fn)
|
|
||||||
))))
|
|
||||||
(fn [curr next]
|
|
||||||
(fn [structure next-fn]
|
|
||||||
(curr structure (fn [structure] (next structure next-fn)))))
|
|
||||||
)]
|
|
||||||
|
|
||||||
(reduce (fn [^TransformFunctions curr ^TransformFunctions next]
|
|
||||||
(->TransformFunctions
|
|
||||||
exs
|
|
||||||
(combiner (.-selector curr) (.-selector next))
|
|
||||||
(combiner (.-transformer curr) (.-transformer next))
|
|
||||||
))
|
|
||||||
all)))
|
|
||||||
|
|
||||||
(defn coerce-tfns-rich [^TransformFunctions tfns]
|
|
||||||
(if (= (extype tfns) :richpath)
|
|
||||||
tfns
|
|
||||||
(let [selector (.-selector tfns)
|
|
||||||
transformer (.-transformer tfns)]
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(selector structure (fn [structure] (next-fn params params-idx vals structure))))
|
|
||||||
(fn [params params-idx vals structure next-fn]
|
|
||||||
(transformer structure (fn [structure] (next-fn params params-idx vals structure))))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn capture-params-internally [path]
|
|
||||||
(if-not (instance? CompiledPath path)
|
|
||||||
path
|
|
||||||
(let [params (:params path)
|
|
||||||
params-idx (:params-idx path)
|
|
||||||
selector (-> path :transform-fns :selector)
|
|
||||||
transformer (-> path :transform-fns :transformer)]
|
|
||||||
(if (empty? params)
|
|
||||||
path
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn [x-params x-params-idx vals structure next-fn]
|
|
||||||
(selector params params-idx vals structure
|
|
||||||
(fn [_ _ vals-next structure-next]
|
|
||||||
(next-fn x-params x-params-idx vals-next structure-next)
|
|
||||||
)))
|
|
||||||
(fn [x-params x-params-idx vals structure next-fn]
|
|
||||||
(transformer params params-idx vals structure
|
|
||||||
(fn [_ _ vals-next structure-next]
|
|
||||||
(next-fn x-params x-params-idx vals-next structure-next)
|
|
||||||
))))
|
|
||||||
)))))
|
|
||||||
|
|
||||||
(extend-protocol PathComposer
|
|
||||||
nil
|
|
||||||
(comp-paths* [sp]
|
|
||||||
(coerce-path sp))
|
|
||||||
#+clj Object #+cljs default
|
|
||||||
(comp-paths* [sp]
|
|
||||||
(coerce-path sp))
|
|
||||||
#+clj java.util.List #+cljs cljs.core/PersistentVector
|
|
||||||
(comp-paths* [structure-paths]
|
|
||||||
(if (empty? structure-paths)
|
|
||||||
(coerce-path nil)
|
|
||||||
(let [coerced (->> structure-paths
|
|
||||||
(map coerce-path)
|
|
||||||
(map capture-params-internally))
|
|
||||||
combined (->> coerced
|
|
||||||
(map :transform-fns)
|
|
||||||
(partition-by extype)
|
|
||||||
(map combine-same-types)
|
|
||||||
)
|
|
||||||
result-tfn (if (= 1 (count combined))
|
|
||||||
(first combined)
|
|
||||||
(->> combined
|
|
||||||
(map coerce-tfns-rich)
|
|
||||||
combine-same-types)
|
|
||||||
)
|
|
||||||
needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)]
|
|
||||||
(if (empty? needs-params-paths)
|
|
||||||
(no-params-compiled-path result-tfn)
|
|
||||||
(->ParamsNeededPath
|
|
||||||
(coerce-tfns-rich result-tfn)
|
|
||||||
(->> needs-params-paths
|
|
||||||
(map :num-needed-params)
|
|
||||||
(reduce +))
|
|
||||||
))
|
|
||||||
))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn num-needed-params [path]
|
|
||||||
(if (instance? CompiledPath path)
|
|
||||||
0
|
|
||||||
(:num-needed-params path)))
|
|
||||||
|
|
||||||
|
|
||||||
;; cell implementation idea taken from prismatic schema library
|
|
||||||
(defprotocol PMutableCell
|
|
||||||
#+clj (get_cell [cell])
|
|
||||||
(set_cell [cell x]))
|
|
||||||
|
|
||||||
(deftype MutableCell [^:volatile-mutable q]
|
|
||||||
PMutableCell
|
|
||||||
#+clj (get_cell [cell] q)
|
|
||||||
(set_cell [this x] (set! q x)))
|
|
||||||
|
|
||||||
(defn mutable-cell
|
|
||||||
([] (mutable-cell nil))
|
|
||||||
([init] (MutableCell. init)))
|
|
||||||
|
|
||||||
(defn set-cell! [cell val]
|
|
||||||
(set_cell cell val))
|
|
||||||
|
|
||||||
(defn get-cell [cell]
|
|
||||||
#+clj (get_cell cell) #+cljs (.-q cell)
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn update-cell! [cell afn]
|
|
||||||
(let [ret (afn (get-cell cell))]
|
|
||||||
(set-cell! cell ret)
|
|
||||||
ret))
|
|
||||||
|
|
||||||
(defn- append [coll elem]
|
|
||||||
(-> coll vec (conj elem)))
|
|
||||||
|
|
||||||
(defprotocol SetExtremes
|
|
||||||
(set-first [s val])
|
|
||||||
(set-last [s val]))
|
|
||||||
|
|
||||||
(defn- set-first-list [l v]
|
|
||||||
(cons v (rest l)))
|
|
||||||
|
|
||||||
(defn- set-last-list [l v]
|
|
||||||
(append (butlast l) v))
|
|
||||||
|
|
||||||
(extend-protocol SetExtremes
|
|
||||||
#+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector
|
|
||||||
(set-first [v val]
|
|
||||||
(assoc v 0 val))
|
|
||||||
(set-last [v val]
|
|
||||||
(assoc v (-> v count dec) val))
|
|
||||||
#+clj Object #+cljs default
|
|
||||||
(set-first [l val]
|
|
||||||
(set-first-list l val))
|
|
||||||
(set-last [l val]
|
|
||||||
(set-last-list l val)
|
|
||||||
))
|
|
||||||
|
|
||||||
(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- fn-invocation? [f]
|
|
||||||
(or (instance? clojure.lang.Cons f)
|
|
||||||
(instance? clojure.lang.LazySeq f)
|
|
||||||
(list? f)))
|
|
||||||
|
|
||||||
(defn codewalk-until [pred on-match-fn structure]
|
|
||||||
(if (pred structure)
|
|
||||||
(on-match-fn structure)
|
|
||||||
(let [ret (walk/walk (partial codewalk-until pred on-match-fn) identity structure)]
|
|
||||||
(if (and (fn-invocation? structure) (fn-invocation? ret))
|
|
||||||
(with-meta ret (meta structure))
|
|
||||||
ret
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn- conj-all! [cell elems]
|
|
||||||
(set-cell! cell (concat (get-cell cell) elems)))
|
|
||||||
|
|
||||||
(defn compiled-select*
|
|
||||||
[^com.rpl.specter.impl.CompiledPath path structure]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
|
|
||||||
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
|
|
||||||
((.-select-executor ex) (.-params path) (.-params-idx path) (.-selector tfns) structure)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn compiled-transform*
|
|
||||||
[^com.rpl.specter.impl.CompiledPath path transform-fn structure]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
|
|
||||||
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
|
|
||||||
((.-transform-executor ex) (.-params path) (.-params-idx path) (.-transformer tfns) transform-fn structure)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn not-selected?*
|
|
||||||
[compiled-path structure]
|
|
||||||
(->> structure
|
|
||||||
(compiled-select* compiled-path)
|
|
||||||
empty?))
|
|
||||||
|
|
||||||
(defn selected?*
|
|
||||||
[compiled-path structure]
|
|
||||||
(not (not-selected?* compiled-path structure)))
|
|
||||||
|
|
||||||
;; returns vector of all results
|
|
||||||
(defn walk-select [pred continue-fn structure]
|
|
||||||
(let [ret (mutable-cell [])
|
|
||||||
walker (fn this [structure]
|
|
||||||
(if (pred structure)
|
|
||||||
(conj-all! ret (continue-fn structure))
|
|
||||||
(walk/walk this identity structure))
|
|
||||||
)]
|
|
||||||
(walker structure)
|
|
||||||
(get-cell ret)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn filter+ancestry [path aseq]
|
|
||||||
(let [aseq (vec aseq)]
|
|
||||||
(reduce (fn [[s m :as orig] i]
|
|
||||||
(let [e (get aseq i)
|
|
||||||
pos (count s)]
|
|
||||||
(if (selected?* path e)
|
|
||||||
[(conj s e) (assoc m pos i)]
|
|
||||||
orig
|
|
||||||
)))
|
|
||||||
[[] {}]
|
|
||||||
(range (count aseq))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defn key-select [akey structure next-fn]
|
|
||||||
(next-fn (get structure akey)))
|
|
||||||
|
|
||||||
(defn key-transform [akey structure next-fn]
|
|
||||||
(assoc structure akey (next-fn (get structure akey))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn all-select [structure next-fn]
|
|
||||||
(into [] (r/mapcat next-fn structure)))
|
|
||||||
|
|
||||||
(defn all-transform [structure next-fn]
|
|
||||||
(let [empty-structure (empty structure)]
|
|
||||||
(cond (list? empty-structure)
|
|
||||||
;; this is done to maintain order, otherwise lists get reversed
|
|
||||||
(doall (map next-fn structure))
|
|
||||||
|
|
||||||
(map? structure)
|
|
||||||
(->> structure (r/map vec) (r/map next-fn) (into empty-structure))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(->> structure (r/map next-fn) (into empty-structure))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftype AllStructurePath [])
|
|
||||||
|
|
||||||
(extend-protocol p/StructurePath
|
|
||||||
AllStructurePath
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(all-select structure next-fn))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(all-transform structure next-fn)))
|
|
||||||
|
|
||||||
(deftype ValCollect [])
|
|
||||||
|
|
||||||
(extend-protocol p/Collector
|
|
||||||
ValCollect
|
|
||||||
(collect-val [this structure]
|
|
||||||
structure))
|
|
||||||
|
|
||||||
(deftype PosStructurePath [getter setter])
|
|
||||||
|
|
||||||
(extend-protocol p/StructurePath
|
|
||||||
PosStructurePath
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(if-not (empty? structure)
|
|
||||||
(next-fn ((.-getter this) structure))))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(if (empty? structure)
|
|
||||||
structure
|
|
||||||
((.-setter this) structure (next-fn ((.-getter this) structure))))))
|
|
||||||
|
|
||||||
(defn srange-select [structure start end next-fn]
|
|
||||||
(next-fn (-> structure vec (subvec start end))))
|
|
||||||
|
|
||||||
(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
|
|
||||||
)))
|
|
||||||
|
|
||||||
(extend-protocol p/StructurePath
|
|
||||||
nil
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn structure))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(next-fn structure)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn retrieve-cond-selector [cond-paths structure]
|
|
||||||
(->> cond-paths
|
|
||||||
(partition 2)
|
|
||||||
(drop-while (fn [[c-selector _]]
|
|
||||||
(->> structure
|
|
||||||
(compiled-select* c-selector)
|
|
||||||
empty?)))
|
|
||||||
first
|
|
||||||
second
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn filter-select [afn structure next-fn]
|
|
||||||
(if (afn structure)
|
|
||||||
(next-fn structure)))
|
|
||||||
|
|
||||||
(defn filter-transform [afn structure next-fn]
|
|
||||||
(if (afn structure)
|
|
||||||
(next-fn structure)
|
|
||||||
structure))
|
|
||||||
|
|
||||||
(defn compiled-selector [^com.rpl.specter.impl.CompiledPath path]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)]
|
|
||||||
(.-selector tfns)))
|
|
||||||
|
|
||||||
(defn compiled-transformer [^com.rpl.specter.impl.CompiledPath path]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)]
|
|
||||||
(.-transformer tfns)))
|
|
||||||
|
|
||||||
(defn params-needed-selector [^com.rpl.specter.impl.ParamsNeededPath path]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)]
|
|
||||||
(.-selector tfns)))
|
|
||||||
|
|
||||||
(defn params-needed-transformer [^com.rpl.specter.impl.ParamsNeededPath path]
|
|
||||||
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)]
|
|
||||||
(.-transformer tfns)))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(defn extend-protocolpath* [protpath protpath-prot extensions]
|
|
||||||
(let [extensions (partition 2 extensions)
|
|
||||||
m (-> protpath-prot :sigs keys first)
|
|
||||||
expected-params (num-needed-params protpath)]
|
|
||||||
(doseq [[atype apath] extensions]
|
|
||||||
(let [p (comp-paths* apath)
|
|
||||||
rp (assoc p :transform-fns (coerce-tfns-rich (:transform-fns p)))
|
|
||||||
needed-params (num-needed-params rp)]
|
|
||||||
(if-not (= needed-params expected-params)
|
|
||||||
(throw-illegal "Invalid number of params in extended protocol path, expected "
|
|
||||||
expected-params " but got " needed-params))
|
|
||||||
(extend atype protpath-prot {m (fn [_] rp)})
|
|
||||||
))))
|
|
||||||
|
|
@ -1,313 +1,54 @@
|
||||||
(ns com.rpl.specter.macros
|
(ns com.rpl.specter.macros
|
||||||
(:use [com.rpl.specter impl])
|
(:use [com.rpl.specter.protocols :only [RichNavigator]])
|
||||||
)
|
(:require [com.rpl.specter.impl :as i]))
|
||||||
|
|
||||||
(defn gensyms [amt]
|
|
||||||
(vec (repeatedly amt gensym)))
|
|
||||||
|
|
||||||
(defn determine-params-impls [[name1 & impl1] [name2 & impl2]]
|
|
||||||
(if-not (= #{name1 name2} #{'select* 'transform*})
|
|
||||||
(throw-illegal "defpath must implement select* and transform*, instead got "
|
|
||||||
name1 " and " name2))
|
|
||||||
(if (= name1 'select*)
|
|
||||||
[impl1 impl2]
|
|
||||||
[impl2 impl1]))
|
|
||||||
|
|
||||||
|
|
||||||
(def PARAMS-SYM (vary-meta (gensym "params") assoc :tag 'objects))
|
(defn- determine-params-impls [impls]
|
||||||
(def PARAMS-IDX-SYM (gensym "params-idx"))
|
(let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))]
|
||||||
|
(if-not (= #{'select* 'transform*} (-> grouped keys set))
|
||||||
(defn paramspath* [bindings num-params [impl1 impl2]]
|
(throw (ex-info "defnav must implement select* and transform*"
|
||||||
(let [[[[_ s-structure-sym s-next-fn-sym] & select-body]
|
{:methods (keys grouped)})))
|
||||||
[[_ t-structure-sym t-next-fn-sym] & transform-body]]
|
grouped))
|
||||||
(determine-params-impls impl1 impl2)]
|
|
||||||
(if (= 0 num-params)
|
|
||||||
`(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
StructurePathExecutor
|
|
||||||
(fn [~s-structure-sym ~s-next-fn-sym]
|
|
||||||
~@select-body)
|
|
||||||
(fn [~t-structure-sym ~t-next-fn-sym]
|
|
||||||
~@transform-body)
|
|
||||||
))
|
|
||||||
`(->ParamsNeededPath
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~s-structure-sym next-fn#]
|
|
||||||
(let [~s-next-fn-sym (fn [structure#]
|
|
||||||
(next-fn#
|
|
||||||
~PARAMS-SYM
|
|
||||||
(+ ~PARAMS-IDX-SYM ~num-params)
|
|
||||||
vals#
|
|
||||||
structure#))
|
|
||||||
~@bindings]
|
|
||||||
~@select-body
|
|
||||||
))
|
|
||||||
(fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~t-structure-sym next-fn#]
|
|
||||||
(let [~t-next-fn-sym (fn [structure#]
|
|
||||||
(next-fn#
|
|
||||||
~PARAMS-SYM
|
|
||||||
(+ ~PARAMS-IDX-SYM ~num-params)
|
|
||||||
vals#
|
|
||||||
structure#))
|
|
||||||
~@bindings]
|
|
||||||
~@transform-body
|
|
||||||
)))
|
|
||||||
~num-params
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn paramscollector* [post-bindings num-params [_ [_ structure-sym] & body]]
|
|
||||||
`(let [collector# (fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~structure-sym next-fn#]
|
|
||||||
(let [~@post-bindings ~@[] ; to avoid syntax highlighting issues
|
|
||||||
c# (do ~@body)]
|
|
||||||
(next-fn#
|
|
||||||
~PARAMS-SYM
|
|
||||||
(+ ~PARAMS-IDX-SYM ~num-params)
|
|
||||||
(conj vals# c#)
|
|
||||||
~structure-sym)
|
|
||||||
))]
|
|
||||||
(->ParamsNeededPath
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
collector#
|
|
||||||
collector# )
|
|
||||||
~num-params
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defn pathed-path* [builder paths-seq latefns-sym pre-bindings post-bindings impls]
|
|
||||||
(let [num-params-sym (gensym "num-params")]
|
|
||||||
`(let [paths# (map comp-paths* ~paths-seq)
|
|
||||||
needed-params# (map num-needed-params paths#)
|
|
||||||
offsets# (cons 0 (reductions + needed-params#))
|
|
||||||
any-params-needed?# (->> paths#
|
|
||||||
(filter params-needed-path?)
|
|
||||||
empty?
|
|
||||||
not)
|
|
||||||
~num-params-sym (last offsets#)
|
|
||||||
~latefns-sym (map
|
|
||||||
(fn [o# p#]
|
|
||||||
(if (compiled-path? p#)
|
|
||||||
(fn [params# params-idx#]
|
|
||||||
p# )
|
|
||||||
(fn [params# params-idx#]
|
|
||||||
(bind-params* p# params# (+ params-idx# o#))
|
|
||||||
)))
|
|
||||||
offsets#
|
|
||||||
paths#)
|
|
||||||
~@pre-bindings
|
|
||||||
ret# ~(builder post-bindings num-params-sym impls)
|
|
||||||
]
|
|
||||||
(if (not any-params-needed?#)
|
|
||||||
(bind-params* ret# nil 0)
|
|
||||||
ret#
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn make-param-retrievers [params]
|
|
||||||
(->> params
|
|
||||||
(map-indexed
|
|
||||||
(fn [i p]
|
|
||||||
[p `(aget ~PARAMS-SYM
|
|
||||||
(+ ~PARAMS-IDX-SYM ~i))]
|
|
||||||
))
|
|
||||||
(apply concat)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro path
|
(defmacro richnav [params & impls]
|
||||||
"Defines a StructurePath with late bound parameters. This path can be precompiled
|
(if (empty? params)
|
||||||
with other selectors without knowing the parameters. When precompiled with other
|
`(reify RichNavigator ~@impls)
|
||||||
selectors, the resulting selector takes in parameters for all selectors in the path
|
`(i/direct-nav-obj
|
||||||
that needed parameters (in the order in which they were declared)."
|
(fn ~params
|
||||||
[params impl1 impl2]
|
(reify RichNavigator
|
||||||
(let [num-params (count params)
|
~@impls)))))
|
||||||
retrieve-params (make-param-retrievers params)]
|
|
||||||
(paramspath* retrieve-params num-params [impl1 impl2])
|
|
||||||
))
|
|
||||||
|
|
||||||
(defmacro paramsfn [params [structure-sym] & impl]
|
|
||||||
`(path ~params
|
|
||||||
(~'select* [this# structure# next-fn#]
|
|
||||||
(let [afn# (fn [~structure-sym] ~@impl)]
|
|
||||||
(filter-select afn# structure# next-fn#)
|
|
||||||
))
|
|
||||||
(~'transform* [this# structure# next-fn#]
|
|
||||||
(let [afn# (fn [~structure-sym] ~@impl)]
|
|
||||||
(filter-transform afn# structure# next-fn#)
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defmacro paramscollector
|
|
||||||
"Defines a Collector with late bound parameters. This collector can be precompiled
|
|
||||||
with other selectors without knowing the parameters. When precompiled with other
|
|
||||||
selectors, the resulting selector takes in parameters for all selectors in the path
|
|
||||||
that needed parameters (in the order in which they were declared).
|
|
||||||
"
|
|
||||||
[params impl]
|
|
||||||
(let [num-params (count params)
|
|
||||||
retrieve-params (make-param-retrievers params)]
|
|
||||||
(paramscollector* retrieve-params num-params impl)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defmacro defpath [name & body]
|
|
||||||
`(def ~name (path ~@body)))
|
|
||||||
|
|
||||||
(defmacro defcollector [name & body]
|
|
||||||
`(def ~name (paramscollector ~@body)))
|
|
||||||
|
|
||||||
(defmacro fixed-pathed-path
|
|
||||||
"This helper is used to define selectors that take in a fixed number of other selector
|
|
||||||
paths as input. Those selector paths may require late-bound params, so this helper
|
|
||||||
will create a parameterized selector if that is the case. If no late-bound params
|
|
||||||
are required, then the result is executable."
|
|
||||||
[bindings impl1 impl2]
|
|
||||||
(let [bindings (partition 2 bindings)
|
|
||||||
paths (mapv second bindings)
|
|
||||||
names (mapv first bindings)
|
|
||||||
latefns-sym (gensym "latefns")
|
|
||||||
latefn-syms (vec (gensyms (count paths)))]
|
|
||||||
(pathed-path*
|
|
||||||
paramspath*
|
|
||||||
paths
|
|
||||||
latefns-sym
|
|
||||||
[latefn-syms latefns-sym]
|
|
||||||
(mapcat (fn [n l] [n `(~l ~PARAMS-SYM ~PARAMS-IDX-SYM)]) names latefn-syms)
|
|
||||||
[impl1 impl2])))
|
|
||||||
|
|
||||||
(defmacro variable-pathed-path
|
|
||||||
"This helper is used to define selectors that take in a variable number of other selector
|
|
||||||
paths as input. Those selector paths may require late-bound params, so this helper
|
|
||||||
will create a parameterized selector if that is the case. If no late-bound params
|
|
||||||
are required, then the result is executable."
|
|
||||||
[[latepaths-seq-sym paths-seq] impl1 impl2]
|
|
||||||
(let [latefns-sym (gensym "latefns")]
|
|
||||||
(pathed-path*
|
|
||||||
paramspath*
|
|
||||||
paths-seq
|
|
||||||
latefns-sym
|
|
||||||
[]
|
|
||||||
[latepaths-seq-sym `(map (fn [l#] (l# ~PARAMS-SYM ~PARAMS-IDX-SYM))
|
|
||||||
~latefns-sym)]
|
|
||||||
[impl1 impl2]
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defmacro pathed-collector
|
|
||||||
"This helper is used to define collectors that take in a single selector
|
|
||||||
paths as input. That path may require late-bound params, so this helper
|
|
||||||
will create a parameterized selector if that is the case. If no late-bound params
|
|
||||||
are required, then the result is executable."
|
|
||||||
[[name path] impl]
|
|
||||||
(let [latefns-sym (gensym "latefns")
|
|
||||||
latefn (gensym "latefn")]
|
|
||||||
(pathed-path*
|
|
||||||
paramscollector*
|
|
||||||
[path]
|
|
||||||
latefns-sym
|
|
||||||
[[latefn] latefns-sym]
|
|
||||||
[name `(~latefn ~PARAMS-SYM ~PARAMS-IDX-SYM)]
|
|
||||||
impl
|
|
||||||
)
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn- protpath-sym [name]
|
|
||||||
(-> name (str "-prot") symbol))
|
|
||||||
|
|
||||||
(defmacro defprotocolpath
|
|
||||||
([name]
|
|
||||||
`(defprotocolpath ~name []))
|
|
||||||
([name params]
|
|
||||||
(let [prot-name (protpath-sym name)
|
|
||||||
m (-> name (str "-retrieve") symbol)
|
|
||||||
num-params (count params)
|
|
||||||
ssym (gensym "structure")
|
|
||||||
rargs [(gensym "params") (gensym "pidx") (gensym "vals") ssym (gensym "next-fn")]
|
|
||||||
retrieve `(~m ~ssym)
|
|
||||||
]
|
|
||||||
`(do
|
|
||||||
(defprotocol ~prot-name (~m [structure#]))
|
|
||||||
(def ~name
|
|
||||||
(if (= ~num-params 0)
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn ~rargs
|
|
||||||
(let [path# ~retrieve
|
|
||||||
selector# (compiled-selector path#)]
|
|
||||||
(selector# ~@rargs)
|
|
||||||
))
|
|
||||||
(fn ~rargs
|
|
||||||
(let [path# ~retrieve
|
|
||||||
transformer# (compiled-transformer path#)]
|
|
||||||
(transformer# ~@rargs)
|
|
||||||
))))
|
|
||||||
(->ParamsNeededPath
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn ~rargs
|
|
||||||
(let [path# ~retrieve
|
|
||||||
selector# (params-needed-selector path#)]
|
|
||||||
(selector# ~@rargs)
|
|
||||||
))
|
|
||||||
(fn ~rargs
|
|
||||||
(let [path# ~retrieve
|
|
||||||
transformer# (params-needed-transformer path#)]
|
|
||||||
(transformer# ~@rargs)
|
|
||||||
)))
|
|
||||||
~num-params
|
|
||||||
)
|
|
||||||
))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn declared-name [name]
|
(defmacro nav [params & impls]
|
||||||
(symbol (str name "-declared")))
|
(let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select*
|
||||||
|
[[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*} (determine-params-impls impls)]
|
||||||
|
`(richnav ~params
|
||||||
|
(~'select* [this# vals# ~s-structure-sym next-fn#]
|
||||||
|
(let [~s-next-fn-sym (fn [s#] (next-fn# vals# s#))]
|
||||||
|
~@s-body))
|
||||||
|
(~'transform* [this# vals# ~t-structure-sym next-fn#]
|
||||||
|
(let [~t-next-fn-sym (fn [s#] (next-fn# vals# s#))]
|
||||||
|
~@t-body)))))
|
||||||
|
|
||||||
(defmacro declarepath
|
(defn- helper-name [name method-name]
|
||||||
([name]
|
(with-meta (symbol (str name "-" method-name)) {:no-doc true}))
|
||||||
`(declarepath ~name []))
|
|
||||||
([name params]
|
|
||||||
(let [num-params (count params)
|
|
||||||
declared (declared-name name)
|
|
||||||
rargs [(gensym "params") (gensym "pidx") (gensym "vals")
|
|
||||||
(gensym "structure") (gensym "next-fn")]]
|
|
||||||
`(do
|
|
||||||
(declare ~declared)
|
|
||||||
(def ~name
|
|
||||||
(if (= ~num-params 0)
|
|
||||||
(no-params-compiled-path
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn ~rargs
|
|
||||||
(let [selector# (compiled-selector ~declared)]
|
|
||||||
(selector# ~@rargs)
|
|
||||||
))
|
|
||||||
(fn ~rargs
|
|
||||||
(let [transformer# (compiled-transformer ~declared)]
|
|
||||||
(transformer# ~@rargs)
|
|
||||||
))))
|
|
||||||
(->ParamsNeededPath
|
|
||||||
(->TransformFunctions
|
|
||||||
RichPathExecutor
|
|
||||||
(fn ~rargs
|
|
||||||
(let [selector# (params-needed-selector ~declared)]
|
|
||||||
(selector# ~@rargs)
|
|
||||||
))
|
|
||||||
(fn ~rargs
|
|
||||||
(let [transformer# (params-needed-transformer ~declared)]
|
|
||||||
(transformer# ~@rargs)
|
|
||||||
)))
|
|
||||||
~num-params
|
|
||||||
)
|
|
||||||
))))))
|
|
||||||
|
|
||||||
(defmacro providepath [name apath]
|
(defmacro defnav [name params & impls]
|
||||||
`(let [comped# (comp-paths* ~apath)
|
;; remove the "this" param for the helper
|
||||||
expected-params# (num-needed-params ~name)
|
(let [helpers (for [[mname [_ & mparams] & mbody] impls]
|
||||||
needed-params# (num-needed-params comped#)]
|
`(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody))
|
||||||
(if-not (= needed-params# expected-params#)
|
decls (for [[mname & _] impls]
|
||||||
(throw-illegal "Invalid number of params in provided path, expected "
|
`(declare ~(helper-name name mname)))
|
||||||
expected-params# " but got " needed-params#))
|
name-with-meta (vary-meta name
|
||||||
(def ~(declared-name name)
|
assoc :arglists (list 'quote (list params)))]
|
||||||
(update-in comped#
|
`(do
|
||||||
[:transform-fns]
|
~@decls
|
||||||
coerce-tfns-rich)
|
~@helpers
|
||||||
)))
|
(def ~name-with-meta (nav ~params ~@impls)))))
|
||||||
|
|
||||||
(defmacro extend-protocolpath [protpath & extensions]
|
(defmacro defrichnav [name params & impls]
|
||||||
`(extend-protocolpath* ~protpath ~(protpath-sym protpath) ~(vec extensions)))
|
(let [name-with-meta (vary-meta name
|
||||||
|
assoc :arglists (list 'quote (list params)))]
|
||||||
|
`(def ~name-with-meta
|
||||||
|
(richnav ~params ~@impls))))
|
||||||
|
|
|
||||||
786
src/clj/com/rpl/specter/navs.cljc
Normal file
786
src/clj/com/rpl/specter/navs.cljc
Normal file
|
|
@ -0,0 +1,786 @@
|
||||||
|
(ns com.rpl.specter.navs
|
||||||
|
#?(:cljs (:require-macros
|
||||||
|
[com.rpl.specter
|
||||||
|
:refer
|
||||||
|
[defnav defrichnav]]
|
||||||
|
[com.rpl.specter.util-macros :refer
|
||||||
|
[doseqres]]))
|
||||||
|
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
|
||||||
|
[com.rpl.specter.util-macros :only [doseqres]]))
|
||||||
|
(:require [com.rpl.specter.impl :as i]
|
||||||
|
#?@(:bb []
|
||||||
|
:clj [[clojure.core.reducers :as r]])))
|
||||||
|
|
||||||
|
|
||||||
|
(defn not-selected?*
|
||||||
|
[compiled-path vals structure]
|
||||||
|
(->> structure
|
||||||
|
(i/compiled-select-any* compiled-path vals)
|
||||||
|
(identical? i/NONE)))
|
||||||
|
|
||||||
|
(defn selected?*
|
||||||
|
[compiled-path vals structure]
|
||||||
|
(not (not-selected?* compiled-path vals structure)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn all-select [structure next-fn]
|
||||||
|
(doseqres i/NONE [e structure]
|
||||||
|
(next-fn e)))
|
||||||
|
|
||||||
|
#?(
|
||||||
|
:clj
|
||||||
|
(defn queue? [coll]
|
||||||
|
(instance? clojure.lang.PersistentQueue coll))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn queue? [coll]
|
||||||
|
(= (type coll) (type #queue []))))
|
||||||
|
|
||||||
|
|
||||||
|
(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 (void-transformed-kv-pair? newkv)
|
||||||
|
m
|
||||||
|
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||||
|
|
||||||
|
empty-map
|
||||||
|
structure))
|
||||||
|
|
||||||
|
(defn not-NONE? [v]
|
||||||
|
(-> v (identical? i/NONE) not))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- all-transform-list [structure next-fn]
|
||||||
|
(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
|
||||||
|
(all-transform [structure next-fn]
|
||||||
|
nil)
|
||||||
|
|
||||||
|
|
||||||
|
#?(:clj clojure.lang.MapEntry)
|
||||||
|
#?(:clj
|
||||||
|
(all-transform [structure next-fn]
|
||||||
|
(let [newk (next-fn (key structure))
|
||||||
|
newv (next-fn (val structure))]
|
||||||
|
(clojure.lang.MapEntry. newk newv))))
|
||||||
|
|
||||||
|
|
||||||
|
#?(: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)
|
||||||
|
#?(: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)
|
||||||
|
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)
|
||||||
|
newkv (next-fn [k v])]
|
||||||
|
(if (void-transformed-kv-pair? newkv)
|
||||||
|
(do
|
||||||
|
(i/update-cell! none-cell inc)
|
||||||
|
(recur (+ i 2) j))
|
||||||
|
(do
|
||||||
|
(aset array j (nth newkv 0))
|
||||||
|
(aset array (inc j) (nth newkv 1))
|
||||||
|
(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
|
||||||
|
(all-transform [structure next-fn]
|
||||||
|
(non-transient-map-all-transform structure next-fn {})))
|
||||||
|
|
||||||
|
|
||||||
|
#?(:clj clojure.lang.PersistentTreeMap :cljs cljs.core/PersistentTreeMap)
|
||||||
|
(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]
|
||||||
|
(persistent!
|
||||||
|
(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)))))
|
||||||
|
|
||||||
|
(transient
|
||||||
|
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
|
||||||
|
|
||||||
|
structure)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#?(:clj Object)
|
||||||
|
#?(:clj
|
||||||
|
(all-transform [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 is much faster than doing r/map through call to (into ...)
|
||||||
|
(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
|
||||||
|
#?(: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]
|
||||||
|
(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 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
|
||||||
|
(fn [m k v]
|
||||||
|
(let [newv (next-fn v)]
|
||||||
|
(if (identical? newv i/NONE)
|
||||||
|
m
|
||||||
|
(assoc m k newv))))
|
||||||
|
empty-map
|
||||||
|
structure))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
#?(: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)
|
||||||
|
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)
|
||||||
|
newv (next-fn v)]
|
||||||
|
(if (identical? newv i/NONE)
|
||||||
|
(do
|
||||||
|
(i/update-cell! none-cell inc)
|
||||||
|
(recur (+ i 2) j))
|
||||||
|
(do
|
||||||
|
(aset array j k)
|
||||||
|
(aset array (inc j) newv)
|
||||||
|
(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. 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)
|
||||||
|
(map-vals-transform [structure next-fn]
|
||||||
|
(persistent!
|
||||||
|
(reduce-kv
|
||||||
|
(fn [m k v]
|
||||||
|
(let [newv (next-fn v)]
|
||||||
|
(if (identical? newv i/NONE)
|
||||||
|
m
|
||||||
|
(assoc! m k newv))))
|
||||||
|
(transient
|
||||||
|
#?(: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]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [m k v]
|
||||||
|
(let [newv (next-fn v)]
|
||||||
|
(if (identical? newv i/NONE)
|
||||||
|
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
|
||||||
|
(if (string? structure)
|
||||||
|
(subs structure start end)
|
||||||
|
(-> structure vec (subvec start end))
|
||||||
|
)))
|
||||||
|
|
||||||
|
(def srange-transform i/srange-transform*)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-basic-filter-fn [path]
|
||||||
|
(cond (fn? path)
|
||||||
|
path
|
||||||
|
|
||||||
|
(and (coll? path)
|
||||||
|
(every? fn? path))
|
||||||
|
(reduce
|
||||||
|
(fn [combined afn]
|
||||||
|
(fn [structure]
|
||||||
|
(and (combined structure) (afn structure))))
|
||||||
|
|
||||||
|
path)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn if-select [vals structure next-fn then-tester then-nav else-nav]
|
||||||
|
(i/exec-select*
|
||||||
|
(if (then-tester structure) then-nav else-nav)
|
||||||
|
vals
|
||||||
|
structure
|
||||||
|
next-fn))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn if-transform [vals structure next-fn then-tester then-nav else-nav]
|
||||||
|
(i/exec-transform*
|
||||||
|
(if (then-tester structure) then-nav else-nav)
|
||||||
|
vals
|
||||||
|
structure
|
||||||
|
next-fn))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defprotocol AddExtremes
|
||||||
|
(append-all [structure elements])
|
||||||
|
(prepend-all [structure elements])
|
||||||
|
(append-one [structure elem])
|
||||||
|
(prepend-one [structure elem])
|
||||||
|
)
|
||||||
|
|
||||||
|
(extend-protocol AddExtremes
|
||||||
|
nil
|
||||||
|
(append-all [_ elements]
|
||||||
|
elements)
|
||||||
|
(prepend-all [_ elements]
|
||||||
|
elements)
|
||||||
|
(append-one [_ elem]
|
||||||
|
(list elem))
|
||||||
|
(prepend-one [_ elem]
|
||||||
|
(list elem))
|
||||||
|
|
||||||
|
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||||
|
(append-all [structure elements]
|
||||||
|
(reduce conj structure elements))
|
||||||
|
(prepend-all [structure elements]
|
||||||
|
(let [ret (transient [])]
|
||||||
|
(as-> ret <>
|
||||||
|
(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))
|
||||||
|
(append-one [structure elem]
|
||||||
|
(concat structure [elem]))
|
||||||
|
(prepend-one [structure elem]
|
||||||
|
(cons elem structure))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defprotocol UpdateExtremes
|
||||||
|
(update-first [s afn])
|
||||||
|
(update-last [s afn]))
|
||||||
|
|
||||||
|
(defprotocol GetExtremes
|
||||||
|
(get-first [s])
|
||||||
|
(get-last [s]))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
(next-fn (getter structure))
|
||||||
|
i/NONE))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(if (fast-empty? structure)
|
||||||
|
structure
|
||||||
|
(updater structure next-fn))))
|
||||||
|
|
||||||
|
#?(:bb
|
||||||
|
(defn vec-count [v]
|
||||||
|
(count v))
|
||||||
|
|
||||||
|
:clj
|
||||||
|
(defn vec-count [^clojure.lang.IPersistentVector v]
|
||||||
|
(.length v))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn vec-count [v]
|
||||||
|
(count v)))
|
||||||
|
|
||||||
|
(defn- update-first-list [l afn]
|
||||||
|
(let [newf (afn (first l))
|
||||||
|
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))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn transient-vec-count [v]
|
||||||
|
(count v)))
|
||||||
|
|
||||||
|
|
||||||
|
(extend-protocol UpdateExtremes
|
||||||
|
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||||
|
(update-first [v afn]
|
||||||
|
(update-first-vector v afn))
|
||||||
|
|
||||||
|
(update-last [v afn]
|
||||||
|
(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]
|
||||||
|
(update-first-list l val))
|
||||||
|
(update-last [l val]
|
||||||
|
(update-last-list l val)))
|
||||||
|
|
||||||
|
|
||||||
|
(extend-protocol GetExtremes
|
||||||
|
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||||
|
(get-first [v]
|
||||||
|
(nth v 0))
|
||||||
|
(get-last [v]
|
||||||
|
(peek v))
|
||||||
|
|
||||||
|
#?(:clj Object :cljs default)
|
||||||
|
(get-first [s]
|
||||||
|
(first s))
|
||||||
|
(get-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))
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(extend-protocol FastEmpty
|
||||||
|
nil
|
||||||
|
(fast-empty? [_] true)
|
||||||
|
|
||||||
|
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||||
|
(fast-empty? [v]
|
||||||
|
(= 0 (vec-count v)))
|
||||||
|
#?(:clj clojure.lang.ITransientVector :cljs cljs.core/TransientVector)
|
||||||
|
(fast-empty? [v]
|
||||||
|
(= 0 (transient-vec-count v)))
|
||||||
|
#?(:clj Object :cljs default)
|
||||||
|
(fast-empty? [s]
|
||||||
|
(empty? s)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- do-keypath-transform [vals structure key next-fn]
|
||||||
|
(let [newv (next-fn vals (get structure key))]
|
||||||
|
(if (identical? newv i/NONE)
|
||||||
|
(if (sequential? structure)
|
||||||
|
(i/srange-transform* structure key (inc key) (fn [_] []))
|
||||||
|
(dissoc structure key))
|
||||||
|
(assoc structure key newv))))
|
||||||
|
|
||||||
|
(defrichnav
|
||||||
|
^{: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]
|
||||||
|
(next-fn vals (get structure key)))
|
||||||
|
(transform* [this vals structure next-fn]
|
||||||
|
(do-keypath-transform vals structure key next-fn)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defrichnav
|
||||||
|
^{: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]
|
||||||
|
(if (contains? structure k)
|
||||||
|
(next-fn vals (get structure k))
|
||||||
|
i/NONE))
|
||||||
|
(transform* [this vals structure next-fn]
|
||||||
|
(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))))
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
(ns com.rpl.specter.prot-opt-invoke)
|
|
||||||
|
|
||||||
(defmacro mk-optimized-invocation [protocol obj method num-args]
|
|
||||||
(let [args (take num-args (repeatedly gensym))
|
|
||||||
o (-> (gensym) (with-meta {:tag 'not-native}))]
|
|
||||||
`(if (~'implements? ~protocol ~obj)
|
|
||||||
(fn [~o ~@args]
|
|
||||||
(~method ~o ~@args)
|
|
||||||
)
|
|
||||||
~method
|
|
||||||
)))
|
|
||||||
27
src/clj/com/rpl/specter/protocols.cljc
Normal file
27
src/clj/com/rpl/specter/protocols.cljc
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
(ns com.rpl.specter.protocols)
|
||||||
|
|
||||||
|
(defprotocol RichNavigator
|
||||||
|
"Do not use this protocol directly. All navigators must be created using macros
|
||||||
|
in com.rpl.specter namespace."
|
||||||
|
(select* [this vals structure next-fn]
|
||||||
|
"An implementation of `select*` must call `next-fn` on each
|
||||||
|
subvalue of `structure`. The result of `select*` is specified
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
1. `NONE` if `next-fn` never called
|
||||||
|
2. `NONE` if all calls to `next-fn` return `NONE`
|
||||||
|
3. Otherwise, any non-`NONE` return value from calling `next-fn`
|
||||||
|
")
|
||||||
|
(transform* [this vals structure next-fn]
|
||||||
|
"An implementation of `transform*` must use `next-fn` to transform
|
||||||
|
any subvalues of `structure` and then merge those transformed values
|
||||||
|
back into `structure`. Everything else in `structure` must be unchanged."))
|
||||||
|
|
||||||
|
|
||||||
|
(defprotocol Collector
|
||||||
|
"Do not use this protocol directly. All navigators must be created using
|
||||||
|
macros in com.rpl.specter namespace."
|
||||||
|
(collect-val [this structure]))
|
||||||
|
|
||||||
|
(defprotocol ImplicitNav
|
||||||
|
(implicit-nav [obj]))
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
(ns com.rpl.specter.protocols)
|
|
||||||
|
|
||||||
(defprotocol StructurePath
|
|
||||||
(select* [this structure next-fn])
|
|
||||||
(transform* [this structure next-fn]))
|
|
||||||
|
|
||||||
(defprotocol Collector
|
|
||||||
(collect-val [this structure]))
|
|
||||||
100
src/clj/com/rpl/specter/transients.cljc
Normal file
100
src/clj/com/rpl/specter/transients.cljc
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
(ns com.rpl.specter.transients
|
||||||
|
#?(:cljs
|
||||||
|
(:require-macros [com.rpl.specter
|
||||||
|
:refer
|
||||||
|
[defnav]]))
|
||||||
|
(:use #?(:clj
|
||||||
|
[com.rpl.specter :only
|
||||||
|
[defnav]]))
|
||||||
|
(:require [com.rpl.specter.navs :as n]
|
||||||
|
[com.rpl.specter :refer [subselect selected?]]))
|
||||||
|
|
||||||
|
(defnav
|
||||||
|
^{:doc "Navigates to the specified key of a transient collection,
|
||||||
|
navigating to nil if it doesn't exist."}
|
||||||
|
keypath!
|
||||||
|
[key]
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn (get structure key)))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(assoc! structure key (next-fn (get structure key)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defnav
|
||||||
|
^{:doc "Navigates to an empty (persistent) vector at the end of a transient vector."}
|
||||||
|
END!
|
||||||
|
[]
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn []))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(let [res (next-fn [])]
|
||||||
|
(reduce conj! structure res))))
|
||||||
|
|
||||||
|
(defn- t-get-first
|
||||||
|
[tv]
|
||||||
|
(nth tv 0))
|
||||||
|
|
||||||
|
(defn- t-get-last
|
||||||
|
[tv]
|
||||||
|
(nth tv (dec (n/transient-vec-count tv))))
|
||||||
|
|
||||||
|
(defn- t-update-first
|
||||||
|
[tv next-fn]
|
||||||
|
(assoc! tv 0 (next-fn (nth tv 0))))
|
||||||
|
|
||||||
|
(defn- t-update-last
|
||||||
|
[tv next-fn]
|
||||||
|
(let [i (dec (n/transient-vec-count tv))]
|
||||||
|
(assoc! tv i (next-fn (nth tv i)))))
|
||||||
|
|
||||||
|
|
||||||
|
(def FIRST!
|
||||||
|
"Navigates to the first element of a transient vector."
|
||||||
|
(n/PosNavigator t-get-first t-update-first))
|
||||||
|
|
||||||
|
(def LAST!
|
||||||
|
"Navigates to the last element of a transient vector."
|
||||||
|
(n/PosNavigator t-get-last t-update-last))
|
||||||
|
|
||||||
|
#?(
|
||||||
|
:clj
|
||||||
|
(defn- select-keys-from-transient-map
|
||||||
|
"Selects keys from transient map, because built-in select-keys uses
|
||||||
|
`find` which is unsupported."
|
||||||
|
[m m-keys]
|
||||||
|
(loop [result {}
|
||||||
|
m-keys m-keys]
|
||||||
|
(if-not (seq m-keys)
|
||||||
|
result
|
||||||
|
(let [k (first m-keys)
|
||||||
|
;; support Clojure 1.6 where contains? is broken on transients
|
||||||
|
item (get m k ::not-found)]
|
||||||
|
(recur (if-not (identical? item ::not-found)
|
||||||
|
(assoc result k item)
|
||||||
|
result)
|
||||||
|
(rest m-keys))))))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn- select-keys-from-transient-map
|
||||||
|
"Uses select-keys on a transient map."
|
||||||
|
[m m-keys]
|
||||||
|
(select-keys m m-keys)))
|
||||||
|
|
||||||
|
|
||||||
|
(defnav
|
||||||
|
^{:doc "Navigates to the specified persistent submap of a transient map."}
|
||||||
|
submap!
|
||||||
|
[m-keys]
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn (select-keys-from-transient-map structure m-keys)))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(let [selected (select-keys-from-transient-map structure m-keys)
|
||||||
|
res (next-fn selected)]
|
||||||
|
(as-> structure %
|
||||||
|
(reduce (fn [m k]
|
||||||
|
(dissoc! m k))
|
||||||
|
% m-keys)
|
||||||
|
(reduce-kv (fn [m k v]
|
||||||
|
(assoc! m k v))
|
||||||
|
% res)))))
|
||||||
68
src/clj/com/rpl/specter/util_macros.clj
Normal file
68
src/clj/com/rpl/specter/util_macros.clj
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
(ns com.rpl.specter.util-macros)
|
||||||
|
|
||||||
|
(defmacro doseqres [backup-res [n aseq] & body]
|
||||||
|
`(reduce
|
||||||
|
(fn [curr# ~n]
|
||||||
|
(let [ret# (do ~@body)]
|
||||||
|
(if (identical? ret# ~backup-res)
|
||||||
|
curr#
|
||||||
|
(if (reduced? ret#) (reduced ret#) ret#))))
|
||||||
|
|
||||||
|
~backup-res
|
||||||
|
~aseq))
|
||||||
|
|
||||||
|
(defn- gensyms [amt]
|
||||||
|
(vec (repeatedly amt gensym)))
|
||||||
|
|
||||||
|
(defmacro mk-comp-navs []
|
||||||
|
(let [impls (for [i (range 3 20)]
|
||||||
|
(let [[fsym & rsyms :as syms] (gensyms i)]
|
||||||
|
`([~@syms] (~'comp-navs ~fsym (~'comp-navs ~@rsyms)))))
|
||||||
|
last-syms (gensyms 19)]
|
||||||
|
`(defn ~'comp-navs
|
||||||
|
([] ~'com.rpl.specter.impl/STAY*)
|
||||||
|
([nav1#] nav1#)
|
||||||
|
([nav1# nav2#] (~'com.rpl.specter.impl/combine-two-navs nav1# nav2#))
|
||||||
|
~@impls
|
||||||
|
([~@last-syms ~'& rest#]
|
||||||
|
(~'comp-navs
|
||||||
|
(~'comp-navs ~@last-syms)
|
||||||
|
(reduce ~'comp-navs rest#))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;TODO: move these definitions somewhere else
|
||||||
|
(defn late-fn-record-name [i]
|
||||||
|
(symbol (str "LateFn" i)))
|
||||||
|
|
||||||
|
(defn late-fn-record-constructor-name [i]
|
||||||
|
(symbol (str "->LateFn" i)))
|
||||||
|
|
||||||
|
(defn- mk-late-fn-record [i]
|
||||||
|
(let [fields (concat ['fn] (for [j (range i)] (symbol (str "arg" j))))
|
||||||
|
dparams (gensym "dynamic-params")
|
||||||
|
resolvers (for [f fields]
|
||||||
|
`(~'late-resolve ~f ~dparams))]
|
||||||
|
`(defrecord ~(late-fn-record-name i) [~@fields]
|
||||||
|
~'LateResolve
|
||||||
|
(~'late-resolve [this# ~dparams]
|
||||||
|
(~@resolvers)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defmacro mk-late-fn-records []
|
||||||
|
(let [impls (for [i (range 20)] (mk-late-fn-record i))]
|
||||||
|
`(do ~@impls)))
|
||||||
|
|
||||||
|
(defmacro mk-late-fn []
|
||||||
|
(let [f (gensym "afn")
|
||||||
|
args (gensym "args")
|
||||||
|
cases (for [i (range 19)]
|
||||||
|
[i
|
||||||
|
(let [gets (for [j (range i)] `(nth ~args ~j))]
|
||||||
|
`(~(late-fn-record-constructor-name i)
|
||||||
|
~f
|
||||||
|
~@gets))])]
|
||||||
|
`(defn ~'late-fn [~f ~args]
|
||||||
|
(case (count ~args)
|
||||||
|
~@(apply concat cases)
|
||||||
|
(throw (ex-info "Cannot have late function with more than 18 args" {}))))))
|
||||||
138
src/clj/com/rpl/specter/zipper.cljc
Normal file
138
src/clj/com/rpl/specter/zipper.cljc
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
(ns com.rpl.specter.zipper
|
||||||
|
#?(:cljs (:require-macros
|
||||||
|
[com.rpl.specter
|
||||||
|
:refer [defnav nav declarepath providepath recursive-path]]))
|
||||||
|
#?(:clj
|
||||||
|
(:use
|
||||||
|
[com.rpl.specter :only [defnav nav declarepath providepath
|
||||||
|
recursive-path]]))
|
||||||
|
(:require [com.rpl.specter :as s]
|
||||||
|
[clojure.zip :as zip]))
|
||||||
|
|
||||||
|
(defnav zipper [constructor]
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn (constructor structure)))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(zip/root (next-fn (constructor structure)))))
|
||||||
|
|
||||||
|
|
||||||
|
(def VECTOR-ZIP (zipper zip/vector-zip))
|
||||||
|
(def SEQ-ZIP (zipper zip/seq-zip))
|
||||||
|
(def XML-ZIP (zipper zip/xml-zip))
|
||||||
|
|
||||||
|
|
||||||
|
(def ^{:doc "Navigate to the next element in the structure.
|
||||||
|
If no next element, works like STOP."}
|
||||||
|
NEXT
|
||||||
|
(s/comp-paths
|
||||||
|
(s/view zip/next)
|
||||||
|
(s/if-path zip/end?
|
||||||
|
s/STOP
|
||||||
|
s/STAY)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- mk-zip-nav [znav]
|
||||||
|
(nav []
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(let [ret (znav structure)]
|
||||||
|
(if ret (next-fn ret))))
|
||||||
|
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(let [ret (znav structure)]
|
||||||
|
(if ret (next-fn ret) structure)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; (multi-path RIGHT LEFT) will not navigate to the right and left
|
||||||
|
;; of the currently navigated element because locations aren't stable
|
||||||
|
;; like they are for maps/graphs. The path following RIGHT could
|
||||||
|
;; insert lots of elements all over the sequence, and there's no
|
||||||
|
;; way to determine how to get "back".
|
||||||
|
(def ^{:doc "Navigate to the element to the right.
|
||||||
|
If no element there, works like STOP."}
|
||||||
|
RIGHT (mk-zip-nav zip/right))
|
||||||
|
|
||||||
|
(def ^{:doc "Navigate to the element to the left.
|
||||||
|
If no element there, works like STOP."}
|
||||||
|
LEFT (mk-zip-nav zip/left))
|
||||||
|
|
||||||
|
(def DOWN (mk-zip-nav zip/down))
|
||||||
|
(def UP (mk-zip-nav zip/up))
|
||||||
|
|
||||||
|
(def ^{:doc "Navigate to the previous element.
|
||||||
|
If this is the first element, works like STOP."}
|
||||||
|
PREV (mk-zip-nav zip/prev))
|
||||||
|
|
||||||
|
(def RIGHTMOST (s/view zip/rightmost))
|
||||||
|
(def LEFTMOST (s/view zip/leftmost))
|
||||||
|
|
||||||
|
(defn- inner-insert [structure next-fn inserter mover backer]
|
||||||
|
(let [to-insert (next-fn [])
|
||||||
|
inserts (reduce
|
||||||
|
(fn [z e] (-> z (inserter e) mover))
|
||||||
|
structure
|
||||||
|
to-insert)]
|
||||||
|
|
||||||
|
(if backer
|
||||||
|
(reduce (fn [z _] (backer z)) inserts to-insert)
|
||||||
|
inserts)))
|
||||||
|
|
||||||
|
|
||||||
|
(defnav ^{:doc "Navigate to the empty subsequence directly to the
|
||||||
|
right of this element."}
|
||||||
|
INNER-RIGHT []
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn []))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(inner-insert structure next-fn zip/insert-right zip/right zip/left)))
|
||||||
|
|
||||||
|
|
||||||
|
(defnav ^{:doc "Navigate to the empty subsequence directly to the
|
||||||
|
left of this element."}
|
||||||
|
INNER-LEFT []
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn []))
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(inner-insert structure next-fn zip/insert-left identity nil)))
|
||||||
|
|
||||||
|
|
||||||
|
(defnav NODE []
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn (zip/node structure)))
|
||||||
|
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(zip/edit structure next-fn)))
|
||||||
|
|
||||||
|
|
||||||
|
(defnav ^{:doc "Navigate to the subsequence containing only
|
||||||
|
the node currently pointed to. This works just
|
||||||
|
like srange and can be used to remove elements
|
||||||
|
from the structure"}
|
||||||
|
NODE-SEQ []
|
||||||
|
(select* [this structure next-fn]
|
||||||
|
(next-fn [(zip/node structure)]))
|
||||||
|
|
||||||
|
(transform* [this structure next-fn]
|
||||||
|
(let [to-insert (next-fn [(zip/node structure)])
|
||||||
|
inserted (reduce zip/insert-left structure to-insert)]
|
||||||
|
(zip/remove inserted))))
|
||||||
|
|
||||||
|
|
||||||
|
(def ^{:doc "Navigate the zipper to the first element
|
||||||
|
in the structure matching predfn. A linear scan
|
||||||
|
is done using NEXT to find the element."}
|
||||||
|
find-first
|
||||||
|
(recursive-path [predfn] p
|
||||||
|
(s/if-path [NODE (s/pred predfn)]
|
||||||
|
s/STAY
|
||||||
|
[NEXT p])))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(declarepath ^{:doc "Navigate to every element reachable using calls
|
||||||
|
to NEXT"}
|
||||||
|
NEXT-WALK)
|
||||||
|
|
||||||
|
(providepath NEXT-WALK
|
||||||
|
(s/stay-then-continue
|
||||||
|
NEXT
|
||||||
|
NEXT-WALK))
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
(ns com.rpl.specter.zipper
|
|
||||||
#+cljs (:require-macros
|
|
||||||
[com.rpl.specter.macros
|
|
||||||
:refer [fixed-pathed-path defpath]])
|
|
||||||
(:use
|
|
||||||
#+clj [com.rpl.specter.macros :only [fixed-pathed-path defpath]]
|
|
||||||
[com.rpl specter])
|
|
||||||
(:require [clojure [zip :as zip]]))
|
|
||||||
|
|
||||||
(defpath zipper [constructor]
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (constructor structure)))
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(zip/root (next-fn (constructor structure)))
|
|
||||||
))
|
|
||||||
|
|
||||||
(def VECTOR-ZIP (zipper zip/vector-zip))
|
|
||||||
(def SEQ-ZIP (zipper zip/seq-zip))
|
|
||||||
(def XML-ZIP (zipper zip/xml-zip))
|
|
||||||
|
|
||||||
(def NEXT (view zip/next))
|
|
||||||
(def RIGHT (view zip/right))
|
|
||||||
(def RIGHTMOST (view zip/rightmost))
|
|
||||||
(def LEFT (view zip/left))
|
|
||||||
(def DOWN (view zip/down))
|
|
||||||
(def LEFTMOST (view zip/leftmost))
|
|
||||||
(def UP (view zip/up))
|
|
||||||
|
|
||||||
(defpath NODE []
|
|
||||||
(select* [this structure next-fn]
|
|
||||||
(next-fn (zip/node structure))
|
|
||||||
)
|
|
||||||
(transform* [this structure next-fn]
|
|
||||||
(zip/edit structure next-fn)
|
|
||||||
))
|
|
||||||
17
src/java/com/rpl/specter/MutableCell.java
Normal file
17
src/java/com/rpl/specter/MutableCell.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.rpl.specter;
|
||||||
|
|
||||||
|
public class MutableCell {
|
||||||
|
private Object o;
|
||||||
|
|
||||||
|
public MutableCell(Object o) {
|
||||||
|
this.o = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object get() {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Object o) {
|
||||||
|
this.o = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
vars (vec (map first parts))
|
vars (vec (map first parts))
|
||||||
genned (reduce
|
genned (reduce
|
||||||
(fn [curr [v code]]
|
(fn [curr [v code]]
|
||||||
`(cljs.test.check.generators/bind ~code (fn [~v] ~curr)))
|
`(clojure.test.check.generators/bind ~code (fn [~v] ~curr)))
|
||||||
`(cljs.test.check.generators/return ~vars)
|
`(clojure.test.check.generators/return ~vars)
|
||||||
(reverse parts))]
|
(reverse parts))]
|
||||||
`(cljs.test.check.properties/for-all [~vars ~genned]
|
`(clojure.test.check.properties/for-all [~vars ~genned]
|
||||||
~@body )))
|
~@body)))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
(ns com.rpl.specter.cljs-test-runner
|
(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.core-test]
|
||||||
|
[com.rpl.specter.zipper-test]))
|
||||||
|
|
||||||
(run-tests 'com.rpl.specter.core-test)
|
(doo-tests 'com.rpl.specter.core-test
|
||||||
|
'com.rpl.specter.zipper-test)
|
||||||
|
|
|
||||||
1713
test/com/rpl/specter/core_test.cljc
Normal file
1713
test/com/rpl/specter/core_test.cljc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,742 +0,0 @@
|
||||||
(ns com.rpl.specter.core-test
|
|
||||||
#+cljs (:require-macros
|
|
||||||
[cljs.test :refer [is deftest]]
|
|
||||||
[cljs.test.check.cljs-test :refer [defspec]]
|
|
||||||
[com.rpl.specter.cljs-test-helpers :refer [for-all+]]
|
|
||||||
[com.rpl.specter.macros
|
|
||||||
:refer [paramsfn defprotocolpath defpath extend-protocolpath
|
|
||||||
declarepath providepath]])
|
|
||||||
(:use
|
|
||||||
#+clj [clojure.test :only [deftest is]]
|
|
||||||
#+clj [clojure.test.check.clojure-test :only [defspec]]
|
|
||||||
#+clj [com.rpl.specter.test-helpers :only [for-all+]]
|
|
||||||
#+clj [com.rpl.specter.macros
|
|
||||||
:only [paramsfn defprotocolpath defpath extend-protocolpath
|
|
||||||
declarepath providepath]]
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
(: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]
|
|
||||||
[com.rpl.specter :as s]
|
|
||||||
[clojure.set :as set]))
|
|
||||||
|
|
||||||
;;TODO:
|
|
||||||
;; test walk, codewalk
|
|
||||||
|
|
||||||
(defn limit-size [n {gen :gen}]
|
|
||||||
(gen/->Generator
|
|
||||||
(fn [rnd _size]
|
|
||||||
(gen rnd (if (< _size n) _size n)))))
|
|
||||||
|
|
||||||
(defn gen-map-with-keys [key-gen val-gen & keys]
|
|
||||||
(gen/bind (gen/map key-gen val-gen)
|
|
||||||
(fn [m]
|
|
||||||
(gen/bind
|
|
||||||
(apply gen/hash-map (mapcat (fn [k] [k val-gen]) keys))
|
|
||||||
(fn [m2]
|
|
||||||
(gen/return (merge m m2)))))))
|
|
||||||
|
|
||||||
(defspec select-all-keyword-filter
|
|
||||||
(for-all+
|
|
||||||
[kw gen/keyword
|
|
||||||
v (gen/vector (limit-size 5
|
|
||||||
(gen-map-with-keys gen/keyword gen/int kw)))
|
|
||||||
pred (gen/elements [odd? even?])]
|
|
||||||
(= (s/select [s/ALL kw pred] v)
|
|
||||||
(->> v (map kw) (filter pred))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec select-pos-extreme-pred
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
pred (gen/elements [odd? even?])
|
|
||||||
pos (gen/elements [[s/FIRST first] [s/LAST last]])]
|
|
||||||
(= (s/select-one [(s/filterer pred) (first pos)] v)
|
|
||||||
(->> v (filter pred) ((last pos)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec select-all-on-map
|
|
||||||
(for-all+
|
|
||||||
[m (limit-size 5 (gen/map gen/keyword gen/int))]
|
|
||||||
(= (s/select [s/ALL s/LAST] m)
|
|
||||||
(for [[k v] m] v))
|
|
||||||
))
|
|
||||||
|
|
||||||
(deftest select-one-test
|
|
||||||
(is (thrown? #+clj Exception #+cljs js/Error (s/select-one [s/ALL even?] [1 2 3 4])))
|
|
||||||
(is (= 1 (s/select-one [s/ALL odd?] [2 4 1 6])))
|
|
||||||
)
|
|
||||||
|
|
||||||
(deftest select-first-test
|
|
||||||
(is (= 7 (s/select-first [(s/filterer odd?) s/ALL #(> % 4)] [3 4 2 3 7 5 9 8])))
|
|
||||||
(is (nil? (s/select-first [s/ALL even?] [1 3 5 9])))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defspec transform-all-on-map
|
|
||||||
(for-all+
|
|
||||||
[m (limit-size 5 (gen/map gen/keyword gen/int))]
|
|
||||||
(= (s/transform [s/ALL s/LAST] inc m)
|
|
||||||
(into {} (for [[k v] m] [k (inc v)]))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-all
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)]
|
|
||||||
(let [v2 (s/transform [s/ALL] inc v)]
|
|
||||||
(and (vector? v2) (= v2 (map inc v)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-all-list
|
|
||||||
(for-all+
|
|
||||||
[v (gen/list gen/int)]
|
|
||||||
(let [v2 (s/transform [s/ALL] inc v)]
|
|
||||||
(and (seq? v2) (= v2 (map inc v)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-all-filter
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
pred (gen/elements [odd? even?])
|
|
||||||
action (gen/elements [inc dec])]
|
|
||||||
(let [v2 (s/transform [s/ALL pred] action v)]
|
|
||||||
(= v2 (map (fn [v] (if (pred v) (action v) v)) v))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-last
|
|
||||||
(for-all+
|
|
||||||
[v (gen/not-empty (gen/vector gen/int))
|
|
||||||
pred (gen/elements [inc dec])]
|
|
||||||
(let [v2 (s/transform [s/LAST] pred v)]
|
|
||||||
(= v2 (concat (butlast v) [(pred (last v))]))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-first
|
|
||||||
(for-all+
|
|
||||||
[v (gen/not-empty (gen/vector gen/int))
|
|
||||||
pred (gen/elements [inc dec])]
|
|
||||||
(let [v2 (s/transform [s/FIRST] pred v)]
|
|
||||||
(= v2 (concat [(pred (first v))] (rest v) ))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec transform-filterer-all-equivalency
|
|
||||||
(prop/for-all
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
pred (gen/elements [even? odd?])
|
|
||||||
updater (gen/elements [inc dec])]
|
|
||||||
(let [v2 (s/transform [(s/filterer pred) s/ALL] updater v)
|
|
||||||
v3 (s/transform [s/ALL pred] updater v)]
|
|
||||||
(= v2 v3))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defspec transform-with-context
|
|
||||||
(for-all+
|
|
||||||
[kw1 gen/keyword
|
|
||||||
kw2 gen/keyword
|
|
||||||
m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2))
|
|
||||||
pred (gen/elements [odd? even?])]
|
|
||||||
(= (s/transform [(s/collect-one kw2) kw1 pred] + m)
|
|
||||||
(if (pred (kw1 m))
|
|
||||||
(assoc m kw1 (+ (kw1 m) (kw2 m)))
|
|
||||||
m
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn differing-elements [v1 v2]
|
|
||||||
(->> (map vector v1 v2)
|
|
||||||
(map-indexed (fn [i [e1 e2]]
|
|
||||||
(if (not= e1 e2)
|
|
||||||
i)))
|
|
||||||
(filter identity)))
|
|
||||||
|
|
||||||
(defspec transform-last-compound
|
|
||||||
(for-all+
|
|
||||||
[pred (gen/elements [odd? even?])
|
|
||||||
v (gen/such-that #(some pred %) (gen/vector gen/int))]
|
|
||||||
(let [v2 (s/transform [(s/filterer pred) s/LAST] inc v)
|
|
||||||
differing-elems (differing-elements v v2)]
|
|
||||||
(and (= (count v2) (count v))
|
|
||||||
(= (count differing-elems) 1)
|
|
||||||
(every? (complement pred) (drop (first differing-elems) v2))
|
|
||||||
))))
|
|
||||||
|
|
||||||
;; max sizes prevent too much data from being generated and keeps test from taking forever
|
|
||||||
(defspec transform-keyword
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
m1 (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
(gen-map-with-keys gen/keyword gen/int k2)
|
|
||||||
k1))
|
|
||||||
pred (gen/elements [inc dec])]
|
|
||||||
(let [m2 (s/transform [k1 k2] pred m1)]
|
|
||||||
(and (= (assoc-in m1 [k1 k2] nil) (assoc-in m2 [k1 k2] nil))
|
|
||||||
(= (pred (get-in m1 [k1 k2])) (get-in m2 [k1 k2])))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec replace-in-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)]
|
|
||||||
(let [res (->> v (map (fn [v] (if (even? v) (inc v) v))))
|
|
||||||
user-ret (->> v
|
|
||||||
(filter even?)
|
|
||||||
(map (fn [v] [v v]))
|
|
||||||
(apply concat))
|
|
||||||
user-ret (if (empty? user-ret) nil user-ret)]
|
|
||||||
(= (s/replace-in [s/ALL even?] (fn [v] [(inc v) [v v]]) v)
|
|
||||||
[res user-ret]
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec replace-in-custom-merge
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)]
|
|
||||||
(let [res (->> v (map (fn [v] (if (even? v) (inc v) v))))
|
|
||||||
last-even (->> v (filter even?) last)
|
|
||||||
user-ret (if last-even {:a last-even})]
|
|
||||||
(= (s/replace-in [s/ALL even?] (fn [v] [(inc v) v]) v :merge-fn (fn [curr new]
|
|
||||||
(assoc curr :a new)))
|
|
||||||
[res user-ret]
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec srange-extremes-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
v2 (gen/vector gen/int)]
|
|
||||||
(let [b (s/setval s/BEGINNING v2 v)
|
|
||||||
e (s/setval s/END v2 v)]
|
|
||||||
(and (= b (concat v2 v))
|
|
||||||
(= e (concat v v2)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec srange-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
b (gen/elements (-> v count inc range))
|
|
||||||
e (gen/elements (range b (-> v count inc)))
|
|
||||||
]
|
|
||||||
(let [sv (subvec v b e)
|
|
||||||
predcount (fn [pred v] (->> v (filter pred) count))
|
|
||||||
even-count (partial predcount even?)
|
|
||||||
odd-count (partial predcount odd?)
|
|
||||||
b (s/transform (s/srange b e) (fn [r] (filter odd? r)) v)]
|
|
||||||
(and (= (odd-count v) (odd-count b))
|
|
||||||
(= (+ (even-count b) (even-count sv))
|
|
||||||
(even-count v)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest structure-path-directly-test
|
|
||||||
(is (= 3 (s/select-one :b {:a 1 :b 3})))
|
|
||||||
(is (= 5 (s/select-one (s/comp-paths :a :b) {:a {:b 5}})))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defspec view-test
|
|
||||||
(for-all+
|
|
||||||
[i gen/int
|
|
||||||
afn (gen/elements [inc dec])]
|
|
||||||
(= (first (s/select (s/view afn) i))
|
|
||||||
(afn i)
|
|
||||||
(s/transform (s/view afn) identity i)
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest selected?-test
|
|
||||||
(is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []]
|
|
||||||
(s/setval [s/ALL (s/selected? s/ALL even?) s/END]
|
|
||||||
[:a]
|
|
||||||
[[1 3 5] [2] [7 11 4 2] [10 1] []]
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec identity-test
|
|
||||||
(for-all+
|
|
||||||
[i gen/int
|
|
||||||
afn (gen/elements [inc dec])]
|
|
||||||
(and (= [i] (s/select nil i))
|
|
||||||
(= (afn i) (s/transform nil afn i)))))
|
|
||||||
|
|
||||||
(deftest nil-comp-test
|
|
||||||
(is (= [5] (s/select (com.rpl.specter.impl/comp-paths* nil) 5))))
|
|
||||||
|
|
||||||
(defspec putval-test
|
|
||||||
(for-all+
|
|
||||||
[kw gen/keyword
|
|
||||||
m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))
|
|
||||||
c gen/int]
|
|
||||||
(= (s/transform [(s/putval c) kw] + m)
|
|
||||||
(s/transform [kw (s/putval c)] + m)
|
|
||||||
(assoc m kw (+ c (get m kw)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec empty-selector-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)]
|
|
||||||
(= [v]
|
|
||||||
(s/select [] v)
|
|
||||||
(s/select nil v)
|
|
||||||
(s/select (s/comp-paths) v)
|
|
||||||
(s/select (s/comp-paths nil) v)
|
|
||||||
(s/select [nil nil nil] v)
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defspec empty-selector-transform-test
|
|
||||||
(for-all+
|
|
||||||
[kw gen/keyword
|
|
||||||
m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))]
|
|
||||||
(and (= m
|
|
||||||
(s/transform nil identity m)
|
|
||||||
(s/transform [] identity m)
|
|
||||||
(s/transform (s/comp-paths []) identity m)
|
|
||||||
(s/transform (s/comp-paths nil nil) identity m)
|
|
||||||
)
|
|
||||||
(= (s/transform kw inc m)
|
|
||||||
(s/transform [nil kw] inc m)
|
|
||||||
(s/transform (s/comp-paths kw nil) inc m)
|
|
||||||
(s/transform (s/comp-paths nil kw nil) inc m)
|
|
||||||
))))
|
|
||||||
|
|
||||||
(deftest compose-empty-comp-path-test
|
|
||||||
(let [m {:a 1}]
|
|
||||||
(is (= [1]
|
|
||||||
(s/select [:a (s/comp-paths)] m)
|
|
||||||
(s/select [(s/comp-paths) :a] m)
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec mixed-selector-test
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
m (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
(gen-map-with-keys gen/keyword gen/int k2)
|
|
||||||
k1))]
|
|
||||||
(= [(-> m k1 k2)]
|
|
||||||
(s/select [k1 (s/comp-paths k2)] m)
|
|
||||||
(s/select [(s/comp-paths k1) k2] m)
|
|
||||||
(s/select [(s/comp-paths k1 k2) nil] m)
|
|
||||||
(s/select [(s/comp-paths) k1 k2] m)
|
|
||||||
(s/select [k1 (s/comp-paths) k2] m)
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest cond-path-test
|
|
||||||
(is (= [4 2 6 8 10]
|
|
||||||
(s/select [s/ALL (s/cond-path even? [(s/view inc) (s/view inc)]
|
|
||||||
#(= 3 %) (s/view dec))]
|
|
||||||
[1 2 3 4 5 6 7 8])))
|
|
||||||
(is (empty? (s/select (s/if-path odd? (s/view inc)) 2)))
|
|
||||||
(is (= [6 2 10 6 14]
|
|
||||||
(s/transform [(s/putval 2)
|
|
||||||
s/ALL
|
|
||||||
(s/if-path odd? [(s/view inc) (s/view inc)] (s/view dec))]
|
|
||||||
*
|
|
||||||
[1 2 3 4 5]
|
|
||||||
)))
|
|
||||||
(is (= 2
|
|
||||||
(s/transform [(s/putval 2)
|
|
||||||
(s/if-path odd? (s/view inc))]
|
|
||||||
*
|
|
||||||
2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defspec cond-path-selector-test
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
k3 (limit-size 3 gen/keyword)
|
|
||||||
m (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
gen/int
|
|
||||||
k1
|
|
||||||
k2
|
|
||||||
k3))
|
|
||||||
pred (gen/elements [odd? even?])
|
|
||||||
]
|
|
||||||
(let [v1 (get m k1)
|
|
||||||
k (if (pred v1) k2 k3)]
|
|
||||||
(and
|
|
||||||
(= (s/transform (s/if-path [k1 pred] k2 k3) inc m)
|
|
||||||
(s/transform k inc m))
|
|
||||||
(= (s/select (s/if-path [k1 pred] k2 k3) m)
|
|
||||||
(s/select k m))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec multi-path-test
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
m (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
gen/int
|
|
||||||
k1
|
|
||||||
k2))
|
|
||||||
]
|
|
||||||
(= (s/transform (s/multi-path k1 k2) inc m)
|
|
||||||
(->> m
|
|
||||||
(s/transform k1 inc)
|
|
||||||
(s/transform k2 inc)))
|
|
||||||
))
|
|
||||||
|
|
||||||
(deftest empty-pos-transform
|
|
||||||
(is (empty? (s/select s/FIRST [])))
|
|
||||||
(is (empty? (s/select s/LAST [])))
|
|
||||||
(is (= [] (s/transform s/FIRST inc [])))
|
|
||||||
(is (= [] (s/transform s/LAST inc [])))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defspec set-filter-test
|
|
||||||
(for-all+
|
|
||||||
[k1 gen/keyword
|
|
||||||
k2 (gen/such-that #(not= k1 %) gen/keyword)
|
|
||||||
k3 (gen/such-that (complement #{k1 k2}) gen/keyword)
|
|
||||||
v (gen/vector (gen/elements [k1 k2 k3]))]
|
|
||||||
(= (filter #{k1 k2} v) (s/select [s/ALL #{k1 k2}] v))
|
|
||||||
))
|
|
||||||
|
|
||||||
(deftest nil-select-one-test
|
|
||||||
(is (= nil (s/select-one! s/ALL [nil])))
|
|
||||||
(is (thrown? #+clj Exception #+cljs js/Error (s/select-one! s/ALL [])))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
(defspec transformed-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector gen/int)
|
|
||||||
pred (gen/elements [even? odd?])
|
|
||||||
op (gen/elements [inc dec])]
|
|
||||||
(= (s/select-one (s/transformed [s/ALL pred] op) v)
|
|
||||||
(s/transform [s/ALL pred] op v))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defspec basic-parameterized-composition-test
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
m1 (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
(gen-map-with-keys gen/keyword gen/int k2)
|
|
||||||
k1))
|
|
||||||
pred (gen/elements [inc dec])]
|
|
||||||
(let [p (s/comp-paths s/keypath s/keypath)]
|
|
||||||
(and
|
|
||||||
(= (s/compiled-select (p k1 k2) m1) (s/select [k1 k2] m1))
|
|
||||||
(= (s/compiled-transform (p k1 k2) pred m1) (s/transform [k1 k2] pred m1))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec various-orders-comp-test
|
|
||||||
(for-all+
|
|
||||||
[k1 (limit-size 3 gen/keyword)
|
|
||||||
k2 (limit-size 3 gen/keyword)
|
|
||||||
k3 (limit-size 3 gen/keyword)
|
|
||||||
m1 (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
gen/int
|
|
||||||
k3
|
|
||||||
)
|
|
||||||
k2)
|
|
||||||
k1))
|
|
||||||
pred (gen/elements [inc dec])]
|
|
||||||
(let [paths [((s/comp-paths s/keypath s/keypath k3) k1 k2)
|
|
||||||
(s/comp-paths k1 k2 k3)
|
|
||||||
((s/comp-paths s/keypath k2 s/keypath) k1 k3)
|
|
||||||
((s/comp-paths k1 s/keypath k3) k2)
|
|
||||||
(s/comp-paths k1 (s/keypath k2) k3)
|
|
||||||
((s/comp-paths (s/keypath k1) s/keypath (s/keypath k3)) k2)
|
|
||||||
((s/comp-paths s/keypath (s/keypath k2) s/keypath) k1 k3)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
(and
|
|
||||||
(apply = (for [p paths] (s/compiled-select p m1)))
|
|
||||||
(apply = (for [p paths] (s/compiled-transform p pred m1)))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec filterer-param-test
|
|
||||||
(for-all+
|
|
||||||
[k gen/keyword
|
|
||||||
k2 gen/keyword
|
|
||||||
v (gen/vector
|
|
||||||
(limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
gen/int
|
|
||||||
k
|
|
||||||
k2
|
|
||||||
)))
|
|
||||||
pred (gen/elements [odd? even?])
|
|
||||||
updater (gen/elements [inc dec])]
|
|
||||||
(and
|
|
||||||
(= (s/compiled-select ((s/filterer s/keypath pred) k) v)
|
|
||||||
(s/compiled-select (s/filterer k pred) v))
|
|
||||||
(= (s/compiled-transform ((s/comp-paths (s/filterer s/keypath pred) s/ALL k2) k)
|
|
||||||
updater
|
|
||||||
v)
|
|
||||||
(s/compiled-transform (s/comp-paths (s/filterer k pred) s/ALL k2)
|
|
||||||
updater
|
|
||||||
v))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest nested-param-paths
|
|
||||||
(let [p (s/filterer s/keypath (s/selected? s/ALL s/keypath (s/filterer s/keypath even?) s/ALL))
|
|
||||||
p2 (p :a :b :c)
|
|
||||||
p3 (s/filterer :a (s/selected? s/ALL :b (s/filterer :c even?) s/ALL))
|
|
||||||
data [{:a [{:b [{:c 4 :d 5}]}]}
|
|
||||||
{:a [{:c 3}]}
|
|
||||||
{:a [{:b [{:c 7}] :e [1]}]}]
|
|
||||||
]
|
|
||||||
(is (= (s/select p2 data)
|
|
||||||
(s/select p3 data)
|
|
||||||
[[{:a [{:b [{:c 4 :d 5}]}]}]]
|
|
||||||
))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defspec param-multi-path-test
|
|
||||||
(for-all+
|
|
||||||
[k1 gen/keyword
|
|
||||||
k2 gen/keyword
|
|
||||||
k3 gen/keyword
|
|
||||||
m (limit-size 5
|
|
||||||
(gen-map-with-keys
|
|
||||||
gen/keyword
|
|
||||||
gen/int
|
|
||||||
k1
|
|
||||||
k2
|
|
||||||
k3
|
|
||||||
))
|
|
||||||
pred1 (gen/elements [odd? even?])
|
|
||||||
pred2 (gen/elements [odd? even?])
|
|
||||||
updater (gen/elements [inc dec])
|
|
||||||
]
|
|
||||||
(let [paths [((s/multi-path [s/keypath pred1] [s/keypath pred2] k3) k1 k2)
|
|
||||||
((s/multi-path [k1 pred1] [s/keypath pred2] s/keypath) k2 k3)
|
|
||||||
((s/multi-path [s/keypath pred1] [s/keypath pred2] s/keypath) k1 k2 k3)
|
|
||||||
(s/multi-path [k1 pred1] [k2 pred2] k3)
|
|
||||||
((s/multi-path [k1 pred1] [s/keypath pred2] k3) k2)
|
|
||||||
]]
|
|
||||||
(and
|
|
||||||
(apply =
|
|
||||||
(for [p paths]
|
|
||||||
(s/select p m)
|
|
||||||
))
|
|
||||||
(apply =
|
|
||||||
(for [p paths]
|
|
||||||
(s/transform p updater m)
|
|
||||||
))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defspec paramsfn-test
|
|
||||||
(for-all+
|
|
||||||
[v (gen/vector (gen/elements (range 10)))
|
|
||||||
val (gen/elements (range 10))
|
|
||||||
op (gen/elements [inc dec])
|
|
||||||
comparator (gen/elements [= > <])]
|
|
||||||
(let [path (s/comp-paths s/ALL (paramsfn [p] [v] (comparator v p)))]
|
|
||||||
(= (s/transform (path val) op v)
|
|
||||||
(s/transform [s/ALL #(comparator % val)] op v)))
|
|
||||||
))
|
|
||||||
|
|
||||||
(defspec subset-test
|
|
||||||
(for-all+
|
|
||||||
[s1 (gen/vector (limit-size 5 gen/keyword))
|
|
||||||
s2 (gen/vector (limit-size 5 gen/keyword))
|
|
||||||
s3 (gen/vector (limit-size 5 gen/int))
|
|
||||||
s4 (gen/vector (limit-size 5 gen/keyword))]
|
|
||||||
(let [s1 (set s1)
|
|
||||||
s2 (set s1)
|
|
||||||
s3 (set s1)
|
|
||||||
s4 (set s1)
|
|
||||||
combined (set/union s1 s2)
|
|
||||||
ss (set/union s2 s3)]
|
|
||||||
(and
|
|
||||||
(= (s/transform (s/subset s3) identity combined) combined)
|
|
||||||
(= (s/setval (s/subset s3) #{} combined) (set/difference combined s2))
|
|
||||||
(= (s/setval (s/subset s3) s4 combined) (-> combined (set/difference s2) (set/union s4)))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(deftest nil->val-test
|
|
||||||
(is (= {:a #{:b}}
|
|
||||||
(s/setval [:a s/NIL->SET (s/subset #{})] #{:b} nil)))
|
|
||||||
(is (= {:a #{:b :c :d}}
|
|
||||||
(s/setval [:a s/NIL->SET (s/subset #{})] #{:b} {:a #{:c :d}})))
|
|
||||||
(is (= {:a [:b]}
|
|
||||||
(s/setval [:a s/NIL->VECTOR s/END] [:b] nil)))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defspec void-test
|
|
||||||
(for-all+
|
|
||||||
[s1 (gen/vector (limit-size 5 gen/int))]
|
|
||||||
(and
|
|
||||||
(empty? (s/select s/STOP s1))
|
|
||||||
(empty? (s/select [s/STOP s/ALL s/ALL s/ALL s/ALL] s1))
|
|
||||||
(= s1 (s/transform s/STOP inc s1))
|
|
||||||
(= s1 (s/transform [s/ALL s/STOP s/ALL] inc s1))
|
|
||||||
(= (s/transform [s/ALL (s/cond-path even? nil odd? s/STOP)] inc s1)
|
|
||||||
(s/transform [s/ALL even?] inc s1))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest stay-continue-tests
|
|
||||||
(is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b [:a :b]]]
|
|
||||||
(s/setval [(s/stay-then-continue s/ALL) s/END] [[:a :b]] [[1 2] [3]])))
|
|
||||||
(is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b]]
|
|
||||||
(s/setval [(s/continue-then-stay s/ALL) s/END] [[:a :b]] [[1 2] [3]])))
|
|
||||||
(is (= [[1 2 3] 1 3]
|
|
||||||
(s/select (s/stay-then-continue s/ALL odd?) [1 2 3])))
|
|
||||||
(is (= [1 3 [1 2 3]]
|
|
||||||
(s/select (s/continue-then-stay s/ALL odd?) [1 2 3])))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
(declarepath MyWalker)
|
|
||||||
|
|
||||||
(providepath MyWalker
|
|
||||||
(s/if-path vector?
|
|
||||||
(s/if-path [s/FIRST #(= :abc %)]
|
|
||||||
(s/continue-then-stay s/ALL MyWalker)
|
|
||||||
[s/ALL MyWalker]
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest recursive-path-test
|
|
||||||
(is (= [9 1 10 3 1]
|
|
||||||
(s/select [MyWalker s/ALL number?]
|
|
||||||
[:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]])
|
|
||||||
))
|
|
||||||
(is (= [:bb [:aa 34 [:abc 11 [:ccc 9 8 [:abc 10 2]]]] [:abc 2 [:abc 4]]]
|
|
||||||
(s/transform [MyWalker s/ALL number?] inc
|
|
||||||
[:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]])
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
(declarepath map-key-walker [akey])
|
|
||||||
|
|
||||||
(providepath map-key-walker
|
|
||||||
[s/ALL
|
|
||||||
(s/if-path [s/FIRST (paramsfn [akey] [curr] (= curr akey))]
|
|
||||||
s/LAST
|
|
||||||
[s/LAST (s/params-reset map-key-walker)])])
|
|
||||||
|
|
||||||
(deftest recursive-params-path-test
|
|
||||||
(is (= #{1 2 3} (set (s/select (map-key-walker :aaa)
|
|
||||||
{:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}}))))
|
|
||||||
(is (= {:a {:aaa 4 :b {:c {:aaa 3} :aaa 2}}}
|
|
||||||
(s/transform (map-key-walker :aaa) inc
|
|
||||||
{:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}})))
|
|
||||||
(is (= {:a {:c {:b "X"}}})
|
|
||||||
(s/setval (map-key-walker :b) "X" {:a {:c {:b {:d 1}}}}))
|
|
||||||
)
|
|
||||||
|
|
||||||
(deftest recursive-params-composable-path-test
|
|
||||||
(let [p (s/comp-paths s/keypath map-key-walker)]
|
|
||||||
(is (= [1] (s/select (p 1 :a) [{:a 3} {:a 1} {:a 2}])))
|
|
||||||
))
|
|
||||||
|
|
||||||
(deftest all-map-test
|
|
||||||
(is (= {3 3} (s/transform [s/ALL s/FIRST] inc {2 3})))
|
|
||||||
(is (= {3 21 4 31} (s/transform [s/ALL s/ALL] inc {2 20 3 30})))
|
|
||||||
)
|
|
||||||
|
|
||||||
(declarepath NestedHigherOrderWalker [k])
|
|
||||||
|
|
||||||
(providepath NestedHigherOrderWalker
|
|
||||||
(s/if-path vector?
|
|
||||||
(s/if-path [s/FIRST (paramsfn [k] [e] (= k e))]
|
|
||||||
(s/continue-then-stay s/ALL (s/params-reset NestedHigherOrderWalker))
|
|
||||||
[s/ALL (s/params-reset NestedHigherOrderWalker)]
|
|
||||||
)))
|
|
||||||
|
|
||||||
(deftest nested-higher-order-walker-test
|
|
||||||
(is (= [:q [:abc :I 3] [:ccc [:abc :I] [:abc :I "a" [:abc :I [:abc :I [:d]]]]]]
|
|
||||||
(s/setval [(NestedHigherOrderWalker :abc) (s/srange 1 1)]
|
|
||||||
[:I]
|
|
||||||
[:q [:abc 3] [:ccc [:abc] [:abc "a" [:abc [:abc [:d]]]]]]
|
|
||||||
))))
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(deftest large-params-test
|
|
||||||
(let [path (apply s/comp-paths (repeat 25 s/keypath))
|
|
||||||
m (reduce
|
|
||||||
(fn [m k]
|
|
||||||
{k m})
|
|
||||||
:a
|
|
||||||
(reverse (range 25)))]
|
|
||||||
(is (= :a (s/select-one (apply path (range 25)) m)))
|
|
||||||
))
|
|
||||||
;;TODO: there's a bug in clojurescript that won't allow
|
|
||||||
;; non function implementations of IFn to have more than 20 arguments
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(do
|
|
||||||
(defprotocolpath AccountPath [])
|
|
||||||
(defrecord Account [funds])
|
|
||||||
(defrecord User [account])
|
|
||||||
(defrecord Family [accounts])
|
|
||||||
(extend-protocolpath AccountPath User :account Family [:accounts s/ALL])
|
|
||||||
)
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(deftest protocolpath-basic-test
|
|
||||||
(let [data [(->User (->Account 30))
|
|
||||||
(->User (->Account 50))
|
|
||||||
(->Family [(->Account 51) (->Account 52)])]]
|
|
||||||
(is (= [30 50 51 52]
|
|
||||||
(s/select [s/ALL AccountPath :funds] data)))
|
|
||||||
(is (= [(->User (->Account 31))
|
|
||||||
(->User (->Account 51))
|
|
||||||
(->Family [(->Account 52) (->Account 53)])]
|
|
||||||
(s/transform [s/ALL AccountPath :funds]
|
|
||||||
inc
|
|
||||||
data)))
|
|
||||||
))
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(do
|
|
||||||
(defprotocolpath LabeledAccountPath [label])
|
|
||||||
(defrecord LabeledUser [account])
|
|
||||||
(defrecord LabeledFamily [accounts])
|
|
||||||
(extend-protocolpath LabeledAccountPath
|
|
||||||
LabeledUser [:account s/keypath]
|
|
||||||
LabeledFamily [:accounts s/keypath s/ALL])
|
|
||||||
)
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(deftest protocolpath-params-test
|
|
||||||
(let [data [(->LabeledUser {:a (->Account 30)})
|
|
||||||
(->LabeledUser {:a (->Account 50)})
|
|
||||||
(->LabeledFamily {:a [(->Account 51) (->Account 52)]})]]
|
|
||||||
(is (= [30 50 51 52]
|
|
||||||
(s/select [s/ALL (LabeledAccountPath :a) :funds] data)))
|
|
||||||
(is (= [(->LabeledUser {:a (->Account 31)})
|
|
||||||
(->LabeledUser {:a (->Account 51)})
|
|
||||||
(->LabeledFamily {:a [(->Account 52) (->Account 53)]})]
|
|
||||||
(s/transform [s/ALL (LabeledAccountPath :a) :funds]
|
|
||||||
inc
|
|
||||||
data)))
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(do
|
|
||||||
(defprotocolpath CustomWalker [])
|
|
||||||
(extend-protocolpath CustomWalker
|
|
||||||
Object nil
|
|
||||||
clojure.lang.PersistentHashMap [(s/keypath :a) CustomWalker]
|
|
||||||
clojure.lang.PersistentArrayMap [(s/keypath :a) CustomWalker]
|
|
||||||
clojure.lang.PersistentVector [s/ALL CustomWalker]
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
#+clj
|
|
||||||
(deftest mixed-rich-regular-protocolpath
|
|
||||||
(is (= [1 2 3 11 21 22 25]
|
|
||||||
(s/select [CustomWalker number?] [{:a [1 2 :c [3]]} [[[[[[11]]] 21 [22 :c 25]]]]])))
|
|
||||||
(is (= [2 3 [[[4]] :b 0] {:a 4 :b 10}]
|
|
||||||
(s/transform [CustomWalker number?] inc [1 2 [[[3]] :b -1] {:a 3 :b 10}])))
|
|
||||||
)
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
(ns com.rpl.specter.test-helpers
|
(ns com.rpl.specter.test-helpers
|
||||||
(:require [clojure.test.check
|
(:require [clojure.test.check
|
||||||
[generators :as gen]
|
[generators :as gen]
|
||||||
[properties :as prop]]))
|
[properties :as prop]]
|
||||||
|
[clojure.test])
|
||||||
|
|
||||||
|
(:use [com.rpl.specter :only [select transform]]
|
||||||
|
[com.rpl.specter :only [select* transform*]]))
|
||||||
|
|
||||||
|
|
||||||
;; it seems like gen/bind and gen/return are a monad (hence the names)
|
;; it seems like gen/bind and gen/return are a monad (hence the names)
|
||||||
|
;; this is only for clj (cljs version in different file)
|
||||||
(defmacro for-all+ [bindings & body]
|
(defmacro for-all+ [bindings & body]
|
||||||
(let [parts (partition 2 bindings)
|
(let [parts (partition 2 bindings)
|
||||||
vars (vec (map first parts))
|
vars (vec (map first parts))
|
||||||
|
|
@ -14,4 +19,18 @@
|
||||||
`(gen/return ~vars)
|
`(gen/return ~vars)
|
||||||
(reverse parts))]
|
(reverse parts))]
|
||||||
`(prop/for-all [~vars ~genned]
|
`(prop/for-all [~vars ~genned]
|
||||||
~@body )))
|
~@body)))
|
||||||
|
|
||||||
|
|
||||||
|
(defmacro ic-test [params-decl apath transform-fn data params]
|
||||||
|
(let [platform (if (contains? &env :locals) :cljs :clj)
|
||||||
|
is-sym (if (= platform :clj) 'clojure.test/is 'cljs.test/is)]
|
||||||
|
`(let [icfnsel# (fn [~@params-decl] (select ~apath ~data))
|
||||||
|
icfntran# (fn [~@params-decl] (transform ~apath ~transform-fn ~data))
|
||||||
|
regfnsel# (fn [~@params-decl] (select* ~apath ~data))
|
||||||
|
regfntran# (fn [~@params-decl] (transform* ~apath ~transform-fn ~data))
|
||||||
|
params# (if (empty? ~params) [[]] ~params)]
|
||||||
|
(dotimes [_# 3]
|
||||||
|
(doseq [ps# params#]
|
||||||
|
(~is-sym (= (apply icfnsel# ps#) (apply regfnsel# ps#)))
|
||||||
|
(~is-sym (= (apply icfntran# ps#) (apply regfntran# ps#))))))))
|
||||||
|
|
|
||||||
122
test/com/rpl/specter/zipper_test.cljc
Normal file
122
test/com/rpl/specter/zipper_test.cljc
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
(ns com.rpl.specter.zipper-test
|
||||||
|
#?(:cljs (:require-macros
|
||||||
|
[cljs.test :refer [is deftest]]
|
||||||
|
[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!
|
||||||
|
select-first transform setval replace-in]]))
|
||||||
|
|
||||||
|
(:use
|
||||||
|
#?(:clj [clojure.test :only [deftest is]])
|
||||||
|
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
|
||||||
|
#?(:clj [com.rpl.specter.test-helpers :only [for-all+]])
|
||||||
|
#?(:clj [com.rpl.specter
|
||||||
|
:only [declarepath providepath select select-one select-one!
|
||||||
|
select-first transform setval replace-in]]))
|
||||||
|
|
||||||
|
(:require #?(:clj [clojure.test.check.generators :as gen])
|
||||||
|
#?(:clj [clojure.test.check.properties :as prop])
|
||||||
|
#?(: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]))
|
||||||
|
|
||||||
|
(defspec zipper-end-equivalency-test
|
||||||
|
(for-all+
|
||||||
|
[v (gen/not-empty (gen/vector gen/int))
|
||||||
|
i (gen/vector gen/int)]
|
||||||
|
(= (setval s/END i v)
|
||||||
|
(setval [z/VECTOR-ZIP z/DOWN z/RIGHTMOST z/INNER-RIGHT] i v))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest zipper-multi-insert-test
|
||||||
|
(is (= [1 2 :a :b 3 :a :b 4]
|
||||||
|
(setval [z/VECTOR-ZIP
|
||||||
|
z/DOWN
|
||||||
|
z/RIGHT
|
||||||
|
z/RIGHT
|
||||||
|
(s/multi-path z/INNER-RIGHT z/INNER-LEFT)]
|
||||||
|
|
||||||
|
[:a :b]
|
||||||
|
[1 2 3 4])
|
||||||
|
|
||||||
|
(setval [z/VECTOR-ZIP
|
||||||
|
z/DOWN
|
||||||
|
z/RIGHT
|
||||||
|
z/RIGHT
|
||||||
|
(s/multi-path z/INNER-LEFT z/INNER-RIGHT)]
|
||||||
|
|
||||||
|
[:a :b]
|
||||||
|
[1 2 3 4]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(deftest zipper-down-up-test
|
||||||
|
(is (= [1 [2 3 5] 6]
|
||||||
|
(transform [z/VECTOR-ZIP
|
||||||
|
z/DOWN
|
||||||
|
z/RIGHT
|
||||||
|
z/DOWN
|
||||||
|
z/RIGHT
|
||||||
|
z/RIGHT
|
||||||
|
(s/multi-path
|
||||||
|
s/STAY
|
||||||
|
[z/UP z/RIGHT])
|
||||||
|
z/NODE]
|
||||||
|
inc
|
||||||
|
[1 [2 3 4] 5]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(deftest next-terminate-test
|
||||||
|
(is (= [2 [3 4 [5]] 6]
|
||||||
|
(transform [z/VECTOR-ZIP z/NEXT-WALK z/NODE number?]
|
||||||
|
inc
|
||||||
|
[1 [2 3 [4]] 5])))
|
||||||
|
(is (= [1 [3 [[]] 5]]
|
||||||
|
(setval [z/VECTOR-ZIP
|
||||||
|
z/NEXT-WALK
|
||||||
|
(s/selected? z/NODE number? even?)
|
||||||
|
z/NODE-SEQ]
|
||||||
|
[]
|
||||||
|
[1 2 [3 [[4]] 5] 6]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(deftest zipper-nav-stop-test
|
||||||
|
(is (= [1]
|
||||||
|
(transform [z/VECTOR-ZIP z/UP z/NODE] inc [1])))
|
||||||
|
(is (= [1]
|
||||||
|
(transform [z/VECTOR-ZIP z/DOWN z/LEFT z/NODE] inc [1])))
|
||||||
|
(is (= [1]
|
||||||
|
(transform [z/VECTOR-ZIP z/DOWN z/RIGHT z/NODE] inc [1])))
|
||||||
|
(is (= []
|
||||||
|
(transform [z/VECTOR-ZIP z/DOWN z/NODE] inc []))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest find-first-test
|
||||||
|
(is (= [1 [3 [[4]] 5] 6]
|
||||||
|
(setval [z/VECTOR-ZIP
|
||||||
|
(z/find-first #(and (number? %) (even? %)))
|
||||||
|
z/NODE-SEQ]
|
||||||
|
|
||||||
|
[]
|
||||||
|
[1 2 [3 [[4]] 5] 6]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(deftest nodeseq-expand-test
|
||||||
|
(is (= [2 [2] [[4 4 4]] 4 4 4 6]
|
||||||
|
(transform [z/VECTOR-ZIP
|
||||||
|
z/NEXT-WALK
|
||||||
|
(s/selected? z/NODE number? odd?)
|
||||||
|
(s/collect-one z/NODE)
|
||||||
|
z/NODE-SEQ]
|
||||||
|
(fn [v _]
|
||||||
|
(repeat v (inc v)))
|
||||||
|
[1 [2] [[3]] 3 6]))))
|
||||||
Loading…
Reference in a new issue