Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34df817638 | ||
|
|
311a5790d6 | ||
|
|
b4c75f97b4 | ||
|
|
70692e6618 | ||
|
|
529d584262 | ||
|
|
c0fb275d5b | ||
|
|
24f46be144 | ||
|
|
0fe3da9dea | ||
|
|
03d0ad3f03 | ||
|
|
a87f43701f | ||
|
|
dce3a3a2a5 | ||
|
|
7b20084b28 | ||
|
|
542808102f | ||
|
|
fd18b6a41f | ||
|
|
4c9c95f80f | ||
|
|
89460154bb | ||
|
|
b026e8877a | ||
|
|
597381b2e0 | ||
|
|
df70729b14 | ||
|
|
61b116c1a5 | ||
|
|
d37a10bbd0 | ||
|
|
5d75c5c861 | ||
|
|
db4089cc71 | ||
|
|
f9f4f14edf | ||
|
|
58946ce879 | ||
|
|
de1d5ff793 | ||
|
|
a4be8fb260 | ||
|
|
031a27f5fa | ||
|
|
f3c1931cac | ||
|
|
2e48d81c20 | ||
|
|
e7ef3fc95d | ||
|
|
2d01aa1c6a | ||
|
|
ae07124550 | ||
|
|
1f744f4dac | ||
|
|
ad88ac642f | ||
|
|
f7956e9d80 | ||
|
|
da6892aa96 | ||
|
|
7a3bd20804 | ||
|
|
f7015166dc | ||
|
|
c6f346a355 | ||
|
|
911a3ba97f | ||
|
|
a1668a6b28 | ||
|
|
a4cb74a83f | ||
|
|
bbf9552186 | ||
|
|
e621b5828e | ||
|
|
68e282c50a | ||
|
|
403a50fbcd | ||
|
|
8e98cd4f41 | ||
|
|
499c385000 | ||
|
|
aecafc099d | ||
|
|
785c7d700a | ||
|
|
cecc8a0f95 | ||
|
|
52ff7e9686 | ||
|
|
e2eb2cfdb1 | ||
|
|
a5b4c1c04d | ||
|
|
e38f960d7e | ||
|
|
9e74ee686e | ||
|
|
e068625d7d | ||
|
|
00e8446a58 | ||
|
|
2aecfede5b | ||
|
|
9eebefb976 | ||
|
|
b709cba371 | ||
|
|
1d919f59e3 | ||
|
|
0e8d3f9208 | ||
|
|
8931f34582 | ||
|
|
843706e36e | ||
|
|
172bfdb128 | ||
|
|
2c741ec6ae | ||
|
|
12a457829f | ||
|
|
0ff6850237 | ||
|
|
63f6088476 | ||
|
|
206f6413f8 | ||
|
|
a2c0424b37 | ||
|
|
6ea7d3cef6 | ||
|
|
2cafd8c060 | ||
|
|
7271dfebfb | ||
|
|
2eff3590c1 | ||
|
|
b578f45060 | ||
|
|
a34ccfe129 | ||
|
|
8f4442f2df | ||
|
|
598e42e00b | ||
|
|
b93912b9dc | ||
|
|
71eca8a5d6 | ||
|
|
f0bb0e1f7f | ||
|
|
2cb0639208 | ||
|
|
081e3b36ec | ||
|
|
d521d7927f | ||
|
|
7313d26720 | ||
|
|
577a07d47f | ||
|
|
f6093d40d7 | ||
|
|
1dd034d4aa | ||
|
|
2417b196a4 | ||
|
|
50bb712e22 | ||
|
|
7b0747c907 | ||
|
|
b878e414de | ||
|
|
c447868e0f | ||
|
|
681d5aa9c4 | ||
|
|
0263dcc621 | ||
|
|
1ae24c5c5d | ||
|
|
6c02ebe4af | ||
|
|
a08e44c5a9 | ||
|
|
08f30098db | ||
|
|
5dbd91e4fa | ||
|
|
5b2eb54794 | ||
|
|
2c17007693 | ||
|
|
2770c32f3d | ||
|
|
8c8f6d8346 | ||
|
|
3950130e73 | ||
|
|
c8bcd89c7c | ||
|
|
7f7aaa115f | ||
|
|
53aeb26407 | ||
|
|
bba0d12510 | ||
|
|
035d8227a5 | ||
|
|
003a4da87e | ||
|
|
1977631b7f | ||
|
|
c9a0ec000a | ||
|
|
ba842ba113 | ||
|
|
6304919d0d | ||
|
|
d2d27e96e5 | ||
|
|
fc6f7c532c | ||
|
|
1bee85e518 | ||
|
|
66b211987d | ||
|
|
15fdae4de6 | ||
|
|
5b4497fa6b | ||
|
|
612e124ec9 | ||
|
|
abd2b35738 | ||
|
|
2afb875099 | ||
|
|
88507f65f8 | ||
|
|
275d1f5494 | ||
|
|
e6420c8d13 | ||
|
|
60bcf1cadb | ||
|
|
d86ca5826c | ||
|
|
2af00f4c33 | ||
|
|
0fe86e77c1 | ||
|
|
1ca451f779 | ||
|
|
ebe0c8cf8e | ||
|
|
de9feb26d5 | ||
|
|
b8528f6c8e | ||
|
|
c9fbbac391 | ||
|
|
98e1b4ccf8 | ||
|
|
ae19491620 | ||
|
|
64f3760b19 | ||
|
|
0b38bc050f | ||
|
|
9431338654 | ||
|
|
88be750a32 | ||
|
|
032c8b8291 | ||
|
|
2d90d8ac60 | ||
|
|
a8b2cb99ee | ||
|
|
68274311fb | ||
|
|
88f84f80b5 | ||
|
|
2bf898e8fe | ||
|
|
d45f451943 | ||
|
|
c0a075724f | ||
|
|
f1a39375c9 | ||
|
|
b9c1b22163 | ||
|
|
be75e3be17 | ||
|
|
9793caa1ef | ||
|
|
0792fe2c52 | ||
|
|
4408aa3450 | ||
|
|
e4ba8d5d21 | ||
|
|
689f14a867 | ||
|
|
aa5b414e8e | ||
|
|
07467a733b | ||
|
|
74391b793c | ||
|
|
f0b8bc1756 | ||
|
|
3e54b95bf2 | ||
|
|
313d26cb3b | ||
|
|
44b85be014 | ||
|
|
e344b1f9d8 | ||
|
|
f5f4205969 | ||
|
|
538c717ded | ||
|
|
8dd81e661a | ||
|
|
26b61b98f9 | ||
|
|
ab9450d92f | ||
|
|
a4a23b9a93 | ||
|
|
2f83ea5a7f | ||
|
|
7940d223ec | ||
|
|
c6fa951520 | ||
|
|
e5c8194a70 | ||
|
|
4a7e3dd11f | ||
|
|
638fccb79d | ||
|
|
e9d84ec43b | ||
|
|
bb35b9afb1 | ||
|
|
1496c04592 | ||
|
|
3e9725a5a8 | ||
|
|
1565ec7658 | ||
|
|
f1aa2f01c7 | ||
|
|
7284542116 | ||
|
|
4f17e8fd1c | ||
|
|
f536507b9d | ||
|
|
592300a0f0 | ||
|
|
28b1bd16de | ||
|
|
19fdd84f80 | ||
|
|
631e2a051a | ||
|
|
df0f7b5f95 | ||
|
|
a0dfc4160e | ||
|
|
ee8331023e | ||
|
|
a2ee71f5ab | ||
|
|
60163194e2 | ||
|
|
ba37682300 | ||
|
|
15265b9352 | ||
|
|
0a929310de | ||
|
|
a8670c1cfa | ||
|
|
ebd18d45fa | ||
|
|
ccbfec611a | ||
|
|
98a0999c2e | ||
|
|
e504142d3a | ||
|
|
c04e13169f | ||
|
|
96a4bff67b | ||
|
|
46de5ea462 | ||
|
|
22257b8342 | ||
|
|
a0be9fe272 | ||
|
|
1fd5a2a38e | ||
|
|
13aec18bd5 | ||
|
|
a85938d092 | ||
|
|
0d4af57abe | ||
|
|
e00db53112 | ||
|
|
b941618a10 | ||
|
|
d895c762a1 | ||
|
|
427c4b01c9 | ||
|
|
9d5a728cb8 | ||
|
|
1ed99d4ac7 | ||
|
|
ceb17fac7d | ||
|
|
c638f45542 | ||
|
|
c09905d537 | ||
|
|
117a428030 | ||
|
|
2ada7c40d7 | ||
|
|
cd162bf3d6 | ||
|
|
e6e71675ac | ||
|
|
c54e0caf6f | ||
|
|
b64552780e | ||
|
|
add8c66e82 | ||
|
|
aecbd2c16a | ||
|
|
32a649c1cd | ||
|
|
a1e657cb75 | ||
|
|
6bb4c4de03 | ||
|
|
ea5b56d434 | ||
|
|
a4082748cf | ||
|
|
4d763846c3 | ||
|
|
d56cc88f5f | ||
|
|
5e27b7b72b | ||
|
|
260d6d16ab | ||
|
|
1a82f126a4 | ||
|
|
3cf3eee2ea | ||
|
|
9e8a41b2d3 | ||
|
|
a54d9212b8 | ||
|
|
ee33608f75 | ||
|
|
6e91275782 | ||
|
|
c6f27541c1 | ||
|
|
a2662a4b51 | ||
|
|
1d29262949 | ||
|
|
1d655fa8f2 | ||
|
|
4bec785f4c | ||
|
|
e3273f731f | ||
|
|
a8564a7045 | ||
|
|
e2bac56556 | ||
|
|
a38e6b7a0d | ||
|
|
72199ca71d | ||
|
|
9420945f59 | ||
|
|
4ca1736f86 | ||
|
|
fd85ca443e | ||
|
|
187e86749e | ||
|
|
e04a7aef6a | ||
|
|
0b1c8c92c0 | ||
|
|
bf270d144f | ||
|
|
2c009d1ec3 | ||
|
|
08555f545c | ||
|
|
65d3af694b | ||
|
|
8aa43eaedd | ||
|
|
334c732691 | ||
|
|
24587110ad | ||
|
|
b98421fe51 | ||
|
|
752e3f8bc3 | ||
|
|
6c8e70b1f8 | ||
|
|
c6fbfd2a67 | ||
|
|
56ea6e52e4 | ||
|
|
4726e0af91 | ||
|
|
ee82679908 | ||
|
|
aedd5a938f | ||
|
|
2d9c3fb6f0 | ||
|
|
6f07f4af22 | ||
|
|
656bd640e6 | ||
|
|
bc2cb15a96 | ||
|
|
dbf205b311 | ||
|
|
0e0bb1c2a7 | ||
|
|
736406a9b3 | ||
|
|
886917753e | ||
|
|
f29889da99 | ||
|
|
aee33eac6d | ||
|
|
d8428ba1e6 | ||
|
|
1f725b0d2c | ||
|
|
38f895cc13 | ||
|
|
3fc07eff8e | ||
|
|
aa244c30fc | ||
|
|
f8647dae3e | ||
|
|
39fa2878ce | ||
|
|
5b5b95eb11 | ||
|
|
67caa3ebb3 | ||
|
|
d09cd173bf | ||
|
|
79fb40ab6a | ||
|
|
ae2cb6bc43 | ||
|
|
413b815ecc | ||
|
|
df89b5022e | ||
|
|
90729655ee | ||
|
|
171e9b3659 | ||
|
|
3e256fcc1e | ||
|
|
29ebbeda93 | ||
|
|
214d056911 | ||
|
|
9f065a77ca | ||
|
|
e70ac9768e | ||
|
|
ec6c98ef47 | ||
|
|
7d0f92e12a | ||
|
|
8bf6193ccc | ||
|
|
50f5dc76fb | ||
|
|
b9000999c9 | ||
|
|
52be06c314 | ||
|
|
3da560f10c | ||
|
|
f0f25300a0 | ||
|
|
c8a3a619af | ||
|
|
25f64051ba | ||
|
|
5efa6bb1c4 | ||
|
|
cb48f90f28 | ||
|
|
3ba93b1186 | ||
|
|
7babcd0ed2 | ||
|
|
b6c1f2b2e1 | ||
|
|
5f853f8e71 | ||
|
|
6cccf3ac22 | ||
|
|
5b9b186d4b | ||
|
|
57898bee03 | ||
|
|
6a6ba9f50d | ||
|
|
1e3135514f | ||
|
|
2f2a52a648 | ||
|
|
89fd8f305f | ||
|
|
353c043d8c | ||
|
|
54a40efeec | ||
|
|
f781795032 | ||
|
|
54ffb5768d | ||
|
|
8fc068821f | ||
|
|
1f91fd351e | ||
|
|
ab4399323f | ||
|
|
3b34a57bd2 | ||
|
|
62a469dc23 | ||
|
|
8b0c4127ef | ||
|
|
dc4b7c89ea | ||
|
|
b5febd991c | ||
|
|
f1323ade7c | ||
|
|
89561c321d | ||
|
|
d1b8657933 | ||
|
|
89d9cc89ca | ||
|
|
7b0cbb1632 | ||
|
|
3dc0e87c33 | ||
|
|
a19613cf19 | ||
|
|
a8e4fcdb6c | ||
|
|
a70ca83ba2 | ||
|
|
11c857a9a2 | ||
|
|
84b1f6546b | ||
|
|
c8c1757fdd | ||
|
|
9a162b5076 | ||
|
|
92d2f25048 | ||
|
|
a2ddb6abbb | ||
|
|
ac629c875b | ||
|
|
ab8ba93d1d | ||
|
|
9d62e674d7 | ||
|
|
58ca3401e4 | ||
|
|
a5926616a6 | ||
|
|
59910ae4bf | ||
|
|
69b47483c1 | ||
|
|
284e75d031 | ||
|
|
c1d63e4639 | ||
|
|
212b204af8 | ||
|
|
90f83e1b3d | ||
|
|
562af52d07 | ||
|
|
ed79d60cef | ||
|
|
42d378142b | ||
|
|
aeefd59b15 | ||
|
|
6db5091378 | ||
|
|
4e7ba9d598 | ||
|
|
60b66ac669 | ||
|
|
1fda82d9c2 | ||
|
|
55dbd617a6 | ||
|
|
841a4e0195 | ||
|
|
5d60728d4b | ||
|
|
9717a63604 | ||
|
|
fae20670ed | ||
|
|
6874eb7ce8 | ||
|
|
404e95b7ac | ||
|
|
30a5914a92 | ||
|
|
f97a36ff98 | ||
|
|
e68f617e2a | ||
|
|
3056e95ded | ||
|
|
9ba77e1ce6 | ||
|
|
84010d4467 | ||
|
|
a91aa70103 | ||
|
|
301c7fd122 | ||
|
|
8809ab2f5e | ||
|
|
71e441dcc2 | ||
|
|
f11197fd04 | ||
|
|
7ac771da67 | ||
|
|
a47751f4de | ||
|
|
c91217860c | ||
|
|
f69e9df983 | ||
|
|
2ee1d5daef | ||
|
|
d3dba63659 | ||
|
|
c016d74379 | ||
|
|
a6d887ee09 | ||
|
|
aa017a4dcb | ||
|
|
b7c7d7e72c | ||
|
|
d164a9b93f | ||
|
|
0105cf538e | ||
|
|
957e389fe7 | ||
|
|
88d4d38afa | ||
|
|
8b4b985894 | ||
|
|
986eb8bdb1 | ||
|
|
052eac333c | ||
|
|
acae645db1 | ||
|
|
7cb5f6ff3c | ||
|
|
0fe940a621 | ||
|
|
81ed03ffb2 | ||
|
|
3947f7455a | ||
|
|
406a2e6755 | ||
|
|
0b3fcb254a | ||
|
|
054843505a | ||
|
|
92016cd80e | ||
|
|
0c06e72f0c | ||
|
|
a722a4b33c | ||
|
|
4afb8a516c | ||
|
|
e9fc50bc6d | ||
|
|
0a5a980ab7 | ||
|
|
30505b7758 | ||
|
|
9f867f2ec8 | ||
|
|
5e2020804a | ||
|
|
b5cea26e57 | ||
|
|
03e6d3a5a2 | ||
|
|
89513f9d15 | ||
|
|
f20ef46e90 | ||
|
|
d7ebc46a2d | ||
|
|
bd6d4bcd6f | ||
|
|
4f8862cedd | ||
|
|
1afc5ec71b | ||
|
|
925ff6050a | ||
|
|
6ce97090f3 | ||
|
|
6538f33825 | ||
|
|
a1e4a2741f | ||
|
|
fe57a821d8 | ||
|
|
c42fe88dcc | ||
|
|
28d7fc35a6 | ||
|
|
9fa279897b | ||
|
|
6465a3a4af | ||
|
|
1582df8d70 | ||
|
|
8d9aec5a55 | ||
|
|
7f4df95922 | ||
|
|
98a5affa4a | ||
|
|
b720559d56 | ||
|
|
cd5cc91cd4 | ||
|
|
93d92d1f36 | ||
|
|
ce3674dd07 | ||
|
|
e4cfb62a74 | ||
|
|
bb51701614 | ||
|
|
6ffb248f03 | ||
|
|
50af8058ef | ||
|
|
b1d9e147b3 | ||
|
|
7ea91d2d68 | ||
|
|
c7640d3cf4 | ||
|
|
399240295b | ||
|
|
b965d8eb5d | ||
|
|
380bf1e279 | ||
|
|
6272b60d36 | ||
|
|
0c7699f21c | ||
|
|
1086a8455f | ||
|
|
d8e7578e41 | ||
|
|
2d0fbcc23c | ||
|
|
21d5d43ac5 | ||
|
|
027f90380b | ||
|
|
1a68f63525 | ||
|
|
798c53a4cb | ||
|
|
e3a89050f2 | ||
|
|
f176cb55c1 | ||
|
|
97f2e8744b | ||
|
|
c513459c10 | ||
|
|
fd6f1d47be | ||
|
|
6b8b5bfd01 | ||
|
|
e6e96165aa | ||
|
|
42875c0b72 | ||
|
|
b35e2cf82a | ||
|
|
be320880b2 | ||
|
|
fe1712d642 | ||
|
|
63d815912e | ||
|
|
cd4dc75c93 | ||
|
|
058b157b36 | ||
|
|
19c9941e7f | ||
|
|
7895ca0821 | ||
|
|
2b20bb424f | ||
|
|
82535feb90 | ||
|
|
81f007bdd7 | ||
|
|
0b35996d5e | ||
|
|
fc5cf506ae | ||
|
|
c4582e0041 | ||
|
|
c80d215674 | ||
|
|
3ee9d5d0df | ||
|
|
d86ba0e1c7 | ||
|
|
c295ee50de | ||
|
|
54c25a55fe | ||
|
|
583a4ec5f6 | ||
|
|
69b78dad51 | ||
|
|
0685e0315a | ||
|
|
5e429001d9 | ||
|
|
707b810f1b | ||
|
|
e51cfc142d | ||
|
|
b259e793bb | ||
|
|
32738a97d9 | ||
|
|
45c6fbaced | ||
|
|
bba4f1bd1b | ||
|
|
ade2596e92 | ||
|
|
0e804a2b5c | ||
|
|
33ab1679f3 | ||
|
|
f2b12633f3 | ||
|
|
5a4b2769ee | ||
|
|
2439bae409 | ||
|
|
6ed7e40d03 | ||
|
|
2fc76fe1b0 | ||
|
|
e541a56130 | ||
|
|
6378750b12 | ||
|
|
fc25f60718 | ||
|
|
3b351e7650 | ||
|
|
999a535603 | ||
|
|
a726dd812b | ||
|
|
58d54afac6 | ||
|
|
001d776176 | ||
|
|
4bcd25fb4d | ||
|
|
f0fa9e66a6 | ||
|
|
12f9bf09b0 | ||
|
|
bf8f5a995f | ||
|
|
d3a3feace4 | ||
|
|
30331b2bea | ||
|
|
f5bf9d9659 | ||
|
|
2865bd8653 | ||
|
|
4513b6965f | ||
|
|
b47e8c61d7 | ||
|
|
40bf079566 | ||
|
|
2eaf854e79 | ||
|
|
7c12cf029c | ||
|
|
597d90b326 | ||
|
|
5dcad26b79 | ||
|
|
f8c2d5cb4f | ||
|
|
b0f432b2f8 | ||
|
|
651a4bad67 | ||
|
|
f52e350adc | ||
|
|
35e8019447 | ||
|
|
a6fab4a111 | ||
|
|
64d62d2472 | ||
|
|
10b322aa48 | ||
|
|
981955301d | ||
|
|
ac425f04ef | ||
|
|
1dda1563da | ||
|
|
0e71b1e711 | ||
|
|
841f6f2387 | ||
|
|
2eb7fb97de | ||
|
|
e781983e6b | ||
|
|
7d17a12783 | ||
|
|
d888057689 | ||
|
|
76d6e05399 | ||
|
|
1bd0ca6dea | ||
|
|
8e1f7fddca | ||
|
|
abda47903b | ||
|
|
030e72c9c6 | ||
|
|
88dbad9650 | ||
|
|
07263b8533 | ||
|
|
f67741b6dc | ||
|
|
4724ff12e2 | ||
|
|
f7c7ff46b4 | ||
|
|
c4868f8549 | ||
|
|
85951052ca | ||
|
|
28bd15b76c | ||
|
|
1fdb715967 | ||
|
|
0279547232 | ||
|
|
7fabf32d9e | ||
|
|
4cbf0eb2ac | ||
|
|
071b0830f8 | ||
|
|
39cd4a6594 | ||
|
|
0d2332fe5e | ||
|
|
c807fa6ec5 | ||
|
|
4a10806d9a | ||
|
|
57031f8cb0 | ||
|
|
b292486ac9 | ||
|
|
595f9bb0b1 | ||
|
|
ebe3397ad9 | ||
|
|
bc12b76bcc | ||
|
|
d8df85e99c | ||
|
|
80b9879d0a | ||
|
|
d54f9ec931 | ||
|
|
d2258acbb3 | ||
|
|
6c3a4bd019 | ||
|
|
7903211546 | ||
|
|
6b87fcf903 | ||
|
|
07d0d1b275 | ||
|
|
95361172a0 | ||
|
|
a0f3086167 | ||
|
|
65a59b41e5 | ||
|
|
78dc02fb81 | ||
|
|
dc577b8bfa | ||
|
|
ed9862ea91 | ||
|
|
f710c61179 | ||
|
|
d86cf14f0d | ||
|
|
bdfd5817dc | ||
|
|
3b2ece4ddc | ||
|
|
3452f364aa | ||
|
|
dad77a4d4b | ||
|
|
c8b925c581 | ||
|
|
24297c87e6 | ||
|
|
0c589eff04 | ||
|
|
3a3684ca78 | ||
|
|
6e39366c40 | ||
|
|
fd0ccb7741 | ||
|
|
81f151245e | ||
|
|
acfdd5b59e | ||
|
|
588800c495 | ||
|
|
cfd157ab7d | ||
|
|
377eb6126d | ||
|
|
2cad4c96bd | ||
|
|
2453501c83 | ||
|
|
dcc3c6290e | ||
|
|
54240e1b4f | ||
|
|
6639f91ba2 | ||
|
|
15bf309760 | ||
|
|
1de0302ecb | ||
|
|
871cbda3f8 | ||
|
|
c4ea85d7e5 | ||
|
|
a8f3125774 | ||
|
|
c564af180b | ||
|
|
3ebe1ed728 | ||
|
|
6b030b2978 | ||
|
|
9f2fa0f7af | ||
|
|
da06e35443 | ||
|
|
da4d047a37 | ||
|
|
782314abff | ||
|
|
94d53f052b | ||
|
|
9ceebf96db | ||
|
|
8611bb7322 | ||
|
|
ba2e1c95ef | ||
|
|
5979b5ca75 | ||
|
|
f144cae300 | ||
|
|
8bcf473b68 | ||
|
|
b10fdfc7b8 | ||
|
|
95eb1c0978 | ||
|
|
28264c6792 | ||
|
|
18f5063814 | ||
|
|
42d5c544cd | ||
|
|
713a706958 | ||
|
|
ef23a3fe39 | ||
|
|
44b0412042 | ||
|
|
d984a82587 | ||
|
|
e59d50a891 | ||
|
|
7a5cb8d374 | ||
|
|
f23b133d29 | ||
|
|
0bdee49f7f | ||
|
|
7bf568d0b3 | ||
|
|
f95cafe5ca | ||
|
|
f0e1443f21 | ||
|
|
cf619585a6 | ||
|
|
c639b01146 | ||
|
|
b4415f2a14 | ||
|
|
538b51c20a | ||
|
|
c8a0158791 | ||
|
|
b088d4b90e | ||
|
|
02a415facd | ||
|
|
5cee19067d | ||
|
|
3496681236 | ||
|
|
a7645a2c8b | ||
|
|
d9423627f3 | ||
|
|
5a8520fb0f | ||
|
|
4458ee1232 | ||
|
|
a6b1cca91f | ||
|
|
e2535731c8 | ||
|
|
393ff69b1e | ||
|
|
033747ad63 | ||
|
|
730d043631 | ||
|
|
c819bdbf74 | ||
|
|
64f8541ee6 | ||
|
|
d1f4e2d49b | ||
|
|
5a13aecbae | ||
|
|
61904ee24c | ||
|
|
5c7fdcc244 | ||
|
|
f772877039 | ||
|
|
e571218917 | ||
|
|
aa01dd1fc1 | ||
|
|
30971c9cb3 | ||
|
|
abfc69a6e2 | ||
|
|
ac1fc484e4 | ||
|
|
889bfebe4e | ||
|
|
e9899a5462 | ||
|
|
ec92bb118d | ||
|
|
fd9ca435b7 | ||
|
|
3ec0d20621 | ||
|
|
16f3b928a1 | ||
|
|
fb20b6ddf6 | ||
|
|
d77a4d6c3f | ||
|
|
d7900239c2 | ||
|
|
babd98be3f | ||
|
|
9a85b1556b | ||
|
|
d1da200af5 | ||
|
|
e260b92f89 | ||
|
|
e09e5c3f41 | ||
|
|
a76a281309 | ||
|
|
be42d00067 | ||
|
|
f21a998f81 | ||
|
|
89e0018d58 | ||
|
|
21b691cd4e | ||
|
|
a8a91c2282 | ||
|
|
5275646966 | ||
|
|
e60cd6a484 | ||
|
|
552006bea3 | ||
|
|
2077b584ad | ||
|
|
29f05fda35 | ||
|
|
cbb7c545e0 | ||
|
|
c909267077 | ||
|
|
a9bbabfabc | ||
|
|
c4d666a4ba | ||
|
|
8e824d3da1 | ||
|
|
41caa0d635 | ||
|
|
3a0108edf8 | ||
|
|
7123fb9d09 | ||
|
|
6d8e118192 | ||
|
|
846703b31c | ||
|
|
46905b39a7 | ||
|
|
0f43aca5bc | ||
|
|
587c83cbff | ||
|
|
e8194b1764 | ||
|
|
feb9cb645e | ||
|
|
29f9a4fd67 | ||
|
|
22f3a50eba | ||
|
|
470e5424bb | ||
|
|
8f272c4cc8 | ||
|
|
46c23b1547 | ||
|
|
4d607ace08 | ||
|
|
78adb5b200 | ||
|
|
311efb66a9 | ||
|
|
272be2c088 | ||
|
|
911acc3173 | ||
|
|
182cbfe541 | ||
|
|
c43bc6a06d | ||
|
|
38aa5c57e5 | ||
|
|
4f93dae442 | ||
|
|
41c74e9682 | ||
|
|
e0f5dd506a | ||
|
|
f90eeaa024 | ||
|
|
9451e69996 | ||
|
|
924350868a | ||
|
|
ee91be519a | ||
|
|
90951be944 | ||
|
|
65cf31d32d | ||
|
|
e14c9ea6ce | ||
|
|
8635fcc869 | ||
|
|
65809a5644 | ||
|
|
d53a315069 | ||
|
|
8d2199c3f3 | ||
|
|
54e69c023a | ||
|
|
55503239a4 | ||
|
|
19d8446ac0 | ||
|
|
5a94906f1d | ||
|
|
915ba1bb29 | ||
|
|
d35d03a8ee | ||
|
|
b959049c77 | ||
|
|
24c4bc3009 | ||
|
|
000a174353 | ||
|
|
b126e3b089 | ||
|
|
319957f5df | ||
|
|
cb2eb7d376 | ||
|
|
1857267872 | ||
|
|
91faec5f65 | ||
|
|
0f0a91faef | ||
|
|
baec57637e | ||
|
|
0c8c6f03e0 | ||
|
|
7bdbefb277 | ||
|
|
ec0e7d8056 | ||
|
|
c2e912bdfd | ||
|
|
14e1d410f1 | ||
|
|
15925573e8 | ||
|
|
f32ede4e3f | ||
|
|
1dc9193e69 | ||
|
|
dacb30ad05 | ||
|
|
362df17316 | ||
|
|
d54d0f6a6b | ||
|
|
72d2f38cb5 | ||
|
|
b3cc527126 | ||
|
|
6b45855f0e | ||
|
|
40835556da | ||
|
|
dc9a47d259 | ||
|
|
2d1cf2ebc1 | ||
|
|
9a56e5ef5a | ||
|
|
ab148551ba | ||
|
|
4d7c75f2ca | ||
|
|
e7f3db9fd4 | ||
|
|
4682511923 | ||
|
|
e942c07f34 | ||
|
|
dc403e4311 | ||
|
|
b3c6bc2bc5 | ||
|
|
c377483715 | ||
|
|
3ded858534 | ||
|
|
aa09b1e2d2 | ||
|
|
8cb4c719f9 | ||
|
|
ffa2abd8c6 | ||
|
|
14722ba102 | ||
|
|
7f4437ece5 | ||
|
|
afc7644fe0 | ||
|
|
7de880495b | ||
|
|
29addf9e93 | ||
|
|
6e9f9f254f | ||
|
|
db25e24eb8 | ||
|
|
8a184a6b3d | ||
|
|
fcd6213c4c | ||
|
|
5409b7e707 | ||
|
|
815772811f | ||
|
|
9cff61493e | ||
|
|
65a3434bd4 | ||
|
|
4ff9f9aa29 | ||
|
|
3d982fd1a9 | ||
|
|
396b1e68a8 | ||
|
|
9c549b3465 | ||
|
|
04194a189d | ||
|
|
35e04d0ea5 | ||
|
|
1038018e84 | ||
|
|
2b3e53670e | ||
|
|
dff93a44c2 | ||
|
|
f76c8fec7b | ||
|
|
8b19b0b3f4 | ||
|
|
77fad29a1b | ||
|
|
5bab9cae49 | ||
|
|
05507d1f3a | ||
|
|
e369900b06 | ||
|
|
a629cfce86 | ||
|
|
1a93d35d9c | ||
|
|
06cc1c183f | ||
|
|
cb5806bbf3 | ||
|
|
db70fd5a5f | ||
|
|
513ed0bbae | ||
|
|
3bbae0a368 | ||
|
|
2134db3a0d | ||
|
|
80f35eab7d | ||
|
|
ab8a2d195f | ||
|
|
a7096b5061 | ||
|
|
8552886a8a | ||
|
|
789a657490 | ||
|
|
2ee44eba29 | ||
|
|
e4a9fae0fd | ||
|
|
0030b282c1 | ||
|
|
9c90955863 | ||
|
|
61e626bdb8 | ||
|
|
3c40de4c0c | ||
|
|
86a8a6ee88 | ||
|
|
4d5696e074 | ||
|
|
892f211550 | ||
|
|
2caa16ac22 | ||
|
|
32c5694551 | ||
|
|
1aeb7ae7d0 | ||
|
|
fe8f3464b6 | ||
|
|
8941d2821d | ||
|
|
7ba3c3cfb7 | ||
|
|
5cc2724b40 | ||
|
|
40dff7ebe8 | ||
|
|
5158a19fbd | ||
|
|
fd936bb326 | ||
|
|
3e54d33481 | ||
|
|
0fe1e686b3 | ||
|
|
9e80f3b82b | ||
|
|
01bda975c3 | ||
|
|
05a8ce56a3 | ||
|
|
d498a89267 | ||
|
|
46db0d4304 | ||
|
|
f2ec77886a | ||
|
|
4e8ae3abdb | ||
|
|
dc7ffae763 | ||
|
|
b7897b224d | ||
|
|
c80a65b657 | ||
|
|
f02cd8437b | ||
|
|
7d87e84803 | ||
|
|
b4a0b4cc19 | ||
|
|
1940f490b2 | ||
|
|
69098968be | ||
|
|
85a04fe919 | ||
|
|
54db13ae56 | ||
|
|
f60d3668ed | ||
|
|
3471898364 | ||
|
|
bf7d172f11 | ||
|
|
28457563ce | ||
|
|
280a5ceedc | ||
|
|
a696cf41dc | ||
|
|
f3f0ec09b7 | ||
|
|
e040688bd9 | ||
|
|
9d84e1c007 | ||
|
|
2e6a83dff9 | ||
|
|
d866a26ac1 | ||
|
|
3453c30f4c | ||
|
|
50a7b0e1bc | ||
|
|
7ce790035d | ||
|
|
606cd2ff69 | ||
|
|
9c9d2e1ea0 | ||
|
|
6e7baa42ac | ||
|
|
633f92edbe | ||
|
|
b23f2fc360 | ||
|
|
5b31ec421d | ||
|
|
2d38f20b43 | ||
|
|
9f6f92b0f2 | ||
|
|
4a183b462c | ||
|
|
4d53d8c710 | ||
|
|
a09a0c2a1a | ||
|
|
6a42ab0797 | ||
|
|
183ed3ffdd | ||
|
|
8a8779fcbc | ||
|
|
bcb8cd763e | ||
|
|
97c0809845 | ||
|
|
e33a9557aa | ||
|
|
43706a4623 | ||
|
|
b816c98877 | ||
|
|
23f0ff81c4 | ||
|
|
6aada7fe3c | ||
|
|
e24cef0126 | ||
|
|
b161070345 | ||
|
|
0b4f9ad486 | ||
|
|
d484ca8ae2 | ||
|
|
d77c1a34f4 | ||
|
|
f6b0f29f5d | ||
|
|
436f563ad3 | ||
|
|
81b80d5b9c | ||
|
|
425cea5a84 | ||
|
|
b4c0826467 | ||
|
|
d94491841a | ||
|
|
bb1093ef2d | ||
|
|
863e22a7e2 | ||
|
|
aa682a4704 | ||
|
|
01d5f99437 | ||
|
|
34c2da2c65 | ||
|
|
a3d5be91f2 | ||
|
|
bbb77e0102 | ||
|
|
19164d2e55 | ||
|
|
a950d4fda1 | ||
|
|
33ac879521 | ||
|
|
6ef4c80724 | ||
|
|
3843ee6ce5 | ||
|
|
3ab1db6740 | ||
|
|
1f2dc0f716 | ||
|
|
83fa2fbbbf | ||
|
|
cc969f8d01 | ||
|
|
3de70b909a | ||
|
|
7782227182 | ||
|
|
58cd9e5c11 | ||
|
|
862ab01f94 | ||
|
|
2c630ff33c | ||
|
|
007571e979 | ||
|
|
53552d434d | ||
|
|
eb32937858 | ||
|
|
6720cb6830 | ||
|
|
16c0262122 | ||
|
|
2606f47138 | ||
|
|
9337d50775 | ||
|
|
069415595a | ||
|
|
4e88d82871 | ||
|
|
5e8d948e39 | ||
|
|
893700f65e | ||
|
|
c3f0929a77 | ||
|
|
8448513c8b | ||
|
|
bc65fc0276 | ||
|
|
b1f0d3ed99 | ||
|
|
211d9428b4 | ||
|
|
8a3f2cfc2f | ||
|
|
fff7a85769 | ||
|
|
377c64d1b0 |
+1
-3
@@ -4,12 +4,10 @@
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
/lib/elixir/src/elixir.app.src
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
/lib/elixir/test/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.elixir.plt
|
||||
|
||||
@@ -7,3 +7,5 @@ notifications:
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0
|
||||
- 17.1
|
||||
- 17.3
|
||||
|
||||
+373
-31
@@ -1,4 +1,346 @@
|
||||
# v0.13.3 (2014-05-24)
|
||||
# Changelog
|
||||
|
||||
## v1.0.2 (2014-10-21)
|
||||
|
||||
* Bug fixes
|
||||
* [EEx] Remove invalid warning when attempting to inline code in EEx
|
||||
* [EEx] Unify syntax errors to always include `file:line:`
|
||||
* [IEx] Set null prompt function in dumb term mode
|
||||
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
|
||||
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
|
||||
* [Kernel] Improve error messages when parsing fails on aliases
|
||||
* [Kernel] Improve warnings coming from compile-time evaulation of arguments
|
||||
* [String] Fix `String.replace/4` typespec
|
||||
|
||||
## v1.0.1 (2014-10-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix Elixir CLI when running on Cygwin
|
||||
* [ExUnit] Include stacktraces in ExUnit timeouts
|
||||
* [Mix] Load dependencies before `deps.check` compiles dependencies
|
||||
* [Mix] Ensure `Mix.Config` is deep merged
|
||||
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
|
||||
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
|
||||
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
|
||||
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
|
||||
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
|
||||
* [Mix] Do not crash on malformed proxy env var
|
||||
* [Stream] Ensure `chunk/4` works correctly when halted
|
||||
* [System] Do not allow relative paths in `System.cmd/3` as documented
|
||||
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
|
||||
|
||||
## v1.0.0 (2014-09-10)
|
||||
|
||||
* Enhancements
|
||||
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
|
||||
* [List] `List.unzip/2` was removed
|
||||
|
||||
## v1.0.0-rc2 (2014-09-07)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Print process mailbox on failing `assert_receive`
|
||||
* [ExUnit] Tag all doctests with the `:doctest` tag
|
||||
* [Kernel] Friendlier `unquote_splicing` error on inline quote
|
||||
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
|
||||
* [Mix] `mix deps.unlock` warns when given a missing dependency
|
||||
* [Mix] Fetch hex using HTTPS
|
||||
|
||||
* Bug fixes
|
||||
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
|
||||
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
|
||||
* [GenEvent] Do not consider swap handlers to be failures in a stream
|
||||
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
|
||||
* [Logger] Recompute sync/async mode on Logger configuration
|
||||
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
|
||||
|
||||
* Deprecations
|
||||
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
|
||||
|
||||
## v1.0.0-rc1 (2014-08-30)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add timeouts to tests (which is configurable via tags)
|
||||
* [GenEvent] Add support for `GenEvent.ack_notify/2`
|
||||
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
|
||||
* [Logger] Allow any value that implements `String.Chars` to be logger
|
||||
* [Logger] Allow `Logger.log/3` timeout to be configured
|
||||
* [Mix] Add `--force` option to `mix local.rebar`
|
||||
* [Mix] Fix `--no-color`/`--color` option for `mix test`
|
||||
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
|
||||
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
|
||||
* [Mix] Support protocol consolidation on escripts
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
|
||||
* [GenEvent] Do not allow a handler to be added more than once
|
||||
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
|
||||
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
|
||||
* [Mix] Do not pre-compile a Mix project if an alias was found
|
||||
* [Mix] Properly handle compilation errors in the Erlang compiler
|
||||
* [Mix] Always try to compile project if task cannot be found
|
||||
* [Mix] Purge in memory modules before consolidating
|
||||
* [Mix] Always compile a project if a task cannot be found
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
|
||||
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
|
||||
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
|
||||
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
|
||||
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
|
||||
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
|
||||
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
|
||||
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
|
||||
|
||||
## v0.15.1 (2014-08-10)
|
||||
|
||||
* Enhancements
|
||||
* [GenEvent] Support `:ack` mode for GenEvent streams
|
||||
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
|
||||
* [kernel] Print warnings when used ? with characters with escape codes
|
||||
* [Logger] Add SASL log forwarding option to Logger
|
||||
* [Logger] Add `$padlevel` to option Logger formatter
|
||||
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
|
||||
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
|
||||
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
|
||||
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
|
||||
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
|
||||
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
|
||||
* [Mix] Load `config/config.exs` inside escripts
|
||||
* [Mix] Store and check Elixir version requirement for generated archives
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
|
||||
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
|
||||
* [Kernel] Do not expand args for unknown functions/macros
|
||||
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
|
||||
* [Kernel] Do not raise conflicts on imports used from inside quotes
|
||||
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
|
||||
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
|
||||
* [Macro] Ensure bitstrings work with `Macro.escape/1`
|
||||
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
|
||||
* [Mix] Leave it up to the application to start the Logger after compilation
|
||||
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
|
||||
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
|
||||
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Having a function as collectable is deprecated
|
||||
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
|
||||
|
||||
## v0.15.0 (2014-08-02)
|
||||
|
||||
* Enhancements
|
||||
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
|
||||
* [IEx] Support `--werl` call on Windows
|
||||
* [Logger] Add `Logger`
|
||||
* [Map] Add `Map.from_struct/1`
|
||||
* [Mix] Allow `--app` flag to be passed to `mix new`
|
||||
* [Mix] Support lowercase `http(s)_proxy` environment variables
|
||||
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
|
||||
* [String] `String.slice/2` and `String.slice/3` have been optimized
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
|
||||
* [IEx] Ensure remote shells can be started with `--remsh`
|
||||
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
|
||||
* [Kernel] Ensure private functions are not exported
|
||||
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
|
||||
|
||||
* Deprecations
|
||||
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
|
||||
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
|
||||
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
|
||||
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
|
||||
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
|
||||
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
|
||||
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
|
||||
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
|
||||
## v0.14.3 (2014-07-12)
|
||||
|
||||
* Enhancements
|
||||
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
|
||||
* [Enum] Add `Enum.sort_by/3`
|
||||
* [ExUnit] Match the line filter by proximity instead of exact match
|
||||
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
|
||||
* [IO] Add `IO.(bin)read(device, :all)`
|
||||
* [Kernel] Print a warning if a dangling `@doc` clause is found
|
||||
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
|
||||
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
|
||||
* [Mix] Issue warning and reset mtime for source files from the future
|
||||
* [Mix] Support task aliases in Mix
|
||||
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
|
||||
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
|
||||
* [String] Improve performance of `String.split/1`
|
||||
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
|
||||
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
|
||||
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
|
||||
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
|
||||
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
|
||||
* [Kernel] Run command line and escripts in a process that does not trap exits
|
||||
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
|
||||
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
|
||||
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
|
||||
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
|
||||
* [Macro] `to_string` correctly displays sigils
|
||||
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
|
||||
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
|
||||
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
|
||||
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
|
||||
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
|
||||
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
|
||||
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
|
||||
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
|
||||
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
|
||||
* [System] Giving a char list to `System.find_executable/1` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Access] No longer fill in missing intermediate values with empty maps
|
||||
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
|
||||
* [Kernel] `defstruct` no longer automatically defines a type
|
||||
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
|
||||
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
|
||||
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
|
||||
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
|
||||
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
|
||||
|
||||
## v0.14.2 (2014-06-29)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
|
||||
* [Kernel] Ensure compatibility with Erlang 17.1
|
||||
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
|
||||
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
|
||||
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
|
||||
* [Task] Set `:proc_lib` initial call on task to aid debugging
|
||||
* [Typespec] Delay typespec compilation to after expansion
|
||||
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||
* [IEx] Remove delay when printing data from the application start callback
|
||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||
* [Kernel] Ensure derived protocols are defined with a file
|
||||
* [Kernel] Change precedence of `&` to not special case `/`
|
||||
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
|
||||
* [Kernel] `Kernel.xor/2` is deprecated
|
||||
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
|
||||
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
|
||||
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
|
||||
* [System] `build_info/0` now returns a map
|
||||
|
||||
## v0.14.1 (2014-06-18)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Decoding and encoding functions now accept the `:case` as an option
|
||||
* [ExUnit] The test process now exits with `:shutdown` reason
|
||||
* [GenEvent] `GenEvent.stream/2` now accepts `:sync` and `:async` modes
|
||||
* [Node] Add `Node.start/3` and `Node.stop/0`
|
||||
* [String] Updated Unicode database to 7.0
|
||||
* [Task] Log when tasks crash
|
||||
|
||||
* Bug fixes
|
||||
* [Enum] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
|
||||
* [Kernel] Disambiguate (w)erl to (w)erl.exe
|
||||
* [Mix] Ensure umbrella project is recompiled when a dependency inside an umbrella child changes
|
||||
* [OptionParser] Do not allow underscores in option names
|
||||
* [Path] Fix path expansion of `"/.."`
|
||||
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
|
||||
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
|
||||
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
|
||||
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:escript_*` options moved into a single `:escript` group
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
||||
* [Kernel] Remove `get_in/1`
|
||||
|
||||
## v0.14.0 (2014-06-08)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add `on_exit/1` callbacks that are guaranteed to run once the test process exits and always in another process
|
||||
* [Kernel] Store documentation in the abstract code to avoid loading them when the module is loaded
|
||||
* [Kernel] Add `get_in/2`, `put_in/3`, `update_in/3` and `get_and_update_in/3` to handle nested data structure operations
|
||||
* [Kernel] Add `get_in/1`, `put_in/2`, `update_in/2` and `get_and_update_in/2` to handle nested data structure operations via paths
|
||||
* [Mix] Add `Mix.Config` to ease definition of configuration files
|
||||
* [Mix] Add `mix loadconfig` task that can be called multiple times to load external configs
|
||||
* [Mix] Support `--config` option on `mix run`
|
||||
* [Mix] Support `HTTP_PROXY` and `HTTPS_PROXY` on Mix url commands
|
||||
* [Mix] Support `--names` options in `mix help` which emit only names (useful for autocompletion)
|
||||
* [Protocol] Add `Protocol.consolidate/2`, `Protocol.consolidated?/1` and a `mix compile.protocols` task for protocol consolidation
|
||||
* [Protocol] Add `Protocol.derive/3` for runtime deriving of a struct
|
||||
* [String] Add `String.chunk/2`
|
||||
* [Struct] Add support for `@derive` before `defstruct/2` definitions
|
||||
|
||||
* Bug fixes
|
||||
* [File] `File.rm` now consistently deletes read-only across operating systems
|
||||
* [Kernel] Ensure Mix `_build` structure works on Windows when copying projects
|
||||
* [Kernel] Ensure `1.0E10` (with uppercase E) is also valid syntax
|
||||
* [Mix] Fix `mix do` task for Windows' powershell users
|
||||
* [Path] Fix `Path.absname("/")` and `Path.expand("/")` to return the absolute path `"/"`.
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `size/1` is deprecated, please use `byte_size/1` or `tuple_size/1` instead
|
||||
* [ExUnit] `teardown/2` and `teardown_all/2` are deprecated in favor of `on_exit/1` callbacks
|
||||
|
||||
* Deprecations
|
||||
* [Access] `Access.access/2` is deprecated in favor of `Access.get/2`
|
||||
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
|
||||
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
|
||||
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
|
||||
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
|
||||
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
|
||||
* [Kernel] Passing an atom `var!/1` is deprecated, variables can be built dynamically with `Macro.var/2`
|
||||
* [Mix] Exceptions that define a `:mix_error` field to be compatible with Mix are no longer supported. Instead please provide a `:mix` field and use `Mix.raise/1` and `Mix.raise/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Access] `Kernel.access/2` no longer exists and the `Access` protocol now requires `get/2` (instead of `access/2`) and `get_and_update/3` to be implemented
|
||||
* [Kernel] Retrieving docs as `module.__info__(:docs)` is no longer supported, please use `Code.get_docs/2` instead
|
||||
* [Kernel] `Code.compiler_options/1` no longer accepts custom options, only the ones specified by Elixir (use mix config instead)
|
||||
* [Mix] `mix new` no longer generates a supevision tree by default, please pass `--sup` instead
|
||||
* [Task] Tasks are automatically linked to callers and a failure in the task will crash the caller directly
|
||||
|
||||
## v0.13.3 (2014-05-24)
|
||||
|
||||
* Enhancements
|
||||
* [OptionParser] Add `:strict` option that only parses known switches
|
||||
@@ -46,7 +388,7 @@
|
||||
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
|
||||
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
|
||||
|
||||
# v0.13.2 (2014-05-11)
|
||||
## v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
* [Application] Add an Application module with common functions to work with OTP applications
|
||||
@@ -55,7 +397,7 @@
|
||||
* [Mix] `mix deps.clean` now works accross environments
|
||||
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
|
||||
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
|
||||
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
|
||||
* [Mix] Support application configurations in `config/config.exs`
|
||||
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
|
||||
* [Mix] `mix help` now uses ANSI formatting to print guides
|
||||
* [Regex] Support functions in `Regex.replace/4`
|
||||
@@ -98,7 +440,7 @@
|
||||
* [URI] The `URI.Info` record has now become the `URI` struct
|
||||
* [Version] The `Version.Schema` record has now become the `Version` struct
|
||||
|
||||
# v0.13.1 (2014-04-27)
|
||||
## v0.13.1 (2014-04-27)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
|
||||
@@ -124,7 +466,7 @@
|
||||
* [Path] Atoms are no longer supported in Path functions
|
||||
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
|
||||
|
||||
# v0.13.0 (2014-04-20)
|
||||
## v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
@@ -175,7 +517,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
## v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
@@ -193,7 +535,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
## v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
@@ -207,7 +549,7 @@
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
## v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
@@ -240,7 +582,7 @@
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
## v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
@@ -269,7 +611,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
## v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
@@ -293,7 +635,7 @@
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
## v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
@@ -330,7 +672,7 @@
|
||||
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
|
||||
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
|
||||
|
||||
# v0.11.2 (2013-11-14)
|
||||
## v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
|
||||
@@ -339,7 +681,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
|
||||
|
||||
# v0.11.1 (2013-11-07)
|
||||
## v0.11.1 (2013-11-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
|
||||
@@ -357,7 +699,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Setting `:load_paths` in your project configuration is deprecated
|
||||
|
||||
# v0.11.0 (2013-11-02)
|
||||
## v0.11.0 (2013-11-02)
|
||||
|
||||
* Enhancements
|
||||
* [Code] Eval now returns variables from other contexts
|
||||
@@ -404,7 +746,7 @@
|
||||
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
|
||||
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
|
||||
|
||||
# v0.10.3 (2013-10-02)
|
||||
## v0.10.3 (2013-10-02)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Add `Enum.take_every/2`
|
||||
@@ -439,7 +781,7 @@
|
||||
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
|
||||
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
|
||||
|
||||
# v0.10.2 (2013-09-03)
|
||||
## v0.10.2 (2013-09-03)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
@@ -502,7 +844,7 @@
|
||||
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
|
||||
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
|
||||
|
||||
# v0.10.1 (2013-08-03)
|
||||
## v0.10.1 (2013-08-03)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add support for `defmacrocallback/1`
|
||||
@@ -552,7 +894,7 @@
|
||||
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
|
||||
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
|
||||
|
||||
# v0.10.0 (2013-07-15)
|
||||
## v0.10.0 (2013-07-15)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
@@ -596,7 +938,7 @@
|
||||
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
|
||||
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
## v0.9.3 (2013-06-23)
|
||||
|
||||
* Enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
@@ -634,7 +976,7 @@
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
## v0.9.2 (2013-06-13)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
@@ -659,7 +1001,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
## v0.9.1 (2013-05-30)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
@@ -675,7 +1017,7 @@
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
## v0.9.0 (2013-05-23)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
@@ -688,7 +1030,7 @@
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
## v0.8.3 (2013-05-22)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
@@ -740,7 +1082,7 @@
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
* [Mix] `mix escriptize` now receives arguments as binaries
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
## v0.8.2 (2013-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
@@ -780,7 +1122,7 @@
|
||||
unquote(Macro.escape(x))
|
||||
end
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
## v0.8.1 (2013-02-17)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
@@ -807,7 +1149,7 @@
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
* [Process] `Process.self` is deprecated in favor `Kernel.self`
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
## v0.8.0 (2013-01-28)
|
||||
|
||||
* Enhancements
|
||||
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
|
||||
@@ -849,7 +1191,7 @@
|
||||
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
|
||||
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
|
||||
|
||||
# v0.7.2 (2012-12-04)
|
||||
## v0.7.2 (2012-12-04)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
@@ -875,7 +1217,7 @@
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
## v0.7.1 (2012-11-18)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
@@ -906,7 +1248,7 @@
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
## v0.7.0 (2012-10-20)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
@@ -955,7 +1297,7 @@
|
||||
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
|
||||
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
## v0.6.0 (2012-08-01)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
@@ -1005,6 +1347,6 @@
|
||||
* [Regex] Back references are now properly supported
|
||||
* [System] Add `System.find_executable`
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
## v0.5.0 (2012-05-24)
|
||||
|
||||
* First official release
|
||||
|
||||
+3
-4
@@ -10,7 +10,7 @@ Use the issues tracker for:
|
||||
* [bug reports](#bugs-reports)
|
||||
* [submitting pull requests](#pull-requests)
|
||||
|
||||
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
|
||||
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
|
||||
|
||||
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
We do our best to keep the issue tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
@@ -71,7 +71,7 @@ Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
We incentivate everyone to contribute to Elixir and help us tackle
|
||||
We incentivize everyone to contribute to Elixir and help us tackle
|
||||
existing issues! To do so, there are a few things you need to know
|
||||
about the code. First, Elixir code is divided in applications inside
|
||||
the `lib` folder:
|
||||
@@ -259,7 +259,6 @@ We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
* https://github.com/elixir-lang/elixir/pull/992
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
REBAR := rebar
|
||||
DOCS := v1.0
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -11,7 +12,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Functions
|
||||
@@ -32,7 +33,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
@@ -68,10 +69,10 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile EEx fully
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
|
||||
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
@@ -86,10 +87,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ echo "Embedding the Unicode database... (this may take a while)"
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,logger,Logger))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
$(eval $(call APP_TEMPLATE,mix,Mix))
|
||||
$(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
@@ -101,7 +103,7 @@ install: compile
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
@@ -124,13 +126,16 @@ clean_exbeam:
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
$(Q) rm -rf docs
|
||||
$(call DOCS,Elixir,elixir,Kernel)
|
||||
$(call DOCS,EEx,eex,EEx)
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call DOCS,Logger,logger,Logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -138,12 +143,22 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
rm -rf ../docs/$(DOCS)
|
||||
mv docs ../docs/$(DOCS)
|
||||
|
||||
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
|
||||
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
|
||||
|
||||
publish_mix: compile
|
||||
cd lib/mix && MIX_ENV=prod mix escript.build
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
|
||||
rm lib/mix/mix
|
||||
rm -rf lib/mix/_build
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
@@ -162,7 +177,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
|
||||
$(Q) mkdir -p $(TEST_EBIN)
|
||||
$(Q) $(ERLC) -o $(TEST_EBIN) $<
|
||||
|
||||
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@@ -170,16 +185,27 @@ test_doc_test: compile
|
||||
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) if [ "$(OS)" = "Windows_NT" ]; then \
|
||||
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
else \
|
||||
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
fi
|
||||
|
||||
.dialyzer.base_plt:
|
||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
||||
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
|
||||
#==> Dialyzer tasks
|
||||
|
||||
dialyze: .dialyzer.base_plt
|
||||
$(Q) rm -f .dialyzer_plt
|
||||
$(Q) cp .dialyzer.base_plt .dialyzer_plt
|
||||
@ echo "==> Adding Elixir to PLT..."
|
||||
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
|
||||
PLT = .elixir.plt
|
||||
|
||||
$(PLT):
|
||||
@ echo "==> Building PLT with Elixir's dependencies..."
|
||||
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
|
||||
|
||||
clean_plt:
|
||||
$(Q) rm -f $(PLT)
|
||||
|
||||
build_plt: clean_plt $(PLT)
|
||||
|
||||
dialyze: compile $(PLT)
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
||||
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
|
||||
|
||||
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
$ cd elixir
|
||||
$ make clean test
|
||||
|
||||
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
|
||||
|
||||
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
@@ -37,7 +39,7 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
## Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* \#elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
|
||||
+28
-14
@@ -1,29 +1,43 @@
|
||||
## Release process
|
||||
# Release process
|
||||
|
||||
## All releases
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
1. Ensure you are running on the oldest supported Erlang version
|
||||
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
2. Remove all `-dev` extension from versions (see below for all files)
|
||||
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Ensure CHANGELOG is updated and add current date
|
||||
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
5) Push master and tags
|
||||
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Ensure minimum supported Hex works with new release (instructions upcoming)
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Push master and the new tag
|
||||
|
||||
8) Merge master into stable branch and push it
|
||||
8. Release new docs with `make release_docs`, move docs to `docs/stable` if appropriate, and push
|
||||
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
|
||||
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
|
||||
|
||||
## New vMAJOR.MINOR releases
|
||||
|
||||
11. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
|
||||
|
||||
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
14. `make release_docs` and push it to `elixir-lang/docs`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
* VERSION (make sure there is no newline in this file)
|
||||
* CHANGELOG.md
|
||||
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
|
||||
|
||||
+9
-6
@@ -17,7 +17,6 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -84,7 +80,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
if [ "$OS" != "Windows_NT" ]; then
|
||||
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
|
||||
fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
@@ -94,4 +97,4 @@ if [ -z "$ERL_PATH" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+31
-22
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
if "%1"=="" goto documentation
|
||||
if "%1"=="--help" goto documentation
|
||||
if "%1"=="-h" goto documentation
|
||||
@@ -22,13 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
|
||||
echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --werl Uses Erlang's Windows shell GUI
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
goto :EOF
|
||||
goto end
|
||||
|
||||
:parseopts
|
||||
|
||||
@@ -47,6 +48,9 @@ set beforeExtra=
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Designates which mode / Elixir component to run as
|
||||
set runMode="elixir"
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -60,24 +64,24 @@ if "%par%"=="""" (
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
IF "%par%"==""--werl"" (Set useWerl=1)
|
||||
IF "%par%"==""+iex"" (Set runMode="iex")
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
IF NOT "%par%"=="%par:-r=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pr=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pa=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pz=%" (shift)
|
||||
IF NOT "%par%"=="%par:--app=%" (shift)
|
||||
IF NOT "%par%"=="%par:--remsh=%" (shift)
|
||||
IF """"=="%par:-e=%" (shift)
|
||||
IF """"=="%par:-r=%" (shift)
|
||||
IF """"=="%par:-pr=%" (shift)
|
||||
IF """"=="%par:-pa=%" (shift)
|
||||
IF """"=="%par:-pz=%" (shift)
|
||||
IF """"=="%par:--app=%" (shift)
|
||||
IF """"=="%par:--remsh=%" (shift)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
goto:startloop
|
||||
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
@@ -90,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
IF NOT %runMode% == "iex" (
|
||||
set beforeExtra=-s elixir start_cli %beforeExtra%
|
||||
)
|
||||
IF %useWerl% EQU 1 (
|
||||
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
:end
|
||||
endlocal
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -21,6 +21,6 @@ echo --verbose Print informational messages.
|
||||
echo.
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS >&2
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,7 +16,6 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
@@ -39,4 +38,6 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env elixir
|
||||
# Reminder: apply any changes in this file to bin\mix.bat
|
||||
Mix.start
|
||||
Mix.CLI.main
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
for ($i = 0; $i -lt $args.length; $i++)
|
||||
{
|
||||
if ($args[$i] -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
|
||||
{
|
||||
$newArgs += ($args[$i][$j] + ',')
|
||||
}
|
||||
$newArgs += $args[$i][-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$newArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
& $mixBatPath $newArgs
|
||||
+25
-20
@@ -1,5 +1,9 @@
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message]
|
||||
defexception [:message, :file, :line]
|
||||
|
||||
def message(exception) do
|
||||
"#{exception.file}:#{exception.line}: #{exception.message}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx do
|
||||
@@ -14,32 +18,30 @@ defmodule EEx do
|
||||
|
||||
This module provides 3 main APIs for you to use:
|
||||
|
||||
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before;
|
||||
1. Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before.
|
||||
|
||||
2) Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time;
|
||||
2. Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time.
|
||||
|
||||
3) Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
3. Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
|
||||
## Options
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -83,7 +85,7 @@ defmodule EEx do
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
In other words, `<%= @foo %>` is simply translated to:
|
||||
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
@@ -93,6 +95,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the string.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -121,6 +124,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the file contents.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -148,6 +152,7 @@ defmodule EEx do
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@external_resource file
|
||||
@file file
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
# When changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
|
||||
def compile(source, opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
tokens = EEx.Tokenizer.tokenize(source, line)
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
case EEx.Tokenizer.tokenize(source, line) do
|
||||
{:ok, tokens} ->
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
{:error, line, message} ->
|
||||
raise EEx.SyntaxError, line: line, file: file, message: message
|
||||
end
|
||||
end
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
|
||||
{buffer, t}
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
|
||||
defp generate_buffer([], _buffer, _scope, state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
|
||||
file: state.file, line: state.line
|
||||
end
|
||||
|
||||
# Creates a placeholder and wrap it inside the expression block
|
||||
|
||||
+59
-16
@@ -1,27 +1,28 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
Basic EEx engine that ships with Elixir.
|
||||
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression.
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression.
|
||||
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression.
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are: `""` and `"="`.
|
||||
|
||||
* `""`
|
||||
* `"="`
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
`EEx.Engine` can be used directly if one desires to use the
|
||||
default implementations for the functions above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -30,6 +31,48 @@ defmodule EEx.Engine do
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
|
||||
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles assigns in quoted expressions.
|
||||
|
||||
This can be added to any custom engine by invoking
|
||||
`handle_assign/3` with `Macro.prewalk/1`:
|
||||
|
||||
def handle_expr(buffer, token, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
EEx.Engine.handle_expr(buffer, token, expr)
|
||||
end
|
||||
|
||||
"""
|
||||
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
line = meta[:line] || 0
|
||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
@@ -56,7 +99,7 @@ defmodule EEx.Engine do
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp <> to_string(unquote(expr))
|
||||
tmp <> String.Chars.to_string(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,68 +1,12 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
The default engine used by EEx.
|
||||
|
||||
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
|
||||
examples of using this module.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, transform(expr))
|
||||
end
|
||||
|
||||
defp transform({a, b, c}) do
|
||||
{transform(a), b, transform(c)}
|
||||
end
|
||||
|
||||
defp transform({a, b}) do
|
||||
{transform(a), transform(b)}
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that, when used with the
|
||||
`TransformerEngine`, allows a developer to access
|
||||
assigns using `@` as syntax.
|
||||
|
||||
This engine is included by default on the SmartEngine.
|
||||
It includes assigns (like `@foo`) and possibly other
|
||||
conveniences in the future.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
@@ -88,29 +32,10 @@ defmodule EEx.AssignsEngine do
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
use EEx.Engine
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
super(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes
|
||||
`EEx.AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -3,41 +3,52 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
@doc """
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* {:text, contents}
|
||||
* {:expr, line, marker, contents}
|
||||
* {:start_expr, line, marker, contents}
|
||||
* {:middle_expr, line, marker, contents}
|
||||
* {:end_expr, line, marker, contents}
|
||||
It returns {:ok, list} with the following tokens:
|
||||
|
||||
* `{:text, contents}`
|
||||
* `{:expr, line, marker, contents}`
|
||||
* `{:start_expr, line, marker, contents}`
|
||||
* `{:middle_expr, line, marker, contents}`
|
||||
* `{:end_expr, line, marker, contents}`
|
||||
|
||||
Or `{:error, line, error}` in case of errors.
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(String.to_char_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
tokenize(list, line, [], [])
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
case expr(t, line, [?%, ?<|buffer]) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, buffer, new_line, rest} ->
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{_, new_line, rest} = tokenize_expr t, line, []
|
||||
tokenize rest, new_line, buffer, acc
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, _, new_line, rest} ->
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{marker, t} = retrieve_marker(t)
|
||||
{expr, new_line, rest} = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, expr, new_line, rest} ->
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{buffer, line, t}
|
||||
defp expr([?%, ?>|t], line, buffer) do
|
||||
{:ok, buffer, line, t}
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
tokenize_expr t, line + 1, [?\n|buffer]
|
||||
defp expr('\n' ++ t, line, buffer) do
|
||||
expr t, line + 1, [?\n|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([h|t], line, buffer) do
|
||||
tokenize_expr t, line, [h|buffer]
|
||||
defp expr([h|t], line, buffer) do
|
||||
expr t, line, [h|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([], _line, _buffer) do
|
||||
raise EEx.SyntaxError, message: "missing token: %>"
|
||||
defp expr([], line, _buffer) do
|
||||
{:error, line, "missing token '%>'"}
|
||||
end
|
||||
|
||||
# Receive an expression content and check
|
||||
|
||||
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
test "compiled preserved line numbers" do
|
||||
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
|
||||
Macro.prewalk(result, fn
|
||||
{_left, meta, _right} ->
|
||||
assert Keyword.get(meta, :line, 0) in [0, 1]
|
||||
_ ->
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
|
||||
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
|
||||
assert T.tokenize('foo <% bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -32,50 +35,50 @@ baz %>
|
||||
<% foo %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
assert T.tokenize(string, 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, '\n'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
|
||||
{:text, 'foo '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' cond do '},
|
||||
{:middle_expr, 1, "", ' false -> '},
|
||||
@@ -83,23 +86,21 @@ baz %>
|
||||
{:middle_expr, 1, "", ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "raise syntax error when there is start mark and no end mark" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
T.tokenize('foo <% :bar', 1)
|
||||
end
|
||||
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
|
||||
end
|
||||
end
|
||||
|
||||
+31
-20
@@ -2,7 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
defmodule EExText.Compiled do
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
@@ -12,6 +12,10 @@ defmodule EExText.Compiled do
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
|
||||
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
@@ -46,7 +50,8 @@ defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
doctest EEx.Engine
|
||||
doctest EEx.SmartEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
@@ -106,25 +111,25 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "raises a syntax error when the token is invalid" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
|
||||
EEx.compile_string "foo <%= bar"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% end %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when start expression is found without an end expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
|
||||
EEx.compile_string "foo <% if true do %>"
|
||||
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
|
||||
EEx.compile_string "foo\n<% if true do %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when nested end expression is found without an start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
test "raises a syntax error when nested end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% if true do %><% end %><% end %>"
|
||||
end
|
||||
end
|
||||
@@ -319,39 +324,45 @@ foo
|
||||
end
|
||||
end
|
||||
|
||||
test "sets external resource attribute" do
|
||||
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
end
|
||||
|
||||
test "defined from string" do
|
||||
assert EExText.Compiled.string_sample(1, 2) == "3"
|
||||
assert EExTest.Compiled.string_sample(1, 2) == "3"
|
||||
end
|
||||
|
||||
test "defined from file" do
|
||||
assert EExText.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
|
||||
end
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
assert EExTest.Compiled.before_compile ==
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
{EExTest.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.after_compile ==
|
||||
{23,
|
||||
{EExTest.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.unknown ==
|
||||
{29,
|
||||
{EExTest.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
[file: 'unknown', line: 28]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
+103
-20
@@ -1,13 +1,14 @@
|
||||
import Kernel, except: [access: 2]
|
||||
|
||||
defprotocol Access do
|
||||
@moduledoc """
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
The Access protocol is used by `foo[bar]` and also
|
||||
empowers the nested update functions in Kernel.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3` are also all powered by the Access
|
||||
protocol.
|
||||
|
||||
This protocol is implemented by default for keywords, maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
@@ -22,46 +23,128 @@ defprotocol Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
The key comparison must be implemented using the `===` operator.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
Accesses the given key in the container.
|
||||
"""
|
||||
def access(container, key)
|
||||
@spec get(t, term) :: t
|
||||
def get(container, key)
|
||||
|
||||
@doc """
|
||||
Gets a value and updates the given `key` in one pass.
|
||||
|
||||
The function must receive the value for the given `key`
|
||||
(or `nil` if the key doesn't exist in `container`) and
|
||||
the function must return a tuple containing the `get`
|
||||
value and the new value to be stored in the `container`.
|
||||
"""
|
||||
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
|
||||
def get_and_update(container, key, fun)
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
def access(dict, key) when is_atom(key) do
|
||||
def get(dict, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{^key, value} -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
def get(_dict, key) do
|
||||
raise ArgumentError,
|
||||
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) when is_atom(key) do
|
||||
get_and_update(dict, [], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([{key, value}|t], acc, key, fun) do
|
||||
{get, update} = fun.(value)
|
||||
{get, :lists.reverse(acc, [{key, update}|t])}
|
||||
end
|
||||
|
||||
defp get_and_update([h|t], acc, key, fun) do
|
||||
get_and_update(t, [h|acc], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([], acc, key, fun) do
|
||||
{get, update} = fun.(nil)
|
||||
{get, [{key, update}|:lists.reverse(acc)]}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
def get(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update(map, key, fun) do
|
||||
value =
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
end
|
||||
|
||||
def get!(%{} = map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get!(other, key) do
|
||||
raise ArgumentError,
|
||||
"could not get key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
|
||||
def get_and_update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
:error ->
|
||||
raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
def get(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
def get(atom, _) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
def get_and_update(nil, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key} on a nil value"
|
||||
end
|
||||
|
||||
def get_and_update(atom, _key, _fun) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
defp undefined(atom) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: atom,
|
||||
description: "only the nil atom is supported"
|
||||
end
|
||||
end
|
||||
|
||||
+102
-29
@@ -3,8 +3,8 @@ defmodule Agent do
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
Often in Elixir there is a need to share or store state that
|
||||
must be accessed from different processes or by a same process
|
||||
in different points in time.
|
||||
must be accessed from different processes or by the same process
|
||||
at different points in time.
|
||||
|
||||
The Agent module provides a basic server implementation that
|
||||
allows state to be retrieved and updated via a simple API.
|
||||
@@ -35,12 +35,12 @@ defmodule Agent do
|
||||
end
|
||||
end
|
||||
|
||||
Note that agents still provide a segregation in between the
|
||||
Note that agents still provide a segregation between the
|
||||
client and server APIs, as seen in GenServers. In particular,
|
||||
all code inside the function passed to the agent is executed
|
||||
by the agent. This distinction is important because you may
|
||||
want to avoid expensive operations inside the agent, as it will
|
||||
effectively block the agent until the request is fullfilled.
|
||||
effectively block the agent until the request is fulfilled.
|
||||
|
||||
Consider these two examples:
|
||||
|
||||
@@ -60,7 +60,7 @@ defmodule Agent do
|
||||
sent to the client cheaply or large enough to require processing on
|
||||
the server (or at least some initial processing).
|
||||
|
||||
## Name registration
|
||||
## Name Registration
|
||||
|
||||
An Agent is bound to the same name registration rules as GenServers.
|
||||
Read more about it in the `GenServer` docs.
|
||||
@@ -68,22 +68,22 @@ defmodule Agent do
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions in between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
provides two APIs, one that works with anonymous functions and another
|
||||
that expects explicit module, function and arguments.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them by
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
crash. That said, if you plan to run in distributed environments, agents
|
||||
should likely be avoided.
|
||||
In a distributed setup with multiple nodes, the API that accepts anonymous
|
||||
functions only works if the caller (client) and the agent have the same
|
||||
version of the caller module.
|
||||
|
||||
Note, however, that agents work fine if you want to perform hot code
|
||||
swapping, as it keeps both the old and new versions of a given module.
|
||||
We detail how to do hot code swapping with agents in the next section.
|
||||
Keep in mind this issue also shows up when performing "rolling upgrades"
|
||||
with agents. By rolling upgrades we mean the following situation: you wish
|
||||
to deploy a new version of your software by *shutting down* some of your
|
||||
nodes and replacing them with nodes running a new version of the software.
|
||||
In this setup, part of your environment will have one version of a given
|
||||
module and the other part another version (the newer one) of the same module.
|
||||
|
||||
The best solution is to simply use the explicit module, function and arguments
|
||||
APIs when working with distributed agents.
|
||||
|
||||
## Hot code swapping
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Agent do
|
||||
@type state :: term
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process.
|
||||
Starts an agent linked to the current process with the given function.
|
||||
|
||||
This is often used to start the agent as part of a supervision tree.
|
||||
|
||||
@@ -122,20 +122,22 @@ defmodule Agent do
|
||||
## Options
|
||||
|
||||
The `:name` option is used for registration as described in the module
|
||||
documentation. If the `:timeout` option is present, the agent is allowed to
|
||||
spend at most the given amount of milliseconds on initialization or it will
|
||||
be terminated and the start function will return `{:error, :timeout}`.
|
||||
documentation.
|
||||
|
||||
If the `:timeout` option is present, the agent is allowed to spend at most
|
||||
the given number of milliseconds on initialization or it will be terminated
|
||||
and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists
|
||||
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
|
||||
an agent with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
@@ -147,6 +149,18 @@ defmodule Agent do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process with the given module
|
||||
function and arguments.
|
||||
|
||||
Same as `start_link/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start_link(module, atom, [any], GenServer.options) :: on_start
|
||||
def start_link(module, fun, args, options \\ []) do
|
||||
GenServer.start_link(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent process without links (outside of a supervision tree).
|
||||
|
||||
@@ -158,7 +172,18 @@ defmodule Agent do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the agent value and executes the given function.
|
||||
Starts an agent with the given module function and arguments.
|
||||
|
||||
Similar to `start/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start(module, atom, [any], GenServer.options) :: on_start
|
||||
def start(module, fun, args, options \\ []) do
|
||||
GenServer.start(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The result of the function invocation is
|
||||
@@ -171,12 +196,24 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
Same as `get/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get(agent, module, atom, [term], timeout) :: any
|
||||
def get(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return a tuple with two
|
||||
elements, the first being the value to return (i.e. the get value)
|
||||
elements, the first being the value to return (i.e. the `get` value)
|
||||
and the second one is the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
@@ -186,6 +223,18 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get_and_update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
Same as `get_and_update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get_and_update(agent, module, atom, [term], timeout) :: any
|
||||
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
@@ -195,11 +244,23 @@ defmodule Agent do
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec update(agent, (state -> state)) :: :ok
|
||||
@spec update(agent, (state -> state), timeout) :: :ok
|
||||
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
Same as `update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec update(agent, module, atom, [term], timeout) :: :ok
|
||||
def update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
@@ -211,7 +272,19 @@ defmodule Agent do
|
||||
"""
|
||||
@spec cast(agent, (state -> state)) :: :ok
|
||||
def cast(agent, fun) when is_function(fun, 1) do
|
||||
GenServer.cast(agent, fun)
|
||||
GenServer.cast(agent, {:cast, fun})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
Same as `cast/2` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec cast(agent, module, atom, [term]) :: :ok
|
||||
def cast(agent, module, fun, args) do
|
||||
GenServer.cast(agent, {:cast, {module, fun, args}})
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -4,20 +4,20 @@ defmodule Agent.Server do
|
||||
use GenServer
|
||||
|
||||
def init(fun) do
|
||||
{:ok, fun.()}
|
||||
{:ok, run(fun, [])}
|
||||
end
|
||||
|
||||
def handle_call({:get, fun}, _from, state) do
|
||||
{:reply, fun.(state), state}
|
||||
{:reply, run(fun, [state]), state}
|
||||
end
|
||||
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
{reply, state} = fun.(state)
|
||||
{reply, state} = run(fun, [state])
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:update, fun}, _from, state) do
|
||||
{:reply, :ok, fun.(state)}
|
||||
{:reply, :ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
@@ -28,26 +28,29 @@ defmodule Agent.Server do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
def handle_cast(fun, state) when is_function(fun, 1) do
|
||||
{:noreply, fun.(state)}
|
||||
def handle_cast({:cast, fun}, state) do
|
||||
{:noreply, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
def code_change(_old, state, { m, f, a }) do
|
||||
{:ok, apply(m, f, [state|a])}
|
||||
def code_change(_old, state, fun) do
|
||||
{:ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
# There is a race condition if the agent is
|
||||
# restarted too fast and it is registered.
|
||||
try do
|
||||
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
|
||||
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
|
||||
defp run(fun, extra), do: apply(fun, extra)
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Application do
|
||||
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stoping your application
|
||||
responsible for configuring, starting and stopping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment,
|
||||
and the application callback module.
|
||||
@@ -48,7 +48,7 @@ defmodule Application do
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the fucntions in this module for directly access or modify
|
||||
Do not use the functions in this module for directly access or modify
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
@@ -83,8 +83,8 @@ defmodule Application do
|
||||
setup where applications takeover and failovers are configured. This particular
|
||||
aspect of applications can be read with more detail in the OTP documentation:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
@@ -152,9 +152,8 @@ defmodule Application do
|
||||
|
||||
## Options
|
||||
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms);
|
||||
|
||||
* `:persistent` - persists the given value on application load and reloads;
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms)
|
||||
* `:persistent` - persists the given value on application load and reloads
|
||||
|
||||
If `put_env/4` is called before the application is loaded, the application
|
||||
environment values specified in the `.app` file will override the ones
|
||||
@@ -202,7 +201,7 @@ defmodule Application do
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
@@ -223,13 +222,16 @@ defmodule Application do
|
||||
|
||||
The `type` argument specifies the type of the application:
|
||||
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated;
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application terminates
|
||||
abnormally, all other applications and the entire node are also terminated;
|
||||
* `:temporary` - if `app` termiantes, it is reported but no other applications
|
||||
are terminated (the default);
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated.
|
||||
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application
|
||||
terminates abnormally, all other applications and the entire node are
|
||||
also terminated.
|
||||
|
||||
* `:temporary` - if `app` terminates, it is reported but no other
|
||||
applications are terminated (the default).
|
||||
|
||||
Note that it is always possible to stop an application explicitly by calling
|
||||
`stop/1`. Regardless of the type of the application, no other applications will
|
||||
@@ -320,7 +322,7 @@ defmodule Application do
|
||||
|
||||
@doc """
|
||||
Formats the error reason returned by `start/2`,
|
||||
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
|
||||
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
+230
-102
@@ -5,8 +5,91 @@ defmodule Base do
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
This document defines the commonly used base 16, base 32, and base
|
||||
64 encoding schemes.
|
||||
|
||||
## Base 16 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Base 32 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
|
||||
## Base 32 (extended hex) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Base 64 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Base 64 (URL and filename safe) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
@@ -29,41 +112,82 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_case(:upper, func),
|
||||
do: func
|
||||
defp encode_case(:lower, func),
|
||||
do: &to_lower(func.(&1))
|
||||
|
||||
defp decode_case(:upper, func),
|
||||
do: func
|
||||
defp decode_case(:lower, func),
|
||||
do: &func.(from_lower(&1))
|
||||
defp decode_case(:mixed, func),
|
||||
do: &func.(from_mixed(&1))
|
||||
|
||||
defp to_lower(char) when char in ?A..?Z,
|
||||
do: char + (?a - ?A)
|
||||
defp to_lower(char),
|
||||
do: char
|
||||
|
||||
defp from_lower(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_lower(char) when not char in ?A..?Z,
|
||||
do: char
|
||||
defp from_lower(char),
|
||||
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
|
||||
|
||||
defp from_mixed(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_mixed(char),
|
||||
do: char
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
iex> Base.encode16("foobar", case: :lower)
|
||||
"666f6f626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
@spec encode16(binary, Keyword.t) :: binary
|
||||
def encode16(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode16(data, encode_case(case, &enc16/1))
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6f626172", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6F626172", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{:ok, decode16!(string)}
|
||||
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode16(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -71,6 +195,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -79,10 +207,18 @@ defmodule Base do
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6f626172", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6F626172", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
@spec decode16!(binary, Keyword.t) :: binary
|
||||
def decode16!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode16(string, decode_case(case, &dec16/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -102,28 +238,6 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
@@ -175,28 +289,6 @@ defmodule Base do
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
@@ -231,43 +323,49 @@ defmodule Base do
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
iex> Base.encode32("foobar", case: :lower)
|
||||
"mzxw6ytboi======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
@spec encode32(binary, Keyword.t) :: binary
|
||||
def encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzxw6ytboi======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
def decode32(string) do
|
||||
{:ok, do_decode32(string, &dec32/1)}
|
||||
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode32(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -275,6 +373,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -283,54 +385,68 @@ defmodule Base do
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
@spec decode32!(binary, Keyword.t) :: binary
|
||||
def decode32!(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
iex> Base.hex_encode32("foobar", case: :lower)
|
||||
"cpnmuoj1e8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
@spec hex_encode32(binary, Keyword.t) :: binary
|
||||
def hex_encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32hex/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{:ok, do_decode32(string, &dec32hex/1)}
|
||||
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def hex_decode32(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -339,6 +455,10 @@ defmodule Base do
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -347,10 +467,18 @@ defmodule Base do
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
@spec hex_decode32!(binary, Keyword.t) :: binary
|
||||
def hex_decode32!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32hex/1))
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
@@ -371,7 +499,7 @@ defmodule Base do
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
@@ -386,7 +514,7 @@ defmodule Base do
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
@@ -406,7 +534,7 @@ defmodule Base do
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
@@ -433,7 +561,7 @@ defmodule Base do
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
|
||||
@@ -24,8 +24,8 @@ defmodule Bitwise do
|
||||
Allow a developer to use this module in their programs with
|
||||
the following options:
|
||||
|
||||
* `:only_operators` - Include only operators;
|
||||
* `:skip_operators` - Skip operators;
|
||||
* `:only_operators` - include only operators
|
||||
* `:skip_operators` - skip operators
|
||||
|
||||
"""
|
||||
defmacro __using__(options) do
|
||||
|
||||
+116
-27
@@ -51,6 +51,23 @@ defmodule Code do
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Re-add paths given to the command line to keep their position
|
||||
on the overall code path.
|
||||
|
||||
Some tools may change the code path by prepending new items but
|
||||
still want the paths given by the user to have higher priority.
|
||||
Calling this function guarantees the paths are re-added on
|
||||
top of the user given ones.
|
||||
"""
|
||||
@spec readd_paths() :: :ok
|
||||
def readd_paths() do
|
||||
{pa, pz} = :elixir_code_server.call(:paths)
|
||||
:code.add_pathsa(pa)
|
||||
:code.add_pathsz(pz)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
@@ -59,21 +76,24 @@ defmodule Code do
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
|
||||
Additionally, the following scope values can be configured:
|
||||
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
|
||||
* `:requires` - a list of modules required
|
||||
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity; the list
|
||||
of function names and arity must be sorted
|
||||
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity; the list
|
||||
of function names and arity must be sorted
|
||||
|
||||
Notice that setting any of the values above overrides Elixir's default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
@@ -198,13 +218,13 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable.
|
||||
* `:file` - the filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable
|
||||
|
||||
* `:line` - The line reported in the `__ENV__` variable.
|
||||
* `:line` - the line reported in the `__ENV__` variable
|
||||
|
||||
* `:existing_atoms_only` - When `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
* `:existing_atoms_only` - when `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer
|
||||
|
||||
## Macro.to_string/2
|
||||
|
||||
@@ -240,12 +260,12 @@ defmodule Code do
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
byte code, `eval_file` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
eval_string File.read!(file), [], [file: file, line: 1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -308,6 +328,15 @@ defmodule Code do
|
||||
:elixir_code_server.call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the available compiler options.
|
||||
|
||||
See `Code.compiler_options/1` for more info.
|
||||
"""
|
||||
def available_compiler_options do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :warnings_as_errors]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options.
|
||||
|
||||
@@ -315,20 +344,26 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default;
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default
|
||||
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, `false` by default;
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled
|
||||
module; this allows a developer to reconstruct the original source
|
||||
code, `false` by default
|
||||
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were already defined
|
||||
without raising errors, `false` by default;
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were
|
||||
already defined without raising errors, `false` by default
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are
|
||||
generated
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
{opts, bad} = Keyword.split(opts, available_compiler_options)
|
||||
if bad != [] do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
end
|
||||
|
||||
@@ -448,6 +483,60 @@ defmodule Code do
|
||||
match?({:module, ^module}, ensure_compiled(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the docs for the given module.
|
||||
|
||||
When given a module name, it finds its BEAM code and reads the docs from it.
|
||||
|
||||
When given a path to a .beam file, it will load the docs directly from that
|
||||
file.
|
||||
|
||||
The return value depends on the `kind` value:
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:all` - a keyword list with both `:docs` and `:moduledoc`
|
||||
|
||||
"""
|
||||
def get_docs(module, kind) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{_module, bin, _beam_path} ->
|
||||
do_get_docs(bin, kind)
|
||||
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_docs(binpath, kind) when is_binary(binpath) do
|
||||
do_get_docs(String.to_char_list(binpath), kind)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp do_get_docs(bin_or_path, kind) do
|
||||
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
|
||||
{:ok, {_module, [{@docs_chunk, bin}]}} ->
|
||||
lookup_docs(:erlang.binary_to_term(bin), kind)
|
||||
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind),
|
||||
do: do_lookup_docs(docs, kind)
|
||||
|
||||
# unsupported chunk version
|
||||
defp lookup_docs(_, _), do: nil
|
||||
|
||||
defp do_lookup_docs(docs, :all), do: docs
|
||||
defp do_lookup_docs(docs, kind) when kind in [:docs, :moduledoc],
|
||||
do: Keyword.get(docs, kind)
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
|
||||
@@ -8,12 +8,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
@@ -27,25 +21,13 @@ defprotocol Collectable do
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
`Enumerable` protocol. `into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
|
||||
`Collectable.into/1` is about collecting those values into a structure.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
@@ -65,10 +47,6 @@ defprotocol Collectable do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
@@ -79,34 +57,16 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
|
||||
acc, :done -> IO.iodata_to_binary(acc)
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
|
||||
+102
-102
@@ -40,7 +40,7 @@ defmodule Dict do
|
||||
For example:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
# implement required functions (see below)
|
||||
# override default implementations if optimization
|
||||
@@ -49,11 +49,11 @@ defmodule Dict do
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
@@ -61,24 +61,24 @@ defmodule Dict do
|
||||
Based on these functions, `Dict` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide
|
||||
your own implementation if needed.
|
||||
@@ -288,8 +288,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(dict))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -304,8 +304,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(dict))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -319,8 +319,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(dict)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -334,10 +334,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(dict, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(dict, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -352,12 +352,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
iex> Dict.get(dict, :b)
|
||||
nil
|
||||
iex> Dict.get(d, :b, 3)
|
||||
iex> Dict.get(dict, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -371,10 +371,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(dict, :a)
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(d, :b)
|
||||
iex> Dict.fetch(dict, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@@ -389,8 +389,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -405,9 +405,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -421,9 +421,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put_new(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -438,13 +438,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.delete(dict, :a)
|
||||
iex> Dict.get(dict, :a)
|
||||
nil
|
||||
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
iex> dict = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(dict, :a) == dict
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -454,10 +454,10 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the dict `b` into dict `a`.
|
||||
Merges the dict `dict2` into dict `dict1`.
|
||||
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
If one of the `dict2` entries already exists in `dict1`, the
|
||||
functions in entries in `dict2` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
@@ -466,18 +466,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2)
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -502,18 +502,18 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@@ -528,9 +528,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -546,9 +546,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -566,19 +566,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
|
||||
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c])
|
||||
iex> {Dict.to_list(d1), Dict.to_list(d2)}
|
||||
iex> dict = Enum.into([], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
|
||||
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
|
||||
{[], []}
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
|
||||
{[a: 1, b: 2], []}
|
||||
|
||||
"""
|
||||
@@ -593,14 +593,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[b: 2]
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -616,12 +616,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.take(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[a: 1]
|
||||
iex> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Dict.take(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -645,14 +645,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
true
|
||||
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = []
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
false
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
use Dict
|
||||
end
|
||||
end
|
||||
end
|
||||
+111
-64
@@ -39,9 +39,9 @@ defprotocol Enumerable do
|
||||
|
||||
It must be a tagged tuple with one of the following "tags":
|
||||
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
|
||||
Depending on the accumulator value, the result returned by
|
||||
`Enumerable.reduce/3` will change. Please check the `result`
|
||||
@@ -270,8 +270,13 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `default` if index is out of bounds.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
@@ -287,7 +292,6 @@ defmodule Enum do
|
||||
:none
|
||||
|
||||
"""
|
||||
@spec at(t, integer) :: element | nil
|
||||
@spec at(t, integer, default) :: element | default
|
||||
def at(collection, n, default \\ nil) do
|
||||
case fetch(collection, n) do
|
||||
@@ -330,7 +334,6 @@ defmodule Enum do
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
"""
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
@@ -338,7 +341,7 @@ defmodule Enum do
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
if is_nil(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
||||
@@ -566,12 +569,17 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `{:ok, element}` if found, otherwise `:error`.
|
||||
|
||||
A negative index can be passed, which means the collection is
|
||||
enumerated once and the index is counted from the end (i.e.
|
||||
`-1` fetches the last element).
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
@@ -585,11 +593,11 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec fetch(t, integer) :: {:ok, element} | :error
|
||||
def fetch(collection, n) when is_list(collection) and n >= 0 do
|
||||
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
|
||||
do_fetch(collection, n)
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n >= 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n >= 0 do
|
||||
res =
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
if acc == n do
|
||||
@@ -605,15 +613,20 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n < 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n < 0 do
|
||||
do_fetch(reverse(collection), abs(n + 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Raises `OutOfBoundsError` if the given position
|
||||
is outside the range of the collection.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch!([2, 4, 6], 0)
|
||||
@@ -689,7 +702,6 @@ defmodule Enum do
|
||||
3
|
||||
|
||||
"""
|
||||
@spec find(t, (element -> any)) :: element | nil
|
||||
@spec find(t, default, (element -> any)) :: element | default
|
||||
def find(collection, ifnone \\ nil, fun)
|
||||
|
||||
@@ -716,7 +728,6 @@ defmodule Enum do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||
def find_value(collection, ifnone \\ nil, fun)
|
||||
|
||||
@@ -931,19 +942,18 @@ defmodule Enum do
|
||||
"1 = 2 = 3"
|
||||
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t) :: String.t
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(entry)
|
||||
entry, acc -> acc <> joiner <> to_string(entry)
|
||||
entry, :first -> enum_to_string(entry)
|
||||
entry, acc -> [acc, joiner|enum_to_string(entry)]
|
||||
end)
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -990,20 +1000,19 @@ defmodule Enum do
|
||||
"2 = 4 = 6"
|
||||
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
entry, :first -> enum_to_string(mapper.(entry))
|
||||
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
|
||||
end)
|
||||
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1396,7 +1405,7 @@ defmodule Enum do
|
||||
result. For example, one could do the following to set a seed
|
||||
dynamically:
|
||||
|
||||
:random.seed(:erlang.now)
|
||||
:random.seed(:os.timestamp)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1417,18 +1426,33 @@ defmodule Enum do
|
||||
@doc """
|
||||
Returns a subset list of the given collection. Drops elements
|
||||
until element position `start`, then takes `count` elements.
|
||||
|
||||
If the count is greater than collection length, it returns as
|
||||
much as possible. If zero, then it returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5, 10)
|
||||
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 100)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 0)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, non_neg_integer) :: list
|
||||
|
||||
def slice(_coll, _start, 0), do: []
|
||||
|
||||
def slice(coll, start, count) when start < 0 do
|
||||
{list, new_start} = enumerate_and_count(coll, start)
|
||||
if new_start >= 0, do: slice(list, new_start, count)
|
||||
if new_start >= 0 do
|
||||
slice(list, new_start, count)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
|
||||
@@ -1436,7 +1460,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when start >= 0 and count > 0 do
|
||||
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
{_, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
_entry, {start, count, _list} when start > 0 ->
|
||||
{:cont, {start-1, count, []}}
|
||||
entry, {start, count, list} when count > 1 ->
|
||||
@@ -1445,15 +1469,7 @@ defmodule Enum do
|
||||
{:halt, {start, count, [entry|list]}}
|
||||
end) |> elem(1)
|
||||
|
||||
if start <= 0, do: :lists.reverse(list)
|
||||
end
|
||||
|
||||
def slice(coll, start, 0) do
|
||||
res =
|
||||
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
|
||||
if start > 0, do: {:cont, start-1}, else: {:halt, []}
|
||||
end) |> elem(1)
|
||||
if is_list(res), do: res
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1468,17 +1484,31 @@ defmodule Enum do
|
||||
The first position (after adding count to negative positions) must be smaller
|
||||
or equal to the last position.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
collection or if the range is in reverse order, returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5..10)
|
||||
[6, 7, 8, 9, 10, 11]
|
||||
|
||||
iex> Enum.slice(1..10, 5..20)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 11..20)
|
||||
[]
|
||||
|
||||
iex> Enum.slice(1..10, 6..5)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: list
|
||||
def slice(coll, first..last) when first >= 0 and last >= 0 do
|
||||
# Simple case, which works on infinite collections
|
||||
if last - first >= 0 do
|
||||
slice(coll, first, last - first + 1)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1489,6 +1519,8 @@ defmodule Enum do
|
||||
length = corr_last - corr_first + 1
|
||||
if corr_first >= 0 and length > 0 do
|
||||
slice(list, corr_first, length)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1545,6 +1577,46 @@ defmodule Enum do
|
||||
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the mapped results of the `collection` according to the `sorter` function.
|
||||
|
||||
This function maps each element of the collection using the `mapper`
|
||||
function. The collection is then sorted by the mapped elements using the
|
||||
`sorter` function, which defaults to `<=/2`
|
||||
|
||||
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
|
||||
value for each element in the collection once instead of once for each
|
||||
element in each comparison. If the same function is being called on both
|
||||
element, it's also more compact to use `sort_by/3`.
|
||||
|
||||
This technique is also known as a
|
||||
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
|
||||
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
|
||||
original `collection`, then `sorter` is sorting the decorations, and finally
|
||||
the `collection` is being undecorated so only the original elements remain,
|
||||
but now in sorted order.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the default `sorter` of `<=/2`:
|
||||
|
||||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
|
||||
["of", "some", "kind", "monster"]
|
||||
|
||||
Using a custom `sorter` to override the order:
|
||||
|
||||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
|
||||
["monster", "some", "kind", "of"]
|
||||
|
||||
"""
|
||||
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
|
||||
def sort_by(collection, mapper, sorter \\ &<=/2) do
|
||||
collection
|
||||
|> map(&{&1, mapper.(&1)})
|
||||
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|
||||
|> map(&elem(&1, 0))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the enumerable into two collections, leaving `count`
|
||||
elements in the first one. If `count` is a negative number,
|
||||
@@ -1726,27 +1798,6 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
@@ -1816,7 +1867,7 @@ defmodule Enum do
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, to_string: 2}
|
||||
@compile {:inline, enum_to_string: 1}
|
||||
|
||||
defp enumerate_and_count(collection, count) when is_list(collection) do
|
||||
{collection, length(collection) - abs(count)}
|
||||
@@ -1826,12 +1877,8 @@ defmodule Enum do
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
case mapper.(entry) do
|
||||
x when is_binary(x) -> x
|
||||
o -> String.Chars.to_string(o)
|
||||
end
|
||||
end
|
||||
defp enum_to_string(entry) when is_binary(entry), do: entry
|
||||
defp enum_to_string(entry), do: String.Chars.to_string(entry)
|
||||
|
||||
## Implementations
|
||||
|
||||
@@ -2142,12 +2189,12 @@ defmodule Enum do
|
||||
|
||||
## slice
|
||||
|
||||
defp do_slice([], start, _count) do
|
||||
if start == 0, do: []
|
||||
defp do_slice([], _start, _count) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice(list, start, 0) do
|
||||
if start < length(list), do: []
|
||||
defp do_slice(_list, _start, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice([h|t], 0, count) do
|
||||
@@ -2195,7 +2242,7 @@ defimpl Enumerable, for: Map do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
def reduce(function, acc, fun) when is_function(function, 2),
|
||||
do: function.(acc, fun)
|
||||
def member?(_function, _value),
|
||||
do: {:error, __MODULE__}
|
||||
|
||||
+36
-13
@@ -5,16 +5,16 @@ defmodule Exception do
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
last throw/error/exit that occurred in the current process.
|
||||
|
||||
Do not rely on the particular format returned by the `format`
|
||||
functions in this module. They may be changed in future releases
|
||||
in order to better suit Elixir's tool chain. In other words,
|
||||
by using the functions in this module it is guarantee you will
|
||||
by using the functions in this module it is guaranteed you will
|
||||
format exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@typedoc "The exception type (as generated by defexception)"
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@@ -482,6 +482,7 @@ end
|
||||
defmodule RuntimeError do
|
||||
defexception message: "runtime error"
|
||||
|
||||
@spec exception(String.t) :: Exception.t
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%RuntimeError{message: msg}
|
||||
end
|
||||
@@ -494,6 +495,7 @@ end
|
||||
defmodule ArgumentError do
|
||||
defexception message: "argument error"
|
||||
|
||||
@spec exception(String.t) :: Exception.t
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%ArgumentError{message: msg}
|
||||
end
|
||||
@@ -578,6 +580,14 @@ defmodule CaseClauseError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CondClauseError do
|
||||
defexception []
|
||||
|
||||
def message(_exception) do
|
||||
"no cond clause evaluated to a true value"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TryClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -603,12 +613,17 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
defexception [module: nil, function: nil, arity: nil, self: false]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
def message(%{function: function, module: module, arity: arity, self: self}) do
|
||||
if function do
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
suffix = if self or is_nil(module) or :code.is_loaded(module) do
|
||||
""
|
||||
else
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
"undefined function: #{formatted}" <> suffix
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
@@ -632,7 +647,7 @@ defmodule Code.LoadError do
|
||||
defexception [:file, :message]
|
||||
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
file = Keyword.fetch!(opts, :file)
|
||||
%Code.LoadError{message: "could not load #{file}", file: file}
|
||||
end
|
||||
end
|
||||
@@ -663,8 +678,8 @@ defmodule UnicodeConversionError do
|
||||
|
||||
def exception(opts) do
|
||||
%UnicodeConversionError{
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
encoded: Keyword.fetch!(opts, :encoded),
|
||||
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
|
||||
}
|
||||
end
|
||||
|
||||
@@ -733,6 +748,10 @@ defmodule ErlangError do
|
||||
%SystemLimitError{}
|
||||
end
|
||||
|
||||
def normalize(:cond_clause, _stacktrace) do
|
||||
%CondClauseError{}
|
||||
end
|
||||
|
||||
def normalize({:badarity, {fun, args}}, _stacktrace) do
|
||||
%BadArityError{function: fun, args: args}
|
||||
end
|
||||
@@ -758,8 +777,9 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
stacktrace = stacktrace || :erlang.get_stacktrace
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
@@ -786,4 +806,7 @@ defmodule ErlangError do
|
||||
defp from_stacktrace(_) do
|
||||
{nil, nil, nil}
|
||||
end
|
||||
|
||||
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
|
||||
defp from_self(_), do: false
|
||||
end
|
||||
|
||||
+183
-249
@@ -1,144 +1,3 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - Size of file in bytes.
|
||||
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
|
||||
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
|
||||
the file.
|
||||
* `atime` - The last time the file was read.
|
||||
* `mtime` - The last time the file was written.
|
||||
* `ctime` - The interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was
|
||||
changed. In Windows, it is the create time.
|
||||
* `mode` - The file permissions.
|
||||
* `links` - The number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
* `major_device` - Identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows:
|
||||
0 means A:, 1 means B:, and so on.
|
||||
* `minor_device` - Only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
* `inode` - Gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
* `uid` - Indicates the owner of the file.
|
||||
* `gid` - Gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
@@ -211,12 +70,17 @@ defmodule File do
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
alias :filelib, as: FL
|
||||
alias :file, as: F
|
||||
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@type stat_options :: [time: :local | :universal | :posix]
|
||||
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
|
||||
:raw | :read | :read_ahead | :sync | :write |
|
||||
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
|
||||
{:utf16, :big | :little} | {:utf32, :big | :little}} |
|
||||
{:read_ahead, pos_integer} |
|
||||
{:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
Returns `true` if the path is a regular file.
|
||||
@@ -228,7 +92,7 @@ defmodule File do
|
||||
"""
|
||||
@spec regular?(Path.t) :: boolean
|
||||
def regular?(path) do
|
||||
FL.is_regular(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,7 +100,7 @@ defmodule File do
|
||||
"""
|
||||
@spec dir?(Path.t) :: boolean
|
||||
def dir?(path) do
|
||||
FL.is_dir(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,12 +131,13 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :eexist - There is already a file or directory named `path`.
|
||||
* :enoent - A component of `path` does not exist.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:eexist` - there is already a file or directory named `path`
|
||||
* `:enoent` - a component of `path` does not exist
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
@@ -298,13 +163,38 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory
|
||||
"""
|
||||
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir_p(path) do
|
||||
FL.ensure_dir(Path.join(path, "."))
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defp do_mkdir_p("/") do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
if dir?(path) do
|
||||
:ok
|
||||
else
|
||||
parent = Path.dirname(path)
|
||||
if parent == path do
|
||||
# Protect against infinite loop
|
||||
{:error, :einval}
|
||||
else
|
||||
_ = do_mkdir_p(parent)
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} = error ->
|
||||
if dir?(path), do: :ok, else: error
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,13 +216,13 @@ defmodule File do
|
||||
|
||||
Typical error reasons:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for reading the file,
|
||||
or for searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* :enomem - There is not enough memory for the contents of the file.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for reading the file,
|
||||
or for searching one of the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
* `:enomem` - there is not enough memory for the contents of the file
|
||||
|
||||
You can use `:file.format_error/1` to get a descriptive string of the error.
|
||||
"""
|
||||
@@ -359,15 +249,14 @@ defmodule File do
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{:ok, info}` tuple, where info is a
|
||||
`File.Stat` record. Returns `{:error, reason}` with
|
||||
`File.Stat` struct. Returns `{:error, reason}` with
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
|
||||
The accepted options are:
|
||||
|
||||
* `:time` if the time should be `:local`, `:universal` or `:posix`.
|
||||
Default is `:local`.
|
||||
* `:time` - `:local | :universal | :posix`; default: `:local`
|
||||
|
||||
"""
|
||||
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
|
||||
@@ -425,11 +314,15 @@ defmodule File do
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.change_time(path, time) do
|
||||
{:error, :enoent} ->
|
||||
write(path, "")
|
||||
F.change_time(path, time)
|
||||
other ->
|
||||
other
|
||||
{:error, :enoent} -> touch_new(path, time)
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp touch_new(path, time) do
|
||||
case write(path, "", [:append]) do
|
||||
:ok -> F.change_time(path, time)
|
||||
{:error, _reason} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -622,7 +515,7 @@ defmodule File do
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case :elixir_utils.file_type(src) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{:ok, :symlink} ->
|
||||
@@ -665,7 +558,8 @@ defmodule File do
|
||||
[dest|acc]
|
||||
{:error, :eexist} ->
|
||||
if callback.(src, dest) do
|
||||
rm(dest)
|
||||
# If rm/1 fails, copy/2 will fail
|
||||
_ = rm(dest)
|
||||
case copy(src, dest) do
|
||||
{:ok, _} ->
|
||||
copy_file_mode!(src, dest)
|
||||
@@ -686,7 +580,8 @@ defmodule File do
|
||||
[dest|acc]
|
||||
{:error, :eexist} ->
|
||||
if callback.(src, dest) do
|
||||
rm(dest)
|
||||
# If rm/1 fails, iF.make_symlink/2 will fail
|
||||
_ = rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
{:error, reason} -> {:error, reason, src}
|
||||
@@ -713,25 +608,25 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - A component of the file name does not exist.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* `:enoent` - a component of the file name does not exist
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:eacces` - missing permission for writing the file or searching one of
|
||||
the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
@@ -743,16 +638,18 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Tries to delete the file `path`.
|
||||
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
Note the file is deleted even if in read-only mode.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for the file or one of its parents.
|
||||
* :eperm - The file is a directory and user is not super-user.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :einval - Filename had an improper type, such as tuple.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for the file or one of its parents
|
||||
* `:eperm` - the file is a directory and user is not super-user
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:einval` - filename had an improper type, such as tuple
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -765,7 +662,33 @@ defmodule File do
|
||||
"""
|
||||
@spec rm(Path.t) :: :ok | {:error, posix}
|
||||
def rm(path) do
|
||||
F.delete(IO.chardata_to_string(path))
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.delete(path) do
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, :eacces} = e ->
|
||||
change_mode_windows(path) || e
|
||||
{:error, _} = e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
defp change_mode_windows(path) do
|
||||
if match? {:win32, _}, :os.type do
|
||||
case F.read_file_info(IO.chardata_to_string(path)) do
|
||||
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
||||
change_mode_windows(path, file_info)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp change_mode_windows(path, file_info) do
|
||||
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
|
||||
:ok -> F.delete(path)
|
||||
{:error, _reason} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -886,9 +809,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp safe_list_dir(path) do
|
||||
case :elixir_utils.file_type(path) do
|
||||
case :elixir_utils.read_link_type(path) do
|
||||
{:ok, :symlink} ->
|
||||
case :elixir_utils.file_type(path, :read_file_info) do
|
||||
case :elixir_utils.read_file_type(path) do
|
||||
{:ok, :directory} -> {:ok, :directory}
|
||||
_ -> {:ok, :regular}
|
||||
end
|
||||
@@ -928,43 +851,55 @@ defmodule File do
|
||||
|
||||
The allowed modes:
|
||||
|
||||
* `:read` - The file, which must exist, is opened for reading.
|
||||
* `:read` - the file, which must exist, is opened for reading.
|
||||
|
||||
* `:write` - The file is opened for writing. It is created if it does not exist.
|
||||
If the file exists, and if write is not combined with read, the file will be truncated.
|
||||
* `:write` - the file is opened for writing. It is created if it does not
|
||||
exist.
|
||||
|
||||
* `:append` - The file will be opened for writing, and it will be created if it does not exist.
|
||||
Every write operation to a file opened with append will take place at the end of the file.
|
||||
If the file does exists, and if write is not combined with read, the file
|
||||
will be truncated.
|
||||
|
||||
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
|
||||
If the file exists, open will return `{:error, :eexist}`.
|
||||
* `:append` - the file will be opened for writing, and it will be created
|
||||
if it does not exist. Every write operation to a file opened with append
|
||||
will take place at the end of the file.
|
||||
|
||||
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
* `:exclusive` - the file, when opened for writing, is created if it does
|
||||
not exist. If the file exists, open will return `{:error, :eexist}`.
|
||||
|
||||
* `:compressed` - Makes it possible to read or write gzip compressed files.
|
||||
The compressed option must be combined with either read or write, but not both.
|
||||
Note that the file size obtained with `stat/1` will most probably not
|
||||
match the number of bytes that can be read from a compressed file.
|
||||
* `:char_list` - when this term is given, read operations on the file will
|
||||
return char lists rather than binaries.
|
||||
|
||||
* `:utf8` - This option denotes how data is actually stored in the disk file and
|
||||
makes the file perform automatic translation of characters to and from utf-8.
|
||||
If data is sent to a file in a format that cannot be converted to the utf-8
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
* `:compressed` - makes it possible to read or write gzip compressed files.
|
||||
|
||||
The compressed option must be combined with either read or write, but not
|
||||
both. Note that the file size obtained with `stat/1` will most probably
|
||||
not match the number of bytes that can be read from a compressed file.
|
||||
|
||||
* `:utf8` - this option denotes how data is actually stored in the disk
|
||||
file and makes the file perform automatic translation of characters to
|
||||
and from utf-8.
|
||||
|
||||
If data is sent to a file in a format that cannot be converted to the
|
||||
utf-8 or if data is read by a function that returns data in a format that
|
||||
cannot cope with the character range of the data, an error occurs and the
|
||||
file will be closed.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `:read_ahead` and `:delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
* `{:ok, io_device}` - The file has been opened in the requested mode.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the `io_device` is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An `io_device` returned
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
* `{:ok, io_device}` - the file has been opened in the requested mode.
|
||||
|
||||
* `{:error, reason}` - The file could not be opened.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the `io_device` is linked terminates, the file
|
||||
will be closed and the process itself will be terminated.
|
||||
|
||||
An `io_device` returned from this call can be used as an argument to the
|
||||
`IO` module functions.
|
||||
|
||||
* `{:error, reason}` - the file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -973,7 +908,8 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
@@ -989,14 +925,15 @@ defmodule File do
|
||||
|
||||
The file is opened, given to the function as argument and
|
||||
automatically closed after the function returns, regardless
|
||||
if there was an error or not.
|
||||
if there was an error when executing the function.
|
||||
|
||||
It returns `{:ok, function_result}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
This function expects the file to be closed with success,
|
||||
which is usually the case unless the `:delayed_write` option
|
||||
is given. For this reason, we do not recommend passing
|
||||
`:delayed_write` to this function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1005,7 +942,7 @@ defmodule File do
|
||||
end)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes, function) do
|
||||
case open(path, modes) do
|
||||
{:ok, device} ->
|
||||
@@ -1020,9 +957,10 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@spec open!(Path.t, [mode]) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
@@ -1034,9 +972,10 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/3` but raises an error if file could not be opened.
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
@@ -1057,18 +996,27 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
The same as `cwd/0`, but raises an exception if it fails.
|
||||
"""
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
case cwd() do
|
||||
{:ok, cwd} -> cwd
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1184,21 +1132,7 @@ defmodule File do
|
||||
"""
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
|
||||
raw: raw, line_or_bytes: line_or_bytes}
|
||||
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1206,7 +1140,7 @@ defmodule File do
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
@@ -1214,7 +1148,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
@@ -1229,7 +1163,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
@@ -1237,7 +1171,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
@@ -1252,7 +1186,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
@@ -1260,7 +1194,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - size of file in bytes.
|
||||
|
||||
* `type` - `:device | :directory | :regular | :other`; the type of the
|
||||
file.
|
||||
|
||||
* `access` - `:read | :write | :read_write | :none`; the current system
|
||||
access to the file.
|
||||
|
||||
* `atime` - the last time the file was read.
|
||||
|
||||
* `mtime` - the last time the file was written.
|
||||
|
||||
* `ctime` - the interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was changed.
|
||||
In Windows, it is the time of creation.
|
||||
|
||||
* `mode` - the file permissions.
|
||||
|
||||
* `links` - the number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
|
||||
* `major_device` - identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows: 0 means A:, 1 means
|
||||
B:, and so on.
|
||||
|
||||
* `minor_device` - only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,102 @@
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/3`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc false
|
||||
def __build__(path, modes, line_or_bytes) do
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
:ok = :file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
:ok = :file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
+59
-35
@@ -1,3 +1,5 @@
|
||||
import Kernel, except: [round: 1]
|
||||
|
||||
defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating point numbers.
|
||||
@@ -99,57 +101,71 @@ defmodule Float do
|
||||
@doc """
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
Floor also accepts a precision to round a floating point value down
|
||||
to an arbitrary number of fractional digits (between 0 and 15).
|
||||
|
||||
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
34.0
|
||||
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
-57.0
|
||||
|
||||
iex> Float.floor(34.253, 2)
|
||||
34.25
|
||||
|
||||
"""
|
||||
@spec floor(float | integer) :: integer
|
||||
def floor(num) when is_integer(num), do: num
|
||||
def floor(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num < 0 -> truncated - 1
|
||||
_ -> truncated
|
||||
end
|
||||
@spec floor(float, 0..15) :: float
|
||||
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
number = number * power
|
||||
truncated = trunc(number)
|
||||
variance = if number - truncated < 0, do: -1.0, else: 0.0
|
||||
(truncated + variance) / power
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
|
||||
Ceil also accepts a precision to round a floating point value down to
|
||||
an arbitrary number of fractional digits (between 0 and 15).
|
||||
|
||||
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
35.0
|
||||
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
-56.0
|
||||
|
||||
iex> Float.ceil(34.253, 2)
|
||||
34.26
|
||||
|
||||
"""
|
||||
@spec ceil(float | integer) :: integer
|
||||
def ceil(num) when is_integer(num), do: num
|
||||
def ceil(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num > 0 -> truncated + 1
|
||||
_ -> truncated
|
||||
end
|
||||
@spec ceil(float, 0..15) :: float
|
||||
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
number = number * power
|
||||
truncated = trunc(number)
|
||||
variance = if number - truncated > 0, do: 1.0, else: 0.0
|
||||
(truncated + variance) / power
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a floating point value to an arbitrary number of fractional digits
|
||||
(between 0 and 15).
|
||||
|
||||
This function only accepts floats and returns floats. Use `Kernel.round/1`
|
||||
if you want a function that accepts both floats and integers and always
|
||||
returns an integer.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
@@ -165,9 +181,15 @@ defmodule Float do
|
||||
-5.568
|
||||
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
@spec round(float, 0..15) :: float
|
||||
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
Kernel.round(number * power) / power
|
||||
end
|
||||
|
||||
Enum.reduce 0..15, 1, fn x, acc ->
|
||||
defp power_of_10(unquote(x)), do: unquote(acc)
|
||||
acc * 10
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -192,9 +214,10 @@ defmodule Float do
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -230,9 +253,10 @@ defmodule Float do
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+726
-257
File diff suppressed because it is too large
Load Diff
@@ -1,45 +0,0 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
handle_event: 2,
|
||||
handle_call: 2, handle_info: 2,
|
||||
terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,168 @@
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
|
||||
"""
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, _state) do
|
||||
exit({:bad_event, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _state) do
|
||||
exit({:bad_call, msg})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(),
|
||||
{:add_process_handler, self(), self()}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
|
||||
# Got a sync event.
|
||||
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
|
||||
{[{:sync, pid, ref, event}], acc}
|
||||
|
||||
# Got an ack event.
|
||||
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
|
||||
{[{:ack, pid, ref, event}], acc}
|
||||
|
||||
# The handler was removed. Stop iteration, resolve the
|
||||
# event later. We need to demonitor now, otherwise DOWN
|
||||
# appears with higher priority in the shutdown process.
|
||||
{:gen_event_EXIT, {^pid, ^ref}, _reason} = event ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# The manager died. Stop iteration, resolve the event later.
|
||||
{:DOWN, ^mon_ref, _, _, _} = event ->
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, we know the handler was already
|
||||
# removed, so we don't trigger a request for doing so.
|
||||
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, ref, _} = acc) do
|
||||
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
|
||||
stop(stream, {:removed, acc})
|
||||
end
|
||||
|
||||
defp wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
receive do
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason}
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
+132
-76
@@ -11,7 +11,7 @@ defmodule GenServer do
|
||||
## Example
|
||||
|
||||
The GenServer behaviour abstracts the common client-server interaction.
|
||||
Developer are only required to implement the callbacks and functionality they are
|
||||
Developers are only required to implement the callbacks and functionality they are
|
||||
interested in.
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
@@ -45,14 +45,14 @@ defmodule GenServer do
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
We start our `Stack` by calling `start_link/2`, passing the module
|
||||
We start our `Stack` by calling `start_link/3`, passing the module
|
||||
with the server implementation and its initial argument (a list
|
||||
representing the stack containing the item `:hello`). We can primarily
|
||||
interact with the server by sending two types of messages. **call**
|
||||
messages expect a reply from the server (and is therefore synchronous)
|
||||
messages expect a reply from the server (and are therefore synchronous)
|
||||
while **cast** messages do not.
|
||||
|
||||
Every time you do a `GenServer.call/2`, the client will send a message
|
||||
Every time you do a `GenServer.call/3`, the client will send a message
|
||||
that must be handled by the `handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `handle_cast/2`.
|
||||
|
||||
@@ -63,64 +63,64 @@ defmodule GenServer do
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started
|
||||
* `init(args)` - invoked when the server is started.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, state}
|
||||
{:ok, state, timeout}
|
||||
:ignore
|
||||
{:stop, reason}
|
||||
- `{:ok, state}`
|
||||
- `{:ok, state, timeout}`
|
||||
- `:ignore`
|
||||
- `{:stop, reason}`
|
||||
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
- `{:reply, reply, new_state}`
|
||||
- `{:reply, reply, new_state, timeout}`
|
||||
- `{:reply, reply, new_state, :hibernate}`
|
||||
- `{:noreply, new_state}`
|
||||
- `{:noreply, new_state, timeout}`
|
||||
- `{:noreply, new_state, :hibernate}`
|
||||
- `{:stop, reason, new_state}`
|
||||
- `{:stop, reason, reply, new_state}`
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, state, timeout}
|
||||
{:stop, reason, state}
|
||||
- `{:noreply, state}`
|
||||
- `{:noreply, state, timeout}`
|
||||
- `{:stop, reason, state}`
|
||||
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap).
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swapping).
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
{:error, reason}
|
||||
- `{:ok, new_state}`
|
||||
- `{:error, reason}`
|
||||
|
||||
## Names registering
|
||||
## Name Registration
|
||||
|
||||
Both `start_link/3` and `start/3` support the `GenServer` to register
|
||||
a name on start via the `:name` option. Registered names are also
|
||||
automatically clean up on termination. The supported values are:
|
||||
automatically cleaned up on termination. The supported values are:
|
||||
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`;
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`.
|
||||
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module;
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module.
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism along side a name which can be any term;
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism alongside a name which can be any term.
|
||||
|
||||
For example, we could start and register our Stack server locally as follows:
|
||||
|
||||
@@ -130,22 +130,23 @@ defmodule GenServer do
|
||||
# Now messages can be sent directly to MyStack
|
||||
GenServer.call(MyStack, :pop) #=> :hello
|
||||
|
||||
Once the server is started, the remaining functions in this module (`call/2`,
|
||||
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
|
||||
Once the server is started, the remaining functions in this module (`call/3`,
|
||||
`cast/2`, and friends) will also accept an atom, or any `:global` or `:via`
|
||||
tuples. In general, the following formats are supported:
|
||||
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative registry
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative
|
||||
registry
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
Although in the example above we have used `GenServer.start_link/2` and
|
||||
Although in the example above we have used `GenServer.start_link/3` and
|
||||
friends to directly start and communicate with the server, most of the
|
||||
time we don't call the `GenServer` functions directly, instead, we wrap
|
||||
them in functions too.
|
||||
time we don't call the `GenServer` functions directly. Instead, we wrap
|
||||
the calls in new functions representing the public API of the server.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
@@ -192,14 +193,14 @@ defmodule GenServer do
|
||||
|
||||
## Learn more
|
||||
|
||||
In case you desire to learn more about gen servers, Elixir getting started
|
||||
If you wish to find out more about gen servers, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@@ -269,27 +270,32 @@ defmodule GenServer do
|
||||
passing the given `args` to initialize it. To ensure a synchronized start-up
|
||||
procedure, this function does not return until `init/1` has returned.
|
||||
|
||||
Note that a `GenServer` started with `start_link/3` is linked to the
|
||||
parent process and will exit in case of crashes. The GenServer will also
|
||||
exit due to the `:normal` reasons in case it is configured to trap exits
|
||||
in the `init/1` callback.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for name registered as described in the module
|
||||
The `:name` option is used for name registration as described in the module
|
||||
documentation. If the option `:timeout` option is present, the server is
|
||||
allowed to spend the given milliseconds initializing or it will be
|
||||
terminated and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the option `:debug` is present, the corresponding function in the
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the option `:spawn_opt` is present, the given options will be passed
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized the function returns
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
|
||||
process with the specified server name the function returns
|
||||
process with the specified server name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the `init/1` callback fails with reason, the function returns
|
||||
If the `init/1` callback fails with `reason`, the function returns
|
||||
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
|
||||
or `:ignore`, the process is terminated and the function returns
|
||||
`{:error, reason}` or `:ignore`, respectively.
|
||||
@@ -321,13 +327,13 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the `server` and wait for its reply.
|
||||
Makes a synchronous call to the `server` and waits for its reply.
|
||||
|
||||
The client sends the given `request` to the server and waits until a reply
|
||||
arrives or a timeout occurs. `handle_call/3` will be called on the server
|
||||
to handle the request.
|
||||
|
||||
The server can be any of the values described in the `Name Registering`
|
||||
The server can be any of the values described in the `Name Registration`
|
||||
section of the module documentation.
|
||||
|
||||
## Timeouts
|
||||
@@ -343,18 +349,57 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
try do
|
||||
:gen.call(server, :"$gen_call", request, timeout)
|
||||
catch
|
||||
:exit, reason ->
|
||||
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
|
||||
else
|
||||
{:ok, res} -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, ignoring if the destination node
|
||||
or server does not exist. `handle_cast/2` will be called on the server
|
||||
to handle the request.
|
||||
This function returns `:ok` immediately, regardless of
|
||||
whether the destination node or server does exists, unless
|
||||
the server is specified as an atom.
|
||||
|
||||
`handle_cast/2` will be called on the server to handle
|
||||
the request. In case the server is a node which is not
|
||||
yet connected to the caller one, the call is going to
|
||||
block until a connection happens. This is different than
|
||||
the behaviour in OTP's `:gen_server` where the message
|
||||
would be sent by another process, which could cause
|
||||
messages to arrive out of order.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
def cast(server, request)
|
||||
|
||||
def cast({:global, name}, request) do
|
||||
try do
|
||||
:global.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({:via, mod, name}, request) do
|
||||
try do
|
||||
mod.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -365,8 +410,19 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
defp cast_msg(req) do
|
||||
{:"$gen_cast", req}
|
||||
end
|
||||
|
||||
defp do_send(dest, msg) do
|
||||
send(dest, msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -392,7 +448,7 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replies a client.
|
||||
Replies to a client.
|
||||
|
||||
This function can be used by a server to explicitly send a reply to a
|
||||
client that called `call/3` or `multi_call/4`. When the reply cannot be
|
||||
@@ -402,7 +458,7 @@ defmodule GenServer do
|
||||
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
|
||||
given back to the client as the return value of the call.
|
||||
|
||||
This function always return `:ok`.
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec reply({pid, reference}, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(request, _from, state) do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -14,14 +14,15 @@ defmodule HashDict do
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -85,10 +86,11 @@ defmodule HashDict do
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
@doc false
|
||||
def dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -232,7 +234,14 @@ defimpl Enumerable, for: HashDict do
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
def get(dict, key) do
|
||||
HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) do
|
||||
{get, update} = fun.(HashDict.get(dict, key, nil))
|
||||
{get, HashDict.put(dict, key, update)}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
@@ -248,3 +257,11 @@ defimpl Collectable, for: HashDict do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,8 @@ defmodule HashSet do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -264,3 +265,11 @@ defimpl Collectable, for: HashSet do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+43
-108
@@ -8,7 +8,7 @@ defprotocol Inspect do
|
||||
formatted, either in pretty printing format or a regular one.
|
||||
|
||||
The `inspect/2` function receives the entity to be inspected
|
||||
followed by the inspecting options, represented by the record
|
||||
followed by the inspecting options, represented by the struct
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
@@ -210,33 +210,30 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
|
||||
@doc false
|
||||
# also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
def escape_char(char), do: hexify(char)
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
# Also used by Regex
|
||||
def escape_char(0) do
|
||||
<<?\\, ?0>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x10000 do
|
||||
def escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::size(8)>>
|
||||
<<?\\, ?x, to_hex(a), to_hex(b)>>
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x1000000 do
|
||||
def escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?a+c-10
|
||||
|
||||
defp to_hex(c) when c in 10..15, do: ?A+c-10
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
@@ -274,25 +271,6 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
printed in keywords syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
@@ -300,9 +278,9 @@ defimpl Inspect, for: List do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &keyword/2)
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -349,75 +327,25 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = Tuple.to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
surround_record(name, fields, tail, opts)
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
case Atom.to_string(name) do
|
||||
"Elixir." <> _ ->
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case Atom.to_string(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
defp zip_fields([], []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({k, v}, opts) do
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
nest inspect(map, "", opts), 1
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
defp traverse_fun(list) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
&Inspect.List.keyword/2
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
&to_map/2
|
||||
end
|
||||
end
|
||||
|
||||
@@ -430,8 +358,28 @@ defimpl Inspect, for: Map do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(thing, _opts) do
|
||||
Integer.to_string(thing)
|
||||
def inspect(thing, %Inspect.Opts{base: base}) do
|
||||
Integer.to_string(thing, base_to_value(base))
|
||||
|> prepend_prefix(base)
|
||||
end
|
||||
|
||||
defp base_to_value(base) do
|
||||
case base do
|
||||
:binary -> 2
|
||||
:decimal -> 10
|
||||
:octal -> 8
|
||||
:hex -> 16
|
||||
end
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, :decimal), do: value
|
||||
defp prepend_prefix(value, base) do
|
||||
prefix = case base do
|
||||
:binary -> "0b"
|
||||
:octal -> "0o"
|
||||
:hex -> "0x"
|
||||
end
|
||||
prefix <> value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -443,13 +391,12 @@ end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, _opts) do
|
||||
delim =?/
|
||||
delim = ?/
|
||||
concat ["~r",
|
||||
<<delim, escape(regex.source, delim)::binary, delim>>,
|
||||
regex.opts]
|
||||
end
|
||||
|
||||
|
||||
defp escape(bin, term),
|
||||
do: escape(bin, <<>>, term)
|
||||
|
||||
@@ -575,15 +522,3 @@ defimpl Inspect, for: Any do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+157
-137
@@ -4,36 +4,55 @@ defmodule Inspect.Opts do
|
||||
|
||||
The following fields are available:
|
||||
|
||||
* `:structs` - when false, structs are not formatted by the inspect protocol,
|
||||
they are instead printed as maps, defaults to true;
|
||||
* `:structs` - when false, structs are not formatted by the inspect
|
||||
protocol, they are instead printed as maps, defaults to true.
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
binaries will be printed in bit syntax; when the default
|
||||
`:infer`, the binary will be printed as a string if it is
|
||||
printable, otherwise in bit syntax;
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped.
|
||||
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
|
||||
non-printable elements will be escaped; when `:as_lists` all
|
||||
lists will be printed as lists; when the default `:infer`, the
|
||||
list will be printed as a char list if it is printable,
|
||||
otherwise as list;
|
||||
When `:as_binaries` all binaries will be printed in bit syntax.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists, does not apply to strings nor char lists, defaults to 50;
|
||||
When the default `:infer`, the binary will be printed as a string if it
|
||||
is printable, otherwise in bit syntax.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false;
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char
|
||||
lists, non-printable elements will be escaped.
|
||||
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
When the default `:infer`, the list will be printed as a char list if it
|
||||
is printable, otherwise as list.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples,
|
||||
bitstrings, and lists, does not apply to strings nor char lists, defaults
|
||||
to 50.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false.
|
||||
|
||||
* `:width` - defaults to the 80 characters, used when pretty is true or
|
||||
when printing to IO devices.
|
||||
|
||||
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
|
||||
to :decimal
|
||||
|
||||
* `:width` - defaults to the 80 characters;
|
||||
"""
|
||||
|
||||
defstruct structs: true :: boolean,
|
||||
binaries: :infer :: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean,
|
||||
records: true :: boolean
|
||||
defstruct structs: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
width: 80,
|
||||
base: :decimal,
|
||||
pretty: false
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
structs: boolean,
|
||||
binaries: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer | :as_lists | :as_char_lists,
|
||||
limit: pos_integer,
|
||||
width: pos_integer | :infinity,
|
||||
base: :decimal | :binary | :hex | :octal,
|
||||
pretty: boolean}
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@@ -54,8 +73,8 @@ defmodule Inspect.Algebra do
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"foo"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["foo"]
|
||||
|
||||
The functions `nest/2`, `space/2` and `line/2` help you put the
|
||||
document together into a rigid structure. However, the document
|
||||
@@ -65,15 +84,15 @@ defmodule Inspect.Algebra do
|
||||
render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
Finally, this module also contains Elixir related functions, a bit
|
||||
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
|
||||
@@ -160,29 +179,32 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_tuple(arg) and is_atom(elem(arg, 0)) do
|
||||
# Remove this code when protocols+records are removed"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
_ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
# Because we try to raise a nice error message in case
|
||||
# we can't inspect a struct, there is a chance the error
|
||||
# message itself relies on the struct being printed, so
|
||||
# we need to trap the inspected messages to guarantee
|
||||
# we won't try to render any failed instruct when building
|
||||
# the error message.
|
||||
if Process.get(:inspect_trap) do
|
||||
Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
try do
|
||||
Process.put(:inspect_trap, true)
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
formatted = IO.iodata_to_binary(format(res, :infinity))
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
|
||||
after
|
||||
Process.delete(:inspect_trap)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
@@ -194,8 +216,7 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
Returns a document entity used to represent nothingness.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -207,14 +228,13 @@ defmodule Inspect.Algebra do
|
||||
def empty, do: :doc_nil
|
||||
|
||||
@doc """
|
||||
Concatenates two document entities. Takes two arguments:
|
||||
left doc and right doc. Returns a DocCons doc
|
||||
Concatenates two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"TastelessArtosis"
|
||||
iex> doc = Inspect.Algebra.concat "hello", "world"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["hello", "world"]
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons
|
||||
@@ -230,15 +250,16 @@ defmodule Inspect.Algebra do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
appended to the line breaks.
|
||||
@doc ~S"""
|
||||
Nests document entity `x` positions deep.
|
||||
|
||||
Nesting will be appended to the line breaks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
" 6"
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
|
||||
iex> Inspect.Algebra.format(doc, 5)
|
||||
["hello", "\n ", "world"]
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@@ -251,24 +272,26 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
Document entity representing a break.
|
||||
|
||||
This break can be rendered as a linebreak or as spaces,
|
||||
depending on the `mode` of the chosen layout or the provided
|
||||
separator.
|
||||
|
||||
## Examples
|
||||
|
||||
Let's glue two docs together with a break and then render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break
|
||||
@@ -311,10 +334,10 @@ defmodule Inspect.Algebra do
|
||||
...> "B"
|
||||
...> )
|
||||
...> ))
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hello, A B"
|
||||
iex> Inspect.Algebra.pretty(doc, 6)
|
||||
"Hello,\nA B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hello,", " ", "A", " ", "B"]
|
||||
iex> Inspect.Algebra.format(doc, 6)
|
||||
["Hello,", "\n", "A", " ", "B"]
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
@@ -328,8 +351,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes Wadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", " ", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons
|
||||
@@ -341,8 +364,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes\nWadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", "\n", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons
|
||||
@@ -358,8 +381,8 @@ defmodule Inspect.Algebra do
|
||||
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
|
||||
...> Inspect.Algebra.concat [x, "!", y]
|
||||
...> end)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"A!B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["A", "!", "B"]
|
||||
|
||||
"""
|
||||
@spec folddoc([t], ((t, t) -> t)) :: t
|
||||
@@ -379,8 +402,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
|
||||
iex> Inspect.Algebra.pretty(doc, 3)
|
||||
"[a\n b]"
|
||||
iex> Inspect.Algebra.format(doc, 3)
|
||||
["[", "a", "\n ", "b", "]"]
|
||||
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
@@ -389,55 +412,63 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
Maps and glues a collection of items.
|
||||
|
||||
It uses the given left and right as surrounding and a separator for
|
||||
each item. A limit can be passed which, once reached, stops gluing
|
||||
and outputs "..." instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
|
||||
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
|
||||
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
|
||||
end
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun, sep) do
|
||||
surround(left, surround_many(docs, limit, fun, sep), right)
|
||||
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun, _sep) do
|
||||
defp do_surround_many(_, 0, _opts, _fun, _sep) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun, _sep) do
|
||||
fun.(h)
|
||||
defp do_surround_many([h], limit, opts, fun, _sep) do
|
||||
fun.(h, %{opts | limit: limit})
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), sep),
|
||||
surround_many(t, decrement(limit), fun, sep)
|
||||
concat(fun.(h, %{opts | limit: limit}), sep),
|
||||
do_surround_many(t, limit, opts, fun, sep)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun, _sep) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, _sep) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
|
||||
fun.(t, %{opts | limit: limit})
|
||||
)
|
||||
end
|
||||
|
||||
@@ -445,50 +476,45 @@ defmodule Inspect.Algebra do
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
The pretty printing function.
|
||||
The formatting function.
|
||||
|
||||
Takes the maximum width and a document to print as its arguments
|
||||
and returns the string representation of the best layout for the
|
||||
and returns an IO data representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
|
||||
render(sdoc)
|
||||
@spec format(t, non_neg_integer | :infinity) :: iodata
|
||||
def format(d, w) do
|
||||
format(w, 0, [{0, default_mode(w), doc_group(d)}])
|
||||
end
|
||||
|
||||
defp default_mode(:infinity), do: :flat
|
||||
defp default_mode(_), do: :break
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
defp fits?(w, _) when w < 0, do: false
|
||||
defp fits?(_, []), do: true
|
||||
defp fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
defp format(_, _, []), do: []
|
||||
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
@@ -500,10 +526,4 @@ defmodule Inspect.Algebra do
|
||||
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
IO.iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,9 +9,10 @@ defmodule Integer do
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@@ -19,9 +20,10 @@ defmodule Integer do
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@@ -99,7 +101,7 @@ defmodule Integer do
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
@spec to_string(integer, 2..36) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
@@ -115,7 +117,7 @@ defmodule Integer do
|
||||
'7'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer) :: list
|
||||
@spec to_char_list(integer) :: char_list
|
||||
def to_char_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
@@ -132,7 +134,7 @@ defmodule Integer do
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: list
|
||||
@spec to_char_list(integer, 2..36) :: char_list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
|
||||
+87
-49
@@ -21,12 +21,12 @@ defmodule IO do
|
||||
the atom must be the name of a registered process. However,
|
||||
there are three exceptions for this rule:
|
||||
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
|
||||
"""
|
||||
|
||||
@@ -43,20 +43,29 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
Reads `count` characters from the IO device, a whole
|
||||
`:line` or the whole device with `:all`.
|
||||
|
||||
* `data` - The input characters.
|
||||
It returns:
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `data` - the input characters
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
|
||||
If `:all` is given, `:eof` is never returned, but an
|
||||
empty string in case the device has reached EOF.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
@spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :all) do
|
||||
do_read_all(map_dev(device), "")
|
||||
end
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
end
|
||||
@@ -65,21 +74,41 @@ defmodule IO do
|
||||
:io.get_chars(map_dev(device), '', count)
|
||||
end
|
||||
|
||||
defp do_read_all(mapped_dev, acc) do
|
||||
case :io.get_line(mapped_dev, "") do
|
||||
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
Reads `count` characters from the IO device, a whole
|
||||
`:line` or the whole device with `:all`.
|
||||
|
||||
* `data` - The input characters.
|
||||
It returns:
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `data` - the input characters
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
|
||||
If `:all` is given, `:eof` is never returned, but an
|
||||
empty string in case the device has reached EOF.
|
||||
|
||||
Note: do not use this function on IO devices in unicode mode
|
||||
as it will return the wrong result.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
@spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :all) do
|
||||
do_binread_all(map_dev(device), "")
|
||||
end
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{:ok, data} -> data
|
||||
@@ -94,6 +123,14 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_binread_all(mapped_dev, acc) do
|
||||
case :file.read_line(mapped_dev) do
|
||||
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
|
||||
@@ -119,6 +156,9 @@ defmodule IO do
|
||||
as a binary, no unicode conversion happens.
|
||||
|
||||
Check `write/2` for more information.
|
||||
|
||||
Note: do not use this function on IO devices in unicode mode
|
||||
as it will return the wrong result.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
||||
@@ -139,17 +179,13 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It sets by default pretty printing to true and returns
|
||||
the item itself.
|
||||
|
||||
Note this function does not use the IO device width
|
||||
because some IO devices does not implement the
|
||||
appropriate functions. Setting the width must be done
|
||||
explicitly by passing the `:width` option.
|
||||
It enables pretty printing by default with width of
|
||||
80 characters. The width can be changed by explicitly
|
||||
passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
IO.inspect Process.list, width: 40
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
@@ -162,8 +198,9 @@ defmodule IO do
|
||||
"""
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
opts = struct(Inspect.Opts, opts)
|
||||
iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
|
||||
puts device, iodata
|
||||
item
|
||||
end
|
||||
|
||||
@@ -174,13 +211,13 @@ defmodule IO do
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
@@ -208,14 +245,14 @@ defmodule IO do
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The characters in the line terminated
|
||||
by a LF (or end of file).
|
||||
* `data` - the characters in the line terminated
|
||||
by a LF (or end of file)
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
@@ -248,8 +285,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
|
||||
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -264,11 +300,13 @@ defmodule IO do
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
|
||||
Finally, do not use this function on IO devices in unicode
|
||||
mode as it will return the wrong result.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
|
||||
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -356,11 +394,11 @@ defmodule IO do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -368,11 +406,11 @@ defmodule IO do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+49
-68
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(unquote(Atom.to_char_list(name))) do
|
||||
defp format_sequence(unquote(name)) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@doc """
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
@typep ansicode :: atom()
|
||||
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
|
||||
@type ansidata :: ansilist() | ansicode() | binary()
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be displayed as intended. This is checked by sending a message to
|
||||
the group leader. In case the group leader does not support the message,
|
||||
it will likely lead to a timeout (and a slow down on execution time).
|
||||
@doc """
|
||||
Checks if ANSI coloring is supported and enabled on this machine.
|
||||
|
||||
This function simply reads the configuration value for
|
||||
`:ansi_enabled` in the `:elixir` application. The value is by
|
||||
default false unless Elixir can detect during startup that
|
||||
both `stdout` and `stderr` are terminals.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
!match?({:win32, _}, :os.type()) and
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
@spec enabled? :: boolean
|
||||
def enabled? do
|
||||
Application.get_env(:elixir, :ansi_enabled, false)
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
defp escape_sequence(other) do
|
||||
defp format_sequence(other) do
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
It will also append a `%{reset}` to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/2`.
|
||||
It will also append an `IO.ANSI.reset` to the chardata when a conversion is
|
||||
performed. If you don't want this behaviour, use `format_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
|
||||
[[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
def format(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, :maybe)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
|
||||
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, false)
|
||||
end
|
||||
|
||||
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
sequences =
|
||||
buffer
|
||||
|> Enum.reverse()
|
||||
|> :string.tokens(',')
|
||||
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|
||||
|> Enum.reverse()
|
||||
|
||||
if emit and sequences != [] do
|
||||
do_escape(t, emit, true, nil, sequences ++ acc)
|
||||
else
|
||||
do_escape(t, emit, emitted, nil, acc)
|
||||
end
|
||||
defp do_format([term | rest], rem, acc, emit, append_reset) do
|
||||
do_format(term, [rest | rem], acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
do_escape(t, emit, emitted, [h|buffer], acc)
|
||||
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
|
||||
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = IO.iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, "missing } for escape fragment #{buffer}"
|
||||
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
|
||||
do_format([], rem, acc, false, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, [], acc)
|
||||
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
|
||||
do_format([], rem, [acc | [term]], emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, nil, [h|acc])
|
||||
defp do_format([], [next | rest], acc, emit, append_reset) do
|
||||
do_format(next, rest, acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
defp do_format([], [], acc, true, true) do
|
||||
[acc | IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp do_format([], [], acc, _emit, _append_reset) do
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
+219
-116
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def default_options do
|
||||
[enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline",
|
||||
width: 80]
|
||||
[enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan, :bright],
|
||||
doc_headings: [:yellow, :bright],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow, :bright],
|
||||
doc_underline: [:underline],
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a set of options
|
||||
In addition to the printing string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
@@ -56,52 +59,63 @@ defmodule IO.ANSI.Docs do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", options)
|
||||
|> process([], "", options)
|
||||
end
|
||||
|
||||
defp process([], _indent, _options), do: nil
|
||||
defp process([], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, options) do
|
||||
defp process(["# " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h1(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, options) do
|
||||
defp process(["## " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h2(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, options) do
|
||||
defp process(["### " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h3(String.strip(heading), indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, options) do
|
||||
process(rest, indent, options)
|
||||
defp process(["" | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, options) do
|
||||
defp process([" " <> line | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process_code(rest, [line], indent, options)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, options)
|
||||
defp process(all=[line | rest], text, indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0, :infinity)
|
||||
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
|
||||
write_text(text, indent, options)
|
||||
process_table(all, indent, options)
|
||||
else
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list("• ", item, rest, count, indent, options)
|
||||
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
|
||||
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
|
||||
_ ->
|
||||
process(rest, [stripped | text], indent, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_spaces(" " <> line, acc) do
|
||||
strip_spaces(line, acc + 1)
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{rest, acc}
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, options) do
|
||||
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp write_h2(heading, options) do
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, options) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, options) do
|
||||
IO.write indent <> "• "
|
||||
{contents, rest, done} = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, options)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, options)
|
||||
defp process_list(entry, line, rest, count, indent, options) do
|
||||
# The first list always win some extra padding
|
||||
if indent == "", do: entry = " " <> entry
|
||||
new_indent = indent <> String.duplicate(" ", String.length(entry))
|
||||
|
||||
{contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
|
||||
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
|
||||
|
||||
if done, do: newline_after_block()
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
#
|
||||
# * Continuation of the list
|
||||
# * A nested list
|
||||
# * The end of the list
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> {Enum.reverse(acc), [line|rest], false}
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
defp process_list_next([line | rest], count, max, acc) do
|
||||
{stripped, next_count} = strip_spaces(line, 0, max)
|
||||
case process_list_next_kind(stripped, rest, count, next_count) do
|
||||
:next -> process_list_next(rest, count, max, [stripped | acc])
|
||||
:done -> {Enum.reverse(acc), [line | rest], true}
|
||||
:list -> {Enum.reverse(acc), [line | rest], false}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{Enum.reverse(acc), rest, false}
|
||||
defp process_list_next([], _count, _max, acc) do
|
||||
{Enum.reverse(acc), [], true}
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
process_list_next(rest, count, true, [""|acc])
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{Enum.reverse(acc), rest, done}
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
defp list_next(line, 0), do: chop(line, 2)
|
||||
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
|
||||
defp list_next(line, _acc), do: line
|
||||
|
||||
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
|
||||
defp chop(line, _acc), do: line
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
process(doc, indent, options)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, options)
|
||||
process_list(item, rest, count, indent, options)
|
||||
defp process_list_next_kind(stripped, rest, count, next_count) do
|
||||
case {stripped, rest} do
|
||||
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
|
||||
:list
|
||||
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{"", [" " <> _ | _]} ->
|
||||
:next
|
||||
{"", _} ->
|
||||
:done
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, options)
|
||||
:next
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, options) do
|
||||
process_text(rest, [line | para], indent, from_list, options)
|
||||
## Text
|
||||
|
||||
defp write_text(text, indent, options) do
|
||||
case Enum.reverse(text) do
|
||||
[:no_wrap|rest] -> write_text(rest, indent, options, true)
|
||||
rest -> write_text(rest, indent, options, false)
|
||||
end
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, options) do
|
||||
defp write_text([], _indent, _options, _no_wrap) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, options, no_wrap) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
unless no_wrap, do: newline_after_block()
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
@@ -204,28 +208,124 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, options) do
|
||||
defp process_code(["", " " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line, "" | code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, options) do
|
||||
defp process_code([" " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line|code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, options) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Tables
|
||||
|
||||
defp process_table(lines, indent, options) do
|
||||
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
|
||||
table_lines(table, options)
|
||||
newline_after_block
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp table_lines(lines, options) do
|
||||
lines = Enum.map(lines, &split_into_columns(&1, options))
|
||||
count = Enum.map(lines, &length/1) |> Enum.max
|
||||
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
|
||||
|
||||
widths = for line <- lines, do:
|
||||
(for {_col, length} <- line, do: length)
|
||||
|
||||
col_widths = Enum.reduce(widths,
|
||||
List.duplicate(0, count),
|
||||
&max_column_widths/2)
|
||||
|
||||
render_table(lines, col_widths, options)
|
||||
end
|
||||
|
||||
defp split_into_columns(line, options) do
|
||||
line
|
||||
|> String.strip(?|)
|
||||
|> String.strip()
|
||||
|> String.split(~r/\s\|\s/)
|
||||
|> Enum.map(&render_column(&1, options))
|
||||
end
|
||||
|
||||
defp render_column(col, options) do
|
||||
col = col
|
||||
|> String.replace(~r/\\ \|/x, "|")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
{col, length_without_escape(col, 0)}
|
||||
end
|
||||
|
||||
defp pad_to_number_of_columns(cols, col_count),
|
||||
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
|
||||
|
||||
defp max_column_widths(cols, widths),
|
||||
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
|
||||
|
||||
# If second line is heading separator, use the heading style on the first
|
||||
defp render_table([first, second | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
if table_header?(second) do
|
||||
draw_table_row(combined, options, :heading)
|
||||
render_table(rest, widths, options)
|
||||
else
|
||||
draw_table_row(combined, options)
|
||||
render_table([second | rest], widths, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_table([first | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
draw_table_row(combined, options)
|
||||
render_table(rest, widths, options)
|
||||
end
|
||||
|
||||
defp render_table([], _, _),
|
||||
do: nil
|
||||
|
||||
defp table_header?(row), do:
|
||||
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
|
||||
|
||||
defp draw_table_row(cols_and_widths, options, heading \\ false) do
|
||||
columns =
|
||||
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
|
||||
col <> String.duplicate(" ", width - length)
|
||||
end)
|
||||
|
||||
if heading do
|
||||
write(:doc_table_heading, columns, options)
|
||||
else
|
||||
IO.puts columns
|
||||
end
|
||||
end
|
||||
|
||||
defp is_table_line?(line) do
|
||||
Regex.match?(~r'''
|
||||
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
||||
|
|
||||
(\s \| \s)
|
||||
'''x, line)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max,
|
||||
do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max),
|
||||
do: {rest, acc}
|
||||
|
||||
defp write(style, string, options) do
|
||||
IO.puts color(style, options) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
IO.puts [color(style, options), string, IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
@@ -270,7 +370,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
true -> Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ -> text
|
||||
end
|
||||
end
|
||||
|
||||
@@ -363,14 +461,19 @@ defmodule IO.ANSI.Docs do
|
||||
[color_for(h, options)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
defp color_for(mark, colors) do
|
||||
case mark do
|
||||
"`" -> color(:doc_inline_code, colors)
|
||||
"_" -> color(:doc_underline, colors)
|
||||
"*" -> color(:doc_bold, colors)
|
||||
"**" -> color(:doc_bold, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
IO.ANSI.format_fragment(color, colors[:enabled])
|
||||
end
|
||||
|
||||
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
@@ -14,14 +14,21 @@ defmodule IO.Stream do
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc false
|
||||
def __build__(device, raw, line_or_bytes) do
|
||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
@@ -50,7 +57,7 @@ defmodule IO.Stream do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
|
||||
+621
-607
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
@blank_config %{commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false}
|
||||
pa: [], pz: [], verbose_compile: false}
|
||||
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
|
||||
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
||||
|
||||
{config, argv} = parse_argv(argv)
|
||||
:elixir_code_server.cast({:paths, config.pa, config.pz})
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
run fn _ ->
|
||||
errors = process_commands(config)
|
||||
|
||||
if errors != [] do
|
||||
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt \\ true) do
|
||||
try do
|
||||
fun.()
|
||||
if halt do
|
||||
at_exit(0)
|
||||
System.halt(0)
|
||||
end
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
at_exit(reason)
|
||||
System.halt(reason)
|
||||
:exit, :normal ->
|
||||
at_exit(0)
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
System.halt(1)
|
||||
res = exec_fun(fun, {:ok, 0})
|
||||
if elem(res, 0) == :shutdown or halt do
|
||||
{_, int} = at_exit(res)
|
||||
System.halt(int)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses ARGV returning the CLI config and trailing args.
|
||||
"""
|
||||
@doc false
|
||||
def parse_argv(argv) do
|
||||
parse_argv(argv, @blank_config)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Process commands according to the parsed config from `parse_argv/1`.
|
||||
Returns all errors.
|
||||
"""
|
||||
@doc false
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
defp at_exit(res) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
res = Enum.reduce(hooks, res, &exec_fun/2)
|
||||
if hooks == [], do: res, else: at_exit(res)
|
||||
end
|
||||
|
||||
for hook <- hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
catch
|
||||
kind, reason ->
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
spawn_monitor fn ->
|
||||
try do
|
||||
fun.(elem(res, 1))
|
||||
catch
|
||||
:exit, {:shutdown, int} when is_integer(int) ->
|
||||
send parent, {self, {:shutdown, int}}
|
||||
exit({:shutdown, int})
|
||||
:exit, reason
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
|
||||
send parent, {self, {:shutdown, 0}}
|
||||
exit(reason)
|
||||
kind, reason ->
|
||||
stack = System.stacktrace
|
||||
print_error(kind, reason, stack)
|
||||
send parent, {self, {:shutdown, 1}}
|
||||
:erlang.raise(kind, reason, stack)
|
||||
else
|
||||
_ ->
|
||||
send parent, {self, res}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If an at_exit callback adds a
|
||||
# new hook we need to invoke it.
|
||||
unless hooks == [], do: at_exit(status)
|
||||
receive do
|
||||
{^pid, res} ->
|
||||
:erlang.demonitor(ref, [:flush])
|
||||
res
|
||||
{:DOWN, ^ref, _, _, other} ->
|
||||
print_error({:EXIT, pid}, other, [])
|
||||
{:shutdown, 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
@@ -103,7 +113,8 @@ defmodule Kernel.CLI do
|
||||
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
@@ -129,17 +140,19 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
parse_shared t, config
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_patha/1)
|
||||
parse_shared t, %{config | pa: config.pa ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
parse_shared t, config
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_pathz/1)
|
||||
parse_shared t, %{config | pz: config.pz ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["--app", h|t], config) do
|
||||
parse_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp parse_shared(["--no-halt"|t], config) do
|
||||
@@ -162,7 +175,7 @@ defmodule Kernel.CLI do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
@@ -170,6 +183,14 @@ defmodule Kernel.CLI do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
defp expand_code_path(path) do
|
||||
path = Path.expand(path)
|
||||
case Path.wildcard(path) do
|
||||
[] -> [to_char_list(path)]
|
||||
list -> Enum.map(list, &to_char_list/1)
|
||||
end
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp parse_argv(["--"|t], config) do
|
||||
@@ -240,7 +261,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
@@ -315,7 +336,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
if File.regular?(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{:error, "No file named #{file}"}
|
||||
@@ -323,9 +344,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
@@ -335,9 +354,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
@@ -347,9 +364,10 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
# If ensuring the dir returns an error no files will be found.
|
||||
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case match_regular_files(patterns) do
|
||||
case filter_multiple_patterns(patterns) do
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
{:ok, files} ->
|
||||
@@ -363,9 +381,13 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp match_regular_files(patterns) do
|
||||
defp filter_patterns(pattern) do
|
||||
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
|
||||
end
|
||||
|
||||
defp filter_multiple_patterns(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
case filter_patterns(pattern) do
|
||||
[] -> {:missing, pattern}
|
||||
files -> {:ok, files}
|
||||
end
|
||||
@@ -380,16 +402,14 @@ defmodule Kernel.CLI do
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{:ok, files}
|
||||
{:ok, Enum.uniq(Enum.concat(files))}
|
||||
else
|
||||
{:missing, Enum.uniq(missing_patterns)}
|
||||
end
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
_ = fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -400,8 +420,8 @@ defmodule Kernel.CLI do
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{:win32, _} ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
base = Path.rootname(exec)
|
||||
if File.regular?(base), do: base, else: exec
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
|
||||
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{:module, _} -> []
|
||||
{:module, _} -> :ok
|
||||
{:error, _} ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, {:waiting, module, self(), ref, module}
|
||||
send parent, {:waiting, :module, self(), ref, module}
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{^ref, :ready} -> :ok
|
||||
|
||||
@@ -18,20 +18,22 @@ defmodule Kernel.LexicalTracker do
|
||||
Returns all remotes linked to in this lexical scope.
|
||||
"""
|
||||
def remotes(arg) do
|
||||
# If the module is compiled from a function, its lexical
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the destination the lexical scope is meant to
|
||||
compile to.
|
||||
"""
|
||||
def dest(arg) do
|
||||
:gen_server.call(to_pid(arg), :dest, @timeout)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
def start_link(dest) do
|
||||
:gen_server.start_link(__MODULE__, dest, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
||||
def init([]) do
|
||||
{:ok, :ets.new(:lexical, [:protected])}
|
||||
def init(dest) do
|
||||
{:ok, {:ets.new(:lexical, [:protected]), dest}}
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{:reply, d, d}
|
||||
@doc false
|
||||
def handle_call(:ets, _from, {d, dest}) do
|
||||
{:reply, d, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, d) do
|
||||
{:stop, {:bad_call, request}, d}
|
||||
def handle_call(:dest, _from, {d, dest}) do
|
||||
{:reply, dest, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, d) do
|
||||
def handle_call(request, _from, {d, dest}) do
|
||||
{:stop, {:bad_call, request}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, {d, dest}) do
|
||||
add_module(d, module)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:import_dispatch, module}, d) do
|
||||
def handle_cast({:import_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:alias_dispatch, module}, d) do
|
||||
def handle_cast({:alias_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, module, line, warn}, d) do
|
||||
def handle_cast({:add_import, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_alias, module, line, warn}, d) do
|
||||
def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{:stop, :normal, d}
|
||||
def handle_cast(:stop, {d, dest}) do
|
||||
{:stop, :normal, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(msg, d) do
|
||||
{:stop, {:bad_cast, msg}, d}
|
||||
def handle_cast(msg, {d, dest}) do
|
||||
{:stop, {:bad_cast, msg}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{:noreply, d}
|
||||
@doc false
|
||||
def handle_info(_msg, {d, dest}) do
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{:ok, d}
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
@@ -15,63 +15,69 @@ defmodule Kernel.ParallelCompiler do
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the file
|
||||
* `:each_module` - for each module compiled, invokes the callback
|
||||
passing the file, module and the module bytecode
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
* `:dest` - the destination directory for the beam files. When using `files/2`,
|
||||
this information is only used to properly annotate the beam files before
|
||||
they are loaded into memory. If you want a file to actually be writen to
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
def files(files, options \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok -> result
|
||||
:error -> exit(1)
|
||||
:error -> exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
@@ -85,10 +91,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
exit(try do
|
||||
if output do
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
catch
|
||||
@@ -97,31 +103,31 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
spawn_compilers(t, original, output, options, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
@@ -129,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
@@ -145,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
@@ -158,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
@@ -170,11 +176,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -184,7 +190,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
if all_missing?(entries, waiting, queued) do
|
||||
collect_failures(queued, length(queued) - 1)
|
||||
end
|
||||
exit(1)
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -209,7 +215,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts Exception.format(:exit, reason, [])
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
|
||||
@@ -114,24 +114,31 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{name: "josé", age: 27}
|
||||
%{name: "john", age: 27}
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
In practice though, structs are usually defined with the
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "john", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follows:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
%User{} == %{__struct__: User, name: "john", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
%User{full_name: "john doe"}
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
@@ -148,8 +155,10 @@ defmodule Kernel.SpecialForms do
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
Although structs are maps, by default structs do not implement
|
||||
any of the protocols implemented for maps. Check
|
||||
`Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
@@ -204,7 +213,7 @@ defmodule Kernel.SpecialForms do
|
||||
is an arbitrary series of bits. A binary is a special case of
|
||||
bitstring that has a total size divisible by 8.
|
||||
|
||||
The utf8, utf16, and utf32 types are for UTF code points. They
|
||||
The utf8, utf16, and utf32 types are for unicode codepoints. They
|
||||
can also be applied to literal strings and char lists:
|
||||
|
||||
iex> <<"foo" :: utf16>>
|
||||
@@ -214,8 +223,8 @@ defmodule Kernel.SpecialForms do
|
||||
alias for binary.
|
||||
|
||||
The signedness can also be given as signed or unsigned. The
|
||||
signedness only matters for matching. If unspecified, it
|
||||
defaults to unsigned. Example:
|
||||
signedness only matters for matching and relevant only for
|
||||
integers. If unspecified, it defaults to unsigned. Example:
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
@@ -232,17 +241,15 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
|
||||
Signedness is only relevant on integers.
|
||||
|
||||
The endianness of a segment can be big, little or native (the
|
||||
latter meaning it will be resolved at VM load time). Passing
|
||||
many options can be done by giving a list:
|
||||
latter meaning it will be resolved at VM load time). Many options
|
||||
can be given by using `-` as separator:
|
||||
|
||||
<<102 :: [integer, native], rest :: binary>>
|
||||
<<102 :: integer-native, rest :: binary>>
|
||||
|
||||
Or:
|
||||
|
||||
<<102 :: [unsigned, big, integer], rest :: binary>>
|
||||
<<102 :: unsigned-big-integer, rest :: binary>>
|
||||
|
||||
And so on.
|
||||
|
||||
@@ -251,43 +258,45 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Finally, we can also specify size and unit for each segment. The
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the segment:
|
||||
the segment in bits. The default unit for integers, floats,
|
||||
and bitstrings is 1. For binaries, it is 8.
|
||||
|
||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
||||
"foo"
|
||||
Since integers are default, the default unit is 1. The example below
|
||||
matches because the string "foo" takes 24 bits and we match it
|
||||
against a segment of 24 bits, 8 of which are taken by the integer
|
||||
102 and the remaining 16 bits are specified on the rest.
|
||||
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
|
||||
We can also match by specifying size and unit explicitly:
|
||||
|
||||
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
|
||||
"foo"
|
||||
|
||||
However, if we expect a size of 32, it won't match:
|
||||
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
|
||||
In the example above, the first two expressions matches
|
||||
because the string "foo" takes 24 bits and we are matching
|
||||
against a segment of 24 bits as well, 8 of which are taken by
|
||||
the integer 102 and the remaining 16 bits are specified on
|
||||
the rest. On the last example, we expect a rest with size 32,
|
||||
which won't match.
|
||||
|
||||
Size and unit are not applicable to utf8, utf16, and utf32.
|
||||
|
||||
The default size for integers is 8. For floats, it is 64. For
|
||||
binaries, it is the size of the binary. Only the last binary
|
||||
in a binary match can use the default size (all others must
|
||||
have their size specified explicitly). Bitstrings do not have
|
||||
a default size.
|
||||
have their size specified explicitly).
|
||||
|
||||
Size can also be specified using a syntax shortcut. Instead of
|
||||
writing `size(8)`, one can write just `8` and it will be interpreted
|
||||
as `size(8)`
|
||||
Size and unit can also be specified using a syntax shortcut
|
||||
when passing integer values:
|
||||
|
||||
iex> << 1 :: 3 >> == << 1 :: size(3) >>
|
||||
true
|
||||
<< x :: 8 >> == << x :: size(8) >>
|
||||
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
<< x :: _ * 4 >> == << x :: unit(4) >>
|
||||
|
||||
The default unit for integers, floats, and bitstrings is 1. For
|
||||
binaries, it is 8.
|
||||
This syntax reflects the fact the effective size is given by
|
||||
multiplying the size by the unit.
|
||||
|
||||
For floats, unit * size must result in 32 or 64, corresponding
|
||||
For floats, `size * unit` must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
"""
|
||||
defmacro unquote(:<<>>)(args)
|
||||
@@ -334,28 +343,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
a remote call regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
@@ -580,7 +570,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env[]` record.
|
||||
Returns the current environment information as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
@@ -603,6 +593,14 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Returns the current calling environment as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the filename, line numbers,
|
||||
set up aliases, the function and others.
|
||||
"""
|
||||
defmacro __CALLER__
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
@@ -653,23 +651,28 @@ defmodule Kernel.SpecialForms do
|
||||
The tuple above represents a function call to `sum` passing 1, 2 and
|
||||
3 as arguments. The tuple elements are:
|
||||
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation;
|
||||
* The second element of the tuple represents metadata;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation.
|
||||
|
||||
* The second element of the tuple represents metadata.
|
||||
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call).
|
||||
|
||||
## Options
|
||||
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
* `:unquote` - when false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is able to unquote.
|
||||
|
||||
* `:location` - when set to `:keep`, keeps the current line and file from
|
||||
quote. Read the Stacktrace information section below for more
|
||||
information.
|
||||
|
||||
* `:context` - sets the resolution context.
|
||||
|
||||
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
|
||||
given, `unquote` is automatically disabled.
|
||||
|
||||
## Quote literals
|
||||
|
||||
@@ -1047,9 +1050,9 @@ defmodule Kernel.SpecialForms do
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
This is actually a common pitfall when developing macros. We are
|
||||
assuming a particular shape in the macro. We can work around it
|
||||
by unquoting the variable inside the quoted expression:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -1090,7 +1093,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
Imagine the situation you have a variable `value` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
@@ -1152,10 +1155,10 @@ defmodule Kernel.SpecialForms do
|
||||
Note generators can also be used to filter as it removes any value
|
||||
that doesn't match the left side of `<-`:
|
||||
|
||||
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
|
||||
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOSE", "ERIC"]
|
||||
["JOHN", "MEG"]
|
||||
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
@@ -1305,21 +1308,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1) The head element of aliases can be any term;
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
|
||||
2) The tail elements of aliases are guaranteed to always be atoms;
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
|
||||
|
||||
4) When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
@@ -1375,7 +1369,27 @@ defmodule Kernel.SpecialForms do
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
defmacro case(condition, clauses)
|
||||
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to truth value.
|
||||
|
||||
Raises an error if all conditions evaluate to to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
cond do
|
||||
1 + 1 == 1 ->
|
||||
"This will never match"
|
||||
2 * 2 != 4 ->
|
||||
"Nor this"
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond(clauses)
|
||||
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
@@ -1474,9 +1488,9 @@ defmodule Kernel.SpecialForms do
|
||||
The catch clause can be used to catch throws values and exits.
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, 1 -> IO.puts "Exited with 1"
|
||||
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
|
||||
end
|
||||
|
||||
try do
|
||||
@@ -1634,11 +1648,11 @@ defmodule Kernel.SpecialForms do
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
* `:infinity` - the process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately
|
||||
|
||||
## Variables handling
|
||||
|
||||
|
||||
+210
-116
@@ -1,5 +1,5 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
|
||||
| Fun
|
||||
| Integer
|
||||
| List
|
||||
| Map
|
||||
| Tuple
|
||||
| Union
|
||||
| UserDefined # Described in section "Defining a type"
|
||||
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
|
||||
| nonempty_list(Type) # proper non-empty list
|
||||
| [] # empty list
|
||||
| [Type] # shorthand for list(Type)
|
||||
| [...] # shorthand for nonempty_list()
|
||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||
| [Keyword]
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
Map :: map() # map of any size
|
||||
| %{} # map of any size
|
||||
| %Struct{} # struct (see defstruct/1)
|
||||
| %Struct{Keyword}
|
||||
| %{Keyword}
|
||||
| %{Pairs}
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| {TList}
|
||||
| record(Atom) # record (see Record)
|
||||
| record(Atom, Keyword)
|
||||
|
||||
Keyword :: ElixirAtom: Type
|
||||
| ElixirAtom: Type, Keyword
|
||||
|
||||
Pairs :: Type => Type
|
||||
| Type => Type, Pairs
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
|
||||
Union :: Type1 | Type2
|
||||
Union :: Type | Type
|
||||
|
||||
### Bit strings
|
||||
|
||||
@@ -109,23 +127,25 @@ defmodule Kernel.Typespec do
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`boolean` | `false` \| `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`char` | `0..0x10ffff`
|
||||
`number` | `integer` \| `float`
|
||||
`char_list` | `[char]`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`iodata` | `iolist` \| `binary`
|
||||
`iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
|
||||
`module` | `atom` \| `tuple`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`timeout` | `:infinity` \| `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
@@ -262,60 +282,18 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
|
||||
"""
|
||||
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
|
||||
else
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
def define_type(kind, expr, doc \\ nil, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
Defines a `spec` by receiving a typespec expression.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
def define_spec(kind, expr, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -324,9 +302,12 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
finder = fn {_kind, expr, _doc, _caller} ->
|
||||
type_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -334,8 +315,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -343,8 +326,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -423,7 +408,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
:lists.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -443,7 +428,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
exported_types = :lists.flatten(exported_types)
|
||||
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
@@ -514,10 +499,40 @@ defmodule Kernel.Typespec do
|
||||
binary
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def spec_to_signature({:when, _, [spec, _]}),
|
||||
do: type_to_signature(spec)
|
||||
def spec_to_signature(other),
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
def defspec(kind, expr, caller) do
|
||||
Module.store_typespec(caller.module, kind, {kind, expr, caller})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftype(kind, expr, caller) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
Module.store_typespec(module, kind, {kind, expr, doc, caller})
|
||||
end
|
||||
|
||||
## Translation from Elixir AST to typespec AST
|
||||
|
||||
@doc false
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -525,31 +540,43 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
arity = length(vars)
|
||||
|
||||
define_type(caller, kind, type)
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
if not export and doc do
|
||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types")
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
def translate_type(_kind, other, _doc, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
def translate_spec(kind, spec, caller) do
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
@@ -565,12 +592,11 @@ defmodule Kernel.Typespec do
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
arity = length(args)
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
defp translate_spec(_kind, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
@@ -578,14 +604,14 @@ defmodule Kernel.Typespec do
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
Enum.reduce(guard, [], fn
|
||||
:lists.foldl(fn
|
||||
{_name, {:var, _, context}}, acc when is_atom(context) ->
|
||||
acc
|
||||
{name, type}, acc ->
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end, [], guard) |> :lists.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
@@ -634,8 +660,20 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :list, args}) do
|
||||
for arg <- args, do: typespec_to_ast(arg)
|
||||
defp typespec_to_ast({:type, line, :list, []}) do
|
||||
{:list, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :list, [arg]}) do
|
||||
[typespec_to_ast(arg)]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
|
||||
[{:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
|
||||
[typespec_to_ast(arg), {:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
@@ -684,7 +722,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
||||
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
{:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, nil, []}) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, name, args}) do
|
||||
@@ -741,14 +783,14 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
<<"_", c :: binary-size(1), rest :: binary>> ->
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
<<c :: binary-size(1), rest :: binary>> ->
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
## To typespec conversion
|
||||
|
||||
defp line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
@@ -779,14 +821,65 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn {k, v} ->
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
end, fields)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
||||
module = Macro.expand(name, caller)
|
||||
|
||||
struct =
|
||||
if module == caller.module do
|
||||
Module.get_attribute(module, :struct) ||
|
||||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
|
||||
else
|
||||
module.__struct__
|
||||
end
|
||||
|
||||
struct =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
end, Map.to_list(struct))
|
||||
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(struct, field) do
|
||||
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
|
||||
end
|
||||
end, fields)
|
||||
|
||||
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
|
||||
typespec({:%{}, meta, fields}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle records
|
||||
defp typespec({:record, meta, [atom]}, vars, caller) do
|
||||
typespec({:record, meta, [atom, []]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
||||
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||
keyword when is_list(keyword) ->
|
||||
keyword =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
end, keyword)
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(keyword, field) do
|
||||
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
|
||||
end
|
||||
end, fields)
|
||||
|
||||
fields = Keyword.merge(keyword, fields)
|
||||
types = Keyword.values(fields)
|
||||
|
||||
typespec({:{}, meta, [atom|types]}, vars, caller)
|
||||
_ ->
|
||||
compile_error(caller, "unknown record #{inspect atom}")
|
||||
end
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
@@ -822,13 +915,6 @@ defmodule Kernel.Typespec do
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: {:any, [], []}]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
@@ -859,7 +945,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if name in vars do
|
||||
if :lists.member(name, vars) do
|
||||
{:var, line(meta), name}
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -867,11 +953,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
|
||||
:elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :string, arguments}
|
||||
{:type, line(meta), type, arguments}
|
||||
end
|
||||
|
||||
defp typespec({:char_list, _meta, []}, vars, caller) do
|
||||
@@ -900,20 +986,28 @@ defmodule Kernel.Typespec do
|
||||
typespec({nil, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||
typespec({:nonempty_list, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec], vars, caller) do
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end, validate_kw(h, list, caller), t)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -942,7 +1036,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
|
||||
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
|
||||
{:type, line(meta), :any}
|
||||
end
|
||||
|
||||
|
||||
+67
-47
@@ -9,21 +9,22 @@ defmodule Keyword do
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
For example, `Keyword.get/3` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
a given key and `delete_first` deletes just one of the existing
|
||||
particular, `Enum.into/2` allows creating new keywords without
|
||||
removing duplicated keys, `get_values/2` returns all values for
|
||||
a given key and `delete_first/2` deletes just one of the existing
|
||||
entries.
|
||||
|
||||
Since a keyword list is simply a list, all the operations defined
|
||||
in `Enum` and `List` can also be applied.
|
||||
in `Enum` and `List` can be applied.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@@ -33,7 +34,7 @@ defmodule Keyword do
|
||||
@type t(value) :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not.
|
||||
Checks if the given argument is a keyword list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
@@ -55,7 +56,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
Unlike `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -75,7 +76,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
Unlike `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -95,7 +96,7 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, return default value (`nil` if no default value).
|
||||
If `key` does not exist, return the default value (`nil` if no default value).
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
@@ -123,6 +124,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
@@ -134,7 +136,7 @@ defmodule Keyword do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
@spec fetch(t, key) :: {:ok, value} | :error
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, value} -> {:ok, value}
|
||||
@@ -143,8 +145,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific `key`. If `key` does not exist,
|
||||
a `KeyError` is raised.
|
||||
Fetches the value for specific `key`.
|
||||
|
||||
If `key` does not exist, a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -174,12 +177,18 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} <- keywords, key == k, do: v
|
||||
fun = fn
|
||||
{k, v} when k === key -> {true, v}
|
||||
{_, _} -> false
|
||||
end
|
||||
|
||||
:lists.filtermap(fun, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys from the keyword list. Duplicated
|
||||
keys appear duplicated in the final list of keys.
|
||||
Returns all keys from the keyword list.
|
||||
|
||||
Duplicated keys appear duplicated in the final list of keys.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -192,7 +201,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for {key, _} <- keywords, do: key
|
||||
:lists.map(fn {k, _} -> k end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -206,11 +215,12 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for {_, value} <- keywords, do: value
|
||||
:lists.map(fn {_, v} -> v end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
Deletes the entries in the keyword list for a `key` with `value`.
|
||||
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -227,13 +237,14 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
|
||||
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
Use `delete_first/2` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
## Examples
|
||||
@@ -250,11 +261,12 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, _} = tuple <- keywords, key != k, do: tuple
|
||||
:lists.filter(fn {k, _} -> k != key end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -313,7 +325,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two keywords are equal. I.e. they contain
|
||||
Checks if two keywords are equal.
|
||||
|
||||
Two keywords are considered to be equal if they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
@@ -328,8 +342,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the one given as second argument wins.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the one given in the second argument wins.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -339,12 +354,14 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
fun = fn {k, _v} -> not has_key?(d2, k) end
|
||||
d2 ++ :lists.filter(fun, d1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the given function is invoked to solve conflicts.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the given function is invoked to solve conflicts.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -385,10 +402,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, raises `KeyError`.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -418,10 +436,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, inserts the given `initial` value.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -448,8 +467,9 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate keyword list. Returns a tuple with the new list and the old list
|
||||
with removed keys.
|
||||
separate keyword list.
|
||||
|
||||
Returns a tuple with the new list and the old list with removed keys.
|
||||
|
||||
Keys for which there are no entires in the keyword list are ignored.
|
||||
|
||||
@@ -467,16 +487,16 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = {[], []}
|
||||
|
||||
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
|
||||
fun = fn {k, v}, {take, drop} ->
|
||||
case k in keys do
|
||||
true -> {[{k, v}|take], drop}
|
||||
false -> {take, [{k, v}|drop]}
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
acc = {[], []}
|
||||
{take, drop} = :lists.foldl(fun, acc, keywords)
|
||||
{:lists.reverse(take), :lists.reverse(drop)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -497,11 +517,11 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
Drops the given keys from the keyword list.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
@@ -517,14 +537,14 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, not k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> not k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
|
||||
All duplicated entries are removed. See `pop_first/3` for
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
@@ -551,10 +571,10 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular ocurrence
|
||||
list as well as the keyword list without that particular occurrence
|
||||
of `key`.
|
||||
|
||||
Duplicated entries are not removed.
|
||||
Duplicated keys are not removed.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+5
-49
@@ -247,7 +247,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -307,6 +307,8 @@ defmodule List do
|
||||
@doc """
|
||||
Zips corresponding elements from each list in `list_of_lists`.
|
||||
|
||||
The zipping finishes as soon as any list terminates.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||
@@ -322,24 +324,6 @@ defmodule List do
|
||||
do_zip(list_of_lists, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unzips the given list of lists or tuples into separate lists and returns a
|
||||
list of lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &Tuple.to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
Note that `index` is capped at the list length. Negative indices
|
||||
@@ -528,7 +512,7 @@ defmodule List do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
@spec to_integer(char_list, 2..36) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
@@ -566,7 +550,7 @@ defmodule List do
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.char_list) :: String.t
|
||||
@spec to_string(:unicode.charlist) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
@@ -580,34 +564,6 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
|
||||
+141
-50
@@ -16,7 +16,8 @@ defmodule Macro do
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in,
|
||||
:and, :or, :when, :in,
|
||||
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -30,20 +31,23 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
o when o in [:<-, :\\] -> {:left, 40}
|
||||
:when -> {:right, 50}
|
||||
::: -> {:right, 60}
|
||||
:| -> {:right, 70}
|
||||
:= -> {:right, 90}
|
||||
o when o in [:||, :|||, :or] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
o when o in [:|>, :<<<, :>>>, :<~, :~>,
|
||||
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
|
||||
:in -> {:left, 180}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,12 +57,16 @@ defmodule Macro do
|
||||
Raises if the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({:|> , _, [left, right]}) do
|
||||
[{left, 0}|unpipe(right)]
|
||||
def unpipe(expr) do
|
||||
:lists.reverse(unpipe(expr, []))
|
||||
end
|
||||
|
||||
def unpipe(other) do
|
||||
[{other, 0}]
|
||||
defp unpipe({:|>, _, [left, right]}, acc) do
|
||||
unpipe(right, unpipe(left, acc))
|
||||
end
|
||||
|
||||
defp unpipe(other, acc) do
|
||||
[{other, 0}|acc]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +76,7 @@ defmodule Macro do
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
@@ -80,7 +88,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
defp bad_pipe(expr, call_args) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
|
||||
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -109,6 +122,31 @@ defmodule Macro do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Genrates a AST node representing the variable given
|
||||
by the atoms `var` and `context`.
|
||||
|
||||
## Examples
|
||||
|
||||
In order to build a variable, a context is expected.
|
||||
Most of the times, in order to preserve hygiene, the
|
||||
context must be `__MODULE__`:
|
||||
|
||||
iex> Macro.var(:foo, __MODULE__)
|
||||
{:foo, [], __MODULE__}
|
||||
|
||||
However, if there is a need to access the user variable,
|
||||
nil can be given:
|
||||
|
||||
iex> Macro.var(:foo, nil)
|
||||
{:foo, [], nil}
|
||||
|
||||
"""
|
||||
@spec var(var, context) :: {var, [], context} when var: atom, context: atom
|
||||
def var(var, context) when is_atom(var) and is_atom(context) do
|
||||
{var, [], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions.
|
||||
"""
|
||||
@@ -269,6 +307,40 @@ defmodule Macro do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the given expressions are valid quoted expressions.
|
||||
|
||||
Check the `type:Macro.t` for the specification of a valid
|
||||
quoted expression.
|
||||
"""
|
||||
@spec validate(term) :: :ok | {:error, term}
|
||||
def validate(expr) do
|
||||
find_invalid(expr) || :ok
|
||||
end
|
||||
|
||||
defp find_invalid({left, right}), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid(list) when is_list(list), do:
|
||||
Enum.find_value(list, &find_invalid/1)
|
||||
|
||||
defp find_invalid(pid) when is_pid(pid), do: nil
|
||||
defp find_invalid(atom) when is_atom(atom), do: nil
|
||||
defp find_invalid(num) when is_number(num), do: nil
|
||||
defp find_invalid(bin) when is_binary(bin), do: nil
|
||||
|
||||
defp find_invalid(fun) when is_function(fun) do
|
||||
unless :erlang.fun_info(fun, :env) == {:env, []} and
|
||||
:erlang.fun_info(fun, :type) == {:type, :external} do
|
||||
{:error, fun}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_invalid(other), do: {:error, other}
|
||||
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
@@ -276,9 +348,9 @@ defmodule Macro do
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
|
||||
are also supported via `\xNN` and `\x{NN...}` syntax.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
@@ -309,6 +381,7 @@ defmodule Macro do
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?0), do: ?0
|
||||
def unescape_map(?a), do: ?\a
|
||||
def unescape_map(?b), do: ?\b
|
||||
def unescape_map(?d), do: ?\d
|
||||
@@ -319,20 +392,14 @@ defmodule Macro do
|
||||
def unescape_map(?s), do: ?\s
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(?x), do: true
|
||||
def unescape_map(e), do: e
|
||||
|
||||
If the `unescape_map` function returns `false`. The char is
|
||||
not escaped and `\` is kept in the char list.
|
||||
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns `false` for `?0`.
|
||||
|
||||
## Hex
|
||||
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns `false` for `?x`.
|
||||
Hexadecimals will be escaped if the map function returns `true`
|
||||
for `?x`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -488,17 +555,21 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string([right], fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
if sigil = sigil_call(ast, fun) do
|
||||
sigil
|
||||
else
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
@@ -521,7 +592,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
|
||||
# Block keywords
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
@@ -531,12 +602,32 @@ defmodule Macro do
|
||||
end
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
|
||||
sigil =
|
||||
case Atom.to_string(func) do
|
||||
<<"sigil_", name>> ->
|
||||
"~" <> <<name>> <>
|
||||
fun.(string, inspect(string, [])) <>
|
||||
sigil_args(args, fun)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
fun.(ast, sigil)
|
||||
end
|
||||
|
||||
defp sigil_call(_other, _fun) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp sigil_args([], _fun), do: ""
|
||||
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
@@ -661,10 +752,10 @@ defmodule Macro do
|
||||
|
||||
The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
* Macros (local or remote)
|
||||
* Aliases are expanded (if possible) and return atoms
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`)
|
||||
* Module attributes reader (`@foo`)
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
@@ -807,7 +898,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
||||
env, extra)
|
||||
env, extra, true)
|
||||
|
||||
case expand do
|
||||
{:ok, receiver, quoted} ->
|
||||
@@ -848,7 +939,7 @@ defmodule Macro do
|
||||
be expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
`expand_once/2` for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
|
||||
+42
-25
@@ -4,30 +4,47 @@ defmodule Macro.Env do
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
accessed as `__CALLER__`.
|
||||
|
||||
* `module` - the current module name.
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as {var, context}
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
An instance of `Macro.Env` must not be modified by hand. If you need to
|
||||
create a custom environment to pass to `Code.eval_quoted/3`, use the
|
||||
following trick:
|
||||
|
||||
def make_custom_env do
|
||||
import SomeModule, only: [some_function: 2]
|
||||
alias A.B.C
|
||||
__ENV__
|
||||
end
|
||||
|
||||
You may then call `make_custom_env()` to get a struct with the desired
|
||||
imports and aliases included.
|
||||
|
||||
It contains the following fields:
|
||||
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity; returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment; it can be `nil`
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as `{var, context}`
|
||||
* `export_vars` - a list keeping all variables to be exported in a
|
||||
construct (may be `nil`)
|
||||
* `lexical_tracker` - PID of the lexical tracker which is responsible to
|
||||
keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
@type name_arity :: {atom, arity}
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{module, module}]
|
||||
@@ -40,10 +57,10 @@ defmodule Macro.Env do
|
||||
@type vars :: [{atom, atom | non_neg_integer}]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type local :: module | nil
|
||||
@type local :: atom | nil
|
||||
|
||||
@type t :: %{__struct__: __MODULE__,
|
||||
module: module,
|
||||
module: atom,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
@@ -103,9 +120,9 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(env.module) ->
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
|
||||
+28
-1
@@ -11,7 +11,7 @@ defmodule Map do
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@@ -38,5 +38,32 @@ defmodule Map do
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a struct to map.
|
||||
|
||||
It accepts the struct module or a struct itself and
|
||||
simply removes the `__struct__` field from the struct.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct [:name]
|
||||
end
|
||||
|
||||
Map.from_struct(User)
|
||||
#=> %{name: nil}
|
||||
|
||||
Map.from_struct(%User{name: "john"})
|
||||
#=> %{name: "john"}
|
||||
|
||||
"""
|
||||
def from_struct(struct) when is_atom(struct) do
|
||||
:maps.remove(:__struct__, struct.__struct__)
|
||||
end
|
||||
|
||||
def from_struct(%{__struct__: _} = struct) do
|
||||
:maps.remove(:__struct__, struct)
|
||||
end
|
||||
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
end
|
||||
|
||||
+117
-120
@@ -14,7 +14,7 @@ defmodule Module do
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
* `@before_compile`
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
@@ -63,7 +63,7 @@ defmodule Module do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the British spelling)
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Module do
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
@@ -85,6 +85,9 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@@ -95,7 +98,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
@@ -122,7 +125,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
@@ -139,7 +142,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
@@ -156,7 +159,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
@@ -211,7 +214,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
@@ -237,7 +240,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
@@ -247,14 +250,25 @@ defmodule Module do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
* `@external_resource`
|
||||
|
||||
Specify an external resource to the current module.
|
||||
|
||||
Many times a module embeds information from an external file. This
|
||||
attribute allows the module to annotate which external resources
|
||||
have been used.
|
||||
|
||||
Tools like Mix may use this information to ensure the module is
|
||||
recompiled in case any of the external resources change.
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -280,18 +294,11 @@ defmodule Module do
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
@@ -356,8 +363,10 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be passed as options.
|
||||
the given quoted expressions.
|
||||
|
||||
The line where the module is defined and its file **must**
|
||||
be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -383,13 +392,16 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
def create(module, quoted, opts)
|
||||
|
||||
def create(module, quoted, %Macro.Env{} = env) do
|
||||
create(module, quoted, Map.to_list(env))
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
|
||||
unless Keyword.has_key?(opts, :file) do
|
||||
raise ArgumentError, "expected :file to be given as option"
|
||||
end
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@@ -467,19 +479,6 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an anonymous function from the given module, function
|
||||
and arity. The module and function are not verified to exist.
|
||||
|
||||
iex> fun = Module.function(Kernel, :is_atom, 1)
|
||||
iex> fun.(:hello)
|
||||
true
|
||||
|
||||
"""
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
@@ -505,23 +504,23 @@ defmodule Module do
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
table = data_table_for(module)
|
||||
|
||||
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{signature, _} = :lists.mapfoldl fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end
|
||||
end, 1, signature
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
case :ets.lookup(table, {:doc, tuple}) do
|
||||
[] ->
|
||||
:ets.insert(table, {tuple, line, kind, signature, doc})
|
||||
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
|
||||
:ok
|
||||
[{tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
doc_tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
if(is_nil(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
end
|
||||
@@ -544,7 +543,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
case Atom.to_string(var) do
|
||||
"_" <> rest -> {String.to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
@@ -613,7 +612,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@@ -633,7 +632,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{_, ^kind, _, _, _, _, _}] -> true
|
||||
_ -> false
|
||||
@@ -653,8 +652,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -672,8 +671,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -699,12 +698,11 @@ defmodule Module do
|
||||
[]
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
old = :elixir_def_overridable.overridable(module)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
:elixir_def_overridable.overridable(module, merged)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -713,7 +711,7 @@ defmodule Module do
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -732,7 +730,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
@@ -757,15 +755,9 @@ defmodule Module do
|
||||
|
||||
@foo
|
||||
|
||||
Expands to:
|
||||
Expands close to:
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument may be given to indicate a stacktrace
|
||||
to be emitted when the attribute was not previously defined.
|
||||
The default value for `warn` is nil for direct calls but the `@foo`
|
||||
macro sets it to the proper stacktrace automatically, warning
|
||||
every time `@foo` is used but not set previously.
|
||||
Module.get_attribute(__MODULE__, :foo)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -779,37 +771,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
||||
def get_attribute(module, key, warn \\ nil) when
|
||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
@spec get_attribute(atom, atom) :: term
|
||||
def get_attribute(module, key) do
|
||||
get_attribute(module, key, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -823,10 +787,12 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec delete_attribute(atom, atom) :: :ok
|
||||
def delete_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
table = data_table_for(module)
|
||||
:ets.delete(table, key)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -838,12 +804,12 @@ defmodule Module do
|
||||
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
* `:accumulate` - several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
* `:persist` - the attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are `false`.
|
||||
|
||||
@@ -865,13 +831,13 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, {:__persisted_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, {:__acc_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -902,7 +868,9 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, :private_doc} ->
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
:elixir_errors.warn line, env.file,
|
||||
"function #{name}/#{arity} is private, " <>
|
||||
"@doc's are always discarded for private functions"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -911,7 +879,7 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_typespec(module, key, value) when is_atom(key) do
|
||||
def store_typespec(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -924,6 +892,38 @@ defmodule Module do
|
||||
:ets.insert(table, {key, new})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||
nil
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||
@@ -931,7 +931,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
# Attempt to compile behaviour but ignore failure (will warn later)
|
||||
_ = Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -954,15 +955,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp data_table_for(module) do
|
||||
module
|
||||
:elixir_module.data_table(module)
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
:elixir_module.docs_table(module)
|
||||
defp defs_table_for(module) do
|
||||
:elixir_module.defs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
|
||||
@@ -65,22 +65,25 @@ defmodule Module.LocalsTracker do
|
||||
"""
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reduce_reachable(d, :local, [])
|
||||
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
|
||||
end
|
||||
|
||||
defp reachable_from(d, starting) do
|
||||
:sets.to_list(reduce_reachable(d, starting, :sets.new))
|
||||
end
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
|
||||
remaining = :sets.subtract(neighbours, vertices)
|
||||
vertices = :sets.union(neighbours, vertices)
|
||||
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__locals_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
@@ -89,8 +92,7 @@ defmodule Module.LocalsTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
:gen_server.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
@@ -134,9 +136,7 @@ defmodule Module.LocalsTracker do
|
||||
# Reattach a previously yanked node
|
||||
@doc false
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, {:reattach, tuple, neighbours})
|
||||
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@@ -156,12 +156,42 @@ defmodule Module.LocalsTracker do
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
def collect_unused_locals(ref, private) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
{unreachable(d, private), collect_warnings(d, private)}
|
||||
end
|
||||
|
||||
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
|
||||
defp unreachable(d, private) do
|
||||
unreachable = for {tuple, _, _} <- private, do: tuple
|
||||
|
||||
private =
|
||||
for {tuple, :defp, _} <- private do
|
||||
neighbours = :digraph.in_neighbours(d, tuple)
|
||||
neighbours = for {_, _} = t <- neighbours, do: t
|
||||
{tuple, :sets.from_list(neighbours)}
|
||||
end
|
||||
|
||||
reduce_unreachable(private, [], :sets.from_list(unreachable))
|
||||
end
|
||||
|
||||
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
|
||||
if :sets.is_subset(callers, unreachable) do
|
||||
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
|
||||
else
|
||||
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce_unreachable([], _acc, unreachable) do
|
||||
:sets.to_list(unreachable)
|
||||
end
|
||||
|
||||
defp collect_warnings(d, private) do
|
||||
reachable = reachable_from(d, :local)
|
||||
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
@@ -169,7 +199,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||
defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||
{name, arity} = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
@@ -211,6 +241,7 @@ defmodule Module.LocalsTracker do
|
||||
{:ok, {d, []}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call({:cache_env, env}, _from, {d, cache}) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
@@ -227,10 +258,9 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_call({:yank, local}, _from, {d, _} = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{:reply, {in_vertices, out_vertices}, state}
|
||||
:digraph.del_edges(d, :digraph.out_edges(d, local))
|
||||
{:reply, {[], out_vertices}, state}
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, {d, _} = state) do
|
||||
@@ -241,6 +271,7 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh do
|
||||
:digraph.add_vertex(d, from)
|
||||
replace_edge!(d, from, tuple)
|
||||
end
|
||||
|
||||
for to <- out_neigh do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, tuple, to)
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
@@ -317,14 +360,9 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
|
||||
defp replace_edge(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
:digraph.add_edge(d, from, to)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,31 @@ defmodule Node do
|
||||
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Turns a non-distributed node into a distributed node.
|
||||
|
||||
This functionality starts the `:net_kernel` and other
|
||||
related processes.
|
||||
"""
|
||||
@spec start(node, :longnames | :shortnames, non_neg_integer) ::
|
||||
{:ok, pid} | {:error, term}
|
||||
def start(name, type \\ :longnames, tick_time \\ 15000) do
|
||||
:net_kernel.start([name, type, tick_time])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Turns a distributed node into a non-distributed node.
|
||||
|
||||
For other nodes in the network, this is the same as the node going down.
|
||||
Only possible when the node was started with `Node.start/3`, otherwise
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current node.
|
||||
|
||||
|
||||
+176
-92
@@ -32,7 +32,9 @@ defmodule OptionParser do
|
||||
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions.
|
||||
conventions. This means that option names on the command line cannot contain
|
||||
underscores; such options will be reported as `:undefined` (in strict mode)
|
||||
or `:invalid` (in basic mode).
|
||||
|
||||
## Switches
|
||||
|
||||
@@ -40,30 +42,30 @@ defmodule OptionParser do
|
||||
switches and their formats. The switches can be specified via two
|
||||
different options:
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error;
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error.
|
||||
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed;
|
||||
* `:switches` - defines some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed.
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
|
||||
For each switch, the following types are supported:
|
||||
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as a string;
|
||||
* `:boolean` - marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is `true` or
|
||||
`false`.
|
||||
* `:integer` - parses the switch as an integer.
|
||||
* `:float` - parses the switch as a float.
|
||||
* `:string` - returns the switch as a string.
|
||||
|
||||
If a switch can't be parsed or is not specfied in the strict case,
|
||||
If a switch can't be parsed or is not specified in the strict case,
|
||||
the option is returned in the invalid options list (third element
|
||||
of the returned tuple).
|
||||
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
* `:keep` - keeps duplicated items in the list instead of overriding
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -89,14 +91,8 @@ defmodule OptionParser do
|
||||
|
||||
## Negation switches
|
||||
|
||||
All switches starting with `--no-` are considered to be booleans and never
|
||||
parse the next value:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{[no_op: true], ["path/to/file"], []}
|
||||
|
||||
However, in case the base switch exists, it sets that particular switch to
|
||||
false:
|
||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||
which will set the option to false:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
@@ -111,8 +107,7 @@ defmodule OptionParser do
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
config = compile_config(opts, true)
|
||||
do_parse(argv, config, [], [], [])
|
||||
do_parse(argv, compile_config(opts), [], [], [], true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -132,37 +127,36 @@ defmodule OptionParser do
|
||||
"""
|
||||
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
config = compile_config(opts, false)
|
||||
do_parse(argv, config, [], [], [])
|
||||
do_parse(argv, compile_config(opts), [], [], [], false)
|
||||
end
|
||||
|
||||
defp do_parse([], _config, opts, args, invalid) do
|
||||
defp do_parse([], _config, opts, args, invalid, _all?) do
|
||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
|
||||
defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
|
||||
case next(argv, aliases, switches, strict) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exist and it was successfully parsed
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
new_opts = do_store_option(opts, option, value, kinds)
|
||||
do_parse(rest, config, new_opts, args, invalid)
|
||||
do_parse(rest, config, new_opts, args, invalid, all?)
|
||||
|
||||
{:invalid, option, value, rest} ->
|
||||
# the option exist but it has wrong value
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid])
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
|
||||
|
||||
{:undefined, option, _value, rest} ->
|
||||
# the option does not exist (for strict cases)
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid])
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
|
||||
|
||||
{:error, ["--"|rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg|rest]=remaining_args} ->
|
||||
# there is no option
|
||||
if all do
|
||||
do_parse(rest, config, opts, [arg|args], invalid)
|
||||
if all? do
|
||||
do_parse(rest, config, opts, [arg|args], invalid, all?)
|
||||
else
|
||||
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
||||
end
|
||||
@@ -176,25 +170,27 @@ defmodule OptionParser do
|
||||
as both functions are built on top of next. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was successfully parsed
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the command line)
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, key :: atom, value :: term, argv} |
|
||||
{:undefined, key :: atom, value :: term, argv} |
|
||||
{:invalid, String.t, String.t | nil, argv} |
|
||||
{:undefined, String.t, String.t | nil, argv} |
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
{aliases, switches, strict, _} = compile_config(opts, true)
|
||||
{aliases, switches, strict} = compile_config(opts)
|
||||
next(argv, aliases, switches, strict)
|
||||
end
|
||||
|
||||
@@ -217,14 +213,14 @@ defmodule OptionParser do
|
||||
defp next(["-" <> option|rest], aliases, switches, strict) do
|
||||
{option, value} = split_option(option)
|
||||
opt_name_bin = "-" <> option
|
||||
tagged = tag_option(option, value, switches, aliases)
|
||||
tagged = tag_option(option, switches, aliases)
|
||||
|
||||
if strict and not option_defined?(tagged, switches) do
|
||||
{:undefined, opt_name_bin, value, rest}
|
||||
else
|
||||
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
|
||||
case validate_option(opt_name, value, kinds) do
|
||||
case validate_option(value, kinds) do
|
||||
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
|
||||
:invalid -> {:invalid, opt_name_bin, value, rest}
|
||||
end
|
||||
@@ -235,9 +231,95 @@ defmodule OptionParser do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a key-value enumerable and convert it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.to_argv([foo_bar: "baz"])
|
||||
["--foo-bar", "baz"]
|
||||
|
||||
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
|
||||
["--bool", "--no-bool"]
|
||||
|
||||
"""
|
||||
@spec to_argv(Enumerable.t) :: argv
|
||||
def to_argv(enum) do
|
||||
Enum.flat_map(enum, fn
|
||||
{_key, nil} -> []
|
||||
{key, true} -> [to_switch(key)]
|
||||
{key, false} -> [to_switch(key, "--no-")]
|
||||
{key, value} -> [to_switch(key), to_string(value)]
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_switch(key, prefix \\ "--") when is_atom(key) do
|
||||
prefix <> String.replace(Atom.to_string(key), "_", "-")
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.split("foo bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> OptionParser.split("foo \"bar baz\"")
|
||||
["foo", "bar baz"]
|
||||
"""
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) do
|
||||
do_split(strip_leading_spaces(string), "", [], nil)
|
||||
end
|
||||
|
||||
# If we have a escaped quote, simply remove the escape
|
||||
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
|
||||
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
|
||||
|
||||
# If we have a quote and we were not in a quote, start one
|
||||
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
|
||||
do: do_split(t, buffer, acc, quote)
|
||||
|
||||
# If we have a quote and we were inside it, close it
|
||||
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
|
||||
do: do_split(t, buffer, acc, nil)
|
||||
|
||||
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
|
||||
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
|
||||
do: do_split(t, <<buffer::binary, h>>, acc, nil)
|
||||
|
||||
# If we have space and we are outside of a quote, start new segment
|
||||
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
|
||||
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
|
||||
|
||||
# All other characters are moved to buffer
|
||||
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
|
||||
do_split(t, <<buffer::binary, h>>, acc, quote)
|
||||
end
|
||||
|
||||
# Finish the string expecting a nil marker
|
||||
defp do_split(<<>>, "", acc, nil),
|
||||
do: Enum.reverse(acc)
|
||||
|
||||
defp do_split(<<>>, buffer, acc, nil),
|
||||
do: Enum.reverse([buffer|acc])
|
||||
|
||||
# Otherwise raise
|
||||
defp do_split(<<>>, _, _acc, marker) do
|
||||
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
|
||||
end
|
||||
|
||||
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
|
||||
defp strip_leading_spaces(t), do: t
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_config(opts, all) do
|
||||
defp compile_config(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
|
||||
{switches, strict} = cond do
|
||||
@@ -249,34 +331,34 @@ defmodule OptionParser do
|
||||
{[], false}
|
||||
end
|
||||
|
||||
{aliases, switches, strict, all}
|
||||
{aliases, switches, strict}
|
||||
end
|
||||
|
||||
defp validate_option(option, value, kinds) do
|
||||
{invalid_opt, value} = cond do
|
||||
defp validate_option(value, kinds) do
|
||||
{is_invalid, value} = cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
{true, value}
|
||||
:boolean in kinds ->
|
||||
case value do
|
||||
t when t in [true, "true"] -> {nil, true}
|
||||
f when f in [false, "false"] -> {nil, false}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_opt do
|
||||
if is_invalid do
|
||||
:invalid
|
||||
else
|
||||
{:ok, value}
|
||||
@@ -292,20 +374,20 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
get_negated(option, value, switches)
|
||||
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
|
||||
get_negated(option, switches)
|
||||
end
|
||||
|
||||
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
|
||||
defp tag_option(option, _switches, aliases) when is_binary(option) do
|
||||
opt = get_option(option)
|
||||
if alias = aliases[opt] do
|
||||
{:default, alias}
|
||||
else
|
||||
{:unknown, opt}
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp option_defined?({:unknown, _option}, _switches) do
|
||||
defp option_defined?(:unknown, _switches) do
|
||||
false
|
||||
end
|
||||
|
||||
@@ -317,27 +399,18 @@ defmodule OptionParser do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp normalize_option({:unknown, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
defp normalize_option(:unknown, value, _switches) do
|
||||
{nil, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, nil, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{option, kinds, false}
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{reverse_negated(option), [:invalid], nil}
|
||||
defp normalize_option({:negated, option}, value, switches) do
|
||||
if value do
|
||||
{option, [:invalid], value}
|
||||
else
|
||||
{option, List.wrap(switches[option]), false}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
@@ -374,29 +447,40 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
defp to_underscore(option), do: to_underscore(option, <<>>)
|
||||
|
||||
defp to_underscore("_" <> _rest, _acc), do: nil
|
||||
|
||||
defp to_underscore("-" <> rest, acc),
|
||||
do: to_underscore(rest, acc <> "_")
|
||||
|
||||
defp to_underscore(<<c>> <> rest, acc),
|
||||
do: to_underscore(rest, <<acc::binary, c>>)
|
||||
|
||||
defp to_underscore(<<>>, acc), do: acc
|
||||
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> String.to_atom
|
||||
end
|
||||
|
||||
defp reverse_negated(negated) do
|
||||
String.to_atom("no_" <> Atom.to_string(negated))
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, value, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
else
|
||||
get_option(option)
|
||||
if str = to_underscore(option) do
|
||||
String.to_atom(str)
|
||||
end
|
||||
{:negated, option}
|
||||
end
|
||||
|
||||
defp get_negated(rest, _value, _switches) do
|
||||
{:default, get_option(rest)}
|
||||
defp get_negated("no-" <> rest = original, switches) do
|
||||
cond do
|
||||
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
|
||||
{:negated, negated}
|
||||
option = get_option(original) ->
|
||||
{:default, option}
|
||||
true ->
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
if option = get_option(rest) do
|
||||
{:default, option}
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+179
-81
@@ -60,37 +60,70 @@ defmodule Path do
|
||||
def absname(path, relative_to) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
if :binary.last(path) == ?/ do
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
else
|
||||
path
|
||||
end
|
||||
:relative -> absname_join(relative_to, path)
|
||||
:absolute -> absname_join([path])
|
||||
:volumerelative ->
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
|
||||
## Absolute path on current drive
|
||||
# Absolute path on current drive
|
||||
defp absname_vr(["/"|rest], [volume|_], _relative),
|
||||
do: join([volume|rest])
|
||||
do: absname_join([volume|rest])
|
||||
|
||||
## Relative to current directory on current drive.
|
||||
# Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
do: absname(join(rest), relative)
|
||||
do: absname(absname_join(rest), relative)
|
||||
|
||||
## Relative to current directory on another drive.
|
||||
# Relative to current directory on another drive.
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
absname(absname_join(name), cwd)
|
||||
end
|
||||
|
||||
# Joins a list
|
||||
defp absname_join([name1, name2|rest]), do:
|
||||
absname_join([absname_join(name1, name2)|rest])
|
||||
defp absname_join([name]), do:
|
||||
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
# Joins two paths
|
||||
defp absname_join(left, right),
|
||||
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
|
||||
defp do_absname_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_absname_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_absname_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, relativename, result, os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_absname_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp reverse_maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp reverse_maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp reverse_maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Converts the path to an absolute one and expands
|
||||
any `.` and `..` characters and a leading `~`.
|
||||
@@ -103,7 +136,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
normalize absname(expand_home(path), System.cwd!)
|
||||
expand_dot absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -130,7 +163,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -138,6 +171,7 @@ defmodule Path do
|
||||
|
||||
## Unix examples
|
||||
|
||||
Path.type("/") #=> :absolute
|
||||
Path.type("/usr/local/bin") #=> :absolute
|
||||
Path.type("usr/local/bin") #=> :relative
|
||||
Path.type("../usr/local/bin") #=> :relative
|
||||
@@ -153,10 +187,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec type(t) :: :absolute | :relative | :volumerelative
|
||||
def type(name) when is_list(name) or is_binary(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(0)
|
||||
pathtype(name, major_os_type) |> elem(0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -178,10 +209,20 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative(t) :: binary
|
||||
def relative(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1) |> IO.chardata_to_string
|
||||
relative(name, major_os_type())
|
||||
end
|
||||
|
||||
defp relative(name, os_type) do
|
||||
pathtype(name, os_type)
|
||||
|> elem(1)
|
||||
|> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp pathtype(name, os_type) do
|
||||
case os_type do
|
||||
:win32 -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
@@ -386,7 +427,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more path components joined by the path separator.
|
||||
Joins a list of strings.
|
||||
|
||||
This function should be used to convert a list of strings to a path.
|
||||
Note that any trailing slash is removed on join.
|
||||
@@ -407,11 +448,14 @@ defmodule Path do
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]), do:
|
||||
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
name
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
|
||||
The right path will always be expanded to its relative format
|
||||
and any trailing slash is removed on join.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.join("foo", "bar")
|
||||
@@ -419,44 +463,34 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
def join(left, right) do
|
||||
left = IO.chardata_to_string(left)
|
||||
os_type = major_os_type()
|
||||
do_join(left, right, os_type) |> remove_dirsep(os_type)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
defp do_join("", right, os_type), do: relative(right, os_type)
|
||||
defp do_join(left, "", _os_type), do: left
|
||||
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
defp remove_dirsep("", _os_type), do: ""
|
||||
defp remove_dirsep(bin, os_type) do
|
||||
last = :binary.last(bin)
|
||||
if last == ?/ or (last == ?\\ and os_type == :win32) do
|
||||
binary_part(bin, 0, byte_size(bin) - 1)
|
||||
else
|
||||
bin
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
@doc ~S"""
|
||||
Splits the path into a list at the path separator.
|
||||
|
||||
If an empty string is given, returns an empty list.
|
||||
|
||||
On Windows, path is split on both "\" and "/" separators
|
||||
and the driver letter, if there is one, is always returned
|
||||
in lowercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -479,6 +513,37 @@ defmodule Path do
|
||||
FN.split(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defmodule Wildcard do
|
||||
@moduledoc false
|
||||
|
||||
def read_link_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
|
||||
# For compatibility with buggy Erlang 17.1.
|
||||
def read_file_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case call({:list_dir, dir}) do
|
||||
{:ok, files} ->
|
||||
{:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, call: 1}
|
||||
|
||||
defp call(tuple) do
|
||||
x = :erlang.dt_spread_tag(true)
|
||||
y = :gen_server.call(:file_server_2, tuple)
|
||||
:erlang.dt_restore_tag(x)
|
||||
y
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Traverses paths according to the given `glob` expression.
|
||||
|
||||
@@ -486,19 +551,26 @@ defmodule Path do
|
||||
"wildcard characters" are interpreted in a special way. The
|
||||
following characters are special:
|
||||
|
||||
* `?` - Matches one character.
|
||||
* `*` - Matches any number of characters up to the end of
|
||||
the filename, the next dot, or the next slash.
|
||||
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
|
||||
match all files and zero or more directories and subdirectories.
|
||||
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
|
||||
separated by a hyphen will match a range of characters.
|
||||
* `{item1,item2,...}` - Matches one of the alternatives.
|
||||
* `?` - matches one character
|
||||
|
||||
* `*` - matches any number of characters up to the end of the filename, the
|
||||
next dot, or the next slash
|
||||
|
||||
* `**` - two adjacent `*`'s used as a single pattern will match all
|
||||
files and zero or more directories and subdirectories
|
||||
|
||||
* `[char1,char2,...]` - matches any of the characters listed; two
|
||||
characters separated by a hyphen will match a range of characters
|
||||
|
||||
* `{item1,item2,...}` - matches one of the alternatives
|
||||
|
||||
Other characters represent themselves. Only paths that have
|
||||
exactly the same character in the same position will match. Note
|
||||
that matching is case-sensitive; i.e. "a" will not match "A".
|
||||
|
||||
By default, the patterns `*` and `?` do not match files starting
|
||||
with a dot `.` unless `match_dot: true` is given.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
@@ -513,14 +585,15 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
def wildcard(glob, opts \\ []) do
|
||||
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
|
||||
glob
|
||||
|> chardata_to_list
|
||||
|> :filelib.wildcard
|
||||
|> chardata_to_list()
|
||||
|> :filelib.wildcard(mod)
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
# expand_dot the given path by expanding "..", "." and "~".
|
||||
|
||||
defp chardata_to_list(chardata) do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
@@ -537,26 +610,51 @@ defmodule Path do
|
||||
|
||||
defp expand_home(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
"~" <> rest -> resolve_home(rest)
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
defp resolve_home(""), do: System.user_home!
|
||||
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
defp resolve_home(rest) do
|
||||
case {rest, major_os_type} do
|
||||
{"\\" <> _, :win32} ->
|
||||
System.user_home! <> rest
|
||||
{"/" <> _, _} ->
|
||||
System.user_home! <> rest
|
||||
_ -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize(["."|t], acc) do
|
||||
normalize t, acc
|
||||
defp expand_dot(<<"/../", rest::binary>>),
|
||||
do: expand_dot("/" <> rest)
|
||||
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/", rest::binary>>)
|
||||
defp expand_dot("/.."),
|
||||
do: "/"
|
||||
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/">>)
|
||||
defp expand_dot(path),
|
||||
do: expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
|
||||
defp expand_dot([".."|t], [_, _|acc]) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize([h|t], acc) do
|
||||
normalize t, [h|acc]
|
||||
defp expand_dot(["."|t], acc) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join :lists.reverse(acc)
|
||||
defp expand_dot([h|t], acc) do
|
||||
expand_dot t, ["/", h|acc]
|
||||
end
|
||||
|
||||
defp expand_dot([], ["/"|acc]) do
|
||||
IO.iodata_to_binary(:lists.reverse(acc))
|
||||
end
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def open(name, settings) do
|
||||
:erlang.open_port(name, settings)
|
||||
@@ -12,6 +14,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def close(port) do
|
||||
:erlang.port_close(port)
|
||||
@@ -19,6 +23,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
@@ -26,6 +32,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def connect(port, pid) do
|
||||
:erlang.port_connect(port, pid)
|
||||
@@ -33,6 +41,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def control(port, operation, data) do
|
||||
:erlang.port_control(port, operation, data)
|
||||
@@ -40,6 +50,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def call(port, operation, data) do
|
||||
:erlang.port_call(port, operation, data)
|
||||
@@ -47,6 +59,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def info(port) do
|
||||
:erlang.port_info(port)
|
||||
@@ -54,6 +68,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def info(port, item) do
|
||||
:erlang.port_info(port, item)
|
||||
@@ -61,6 +77,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#ports-0.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def list do
|
||||
:erlang.ports
|
||||
|
||||
+48
-78
@@ -6,11 +6,11 @@ defmodule Process do
|
||||
exposes and auto-imports some basic functionality related to processes
|
||||
available through the functions:
|
||||
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
|
||||
"""
|
||||
|
||||
@@ -34,7 +34,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for the given key.
|
||||
Returns the value for the given `key`.
|
||||
"""
|
||||
@spec get(term) :: term
|
||||
@spec get(term, default :: term) :: term
|
||||
@@ -64,7 +64,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given key from the dictionary.
|
||||
Deletes the given `key` from the dictionary.
|
||||
"""
|
||||
@spec delete(term) :: term | nil
|
||||
def delete(key) do
|
||||
@@ -76,18 +76,18 @@ defmodule Process do
|
||||
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
1. If pid is not trapping exits, pid will exit with the given reason.
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid;
|
||||
2. If pid is trapping exits, the exit signal is transformed into a message
|
||||
`{:EXIT, from, reason}` and delivered to the message queue of pid.
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
||||
exits, the exit signal is transformed into a message `{:EXIT, from,
|
||||
:normal}` and delivered to its message queue.
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
||||
an untrappable exit signal is sent to pid which will unconditionally
|
||||
exit with exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -129,10 +129,10 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
Sends `msg` to `dest` after `time` milliseconds.
|
||||
|
||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
||||
If `dest` is a pid, it must be the pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it must be the name of a registered process
|
||||
which is looked up at the time of delivery. No error is given if the name does
|
||||
not refer to a process.
|
||||
|
||||
@@ -194,46 +194,6 @@ defmodule Process do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
@@ -248,8 +208,8 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
If monitor_ref is a reference which the calling process
|
||||
obtained by calling monitor/1, this monitoring is turned off.
|
||||
If `monitor_ref` is a reference which the calling process
|
||||
obtained by calling `monitor/1`, this monitoring is turned off.
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||
@@ -267,7 +227,7 @@ defmodule Process do
|
||||
processes currently existing on the local node.
|
||||
|
||||
Note that a process that is exiting, exists but is not alive, i.e.,
|
||||
alive?/1 will return false for a process that is exiting,
|
||||
`alive?/1` will return `false` for a process that is exiting,
|
||||
but its process identifier will be part of the result returned.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
|
||||
@@ -292,7 +252,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Removes the link, if there is one, between the calling process and
|
||||
the process or port referred to by `pid`. Returns true and does not
|
||||
the process or port referred to by `pid`. Returns `true` and does not
|
||||
fail, even if there is no link or `id` does not exist
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
@@ -305,16 +265,16 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
Associates the name with a pid or a port identifier. `name`, which must
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||
is no longer alive, (check with `alive?/1`) or if the name is
|
||||
already registered (check with `registered?/1`).
|
||||
already registered (check with `whereis/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
def register(pid, name) when not name in [nil, false, true] do
|
||||
:erlang.register(name, pid)
|
||||
end
|
||||
|
||||
@@ -330,7 +290,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Returns the pid or port identifier with the registered name.
|
||||
Returns nil if the name is not registered.
|
||||
Returns `nil` if the name is not registered.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
||||
"""
|
||||
@@ -349,7 +309,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
|
||||
started from a certain shell should have another group leader than `:init`.
|
||||
started from a certain shell should have a group leader other than `:init`.
|
||||
"""
|
||||
@spec group_leader(pid, leader :: pid) :: true
|
||||
def group_leader(pid, leader) do
|
||||
@@ -357,7 +317,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of names which have been registered using register/2.
|
||||
Returns a list of names which have been registered using `register/2`.
|
||||
"""
|
||||
@spec registered :: [atom]
|
||||
def registered do
|
||||
@@ -379,19 +339,19 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets certain flags for the process Pid, in the same manner as flag/2.
|
||||
Returns the old value of the flag. The allowed values for Flag are
|
||||
only a subset of those allowed in flag/2, namely: save_calls.
|
||||
Sets certain flags for the process `pid`, in the same manner as `flag/2`.
|
||||
Returns the old value of the flag. The allowed values for `flag` are
|
||||
only a subset of those allowed in `flag/2`, namely: `save_calls`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
||||
"""
|
||||
@spec flag(pid, process_flag, term) :: term
|
||||
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
|
||||
def flag(pid, flag, value) do
|
||||
:erlang.process_flag(pid, flag, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid or nil if the process
|
||||
Returns information about the process identified by `pid` or `nil` if the process
|
||||
is not alive.
|
||||
Use this only for debugging information.
|
||||
|
||||
@@ -403,13 +363,23 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid
|
||||
or nil if the process is not alive.
|
||||
Returns information about the process identified by `pid`
|
||||
or `nil` if the process is not alive.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||
"""
|
||||
@spec info(pid, atom) :: {atom, term}
|
||||
def info(pid, spec) do
|
||||
@spec info(pid, atom) :: {atom, term} | nil
|
||||
def info(pid, spec)
|
||||
|
||||
def info(pid, :registered_name) do
|
||||
case :erlang.process_info(pid, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, []}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def info(pid, spec) when is_atom(spec) do
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
|
||||
+591
-232
@@ -1,225 +1,17 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
Functions for working with protocols.
|
||||
"""
|
||||
|
||||
# Callback for defprotocol.
|
||||
@doc false
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
@doc """
|
||||
Defines a new protocol function.
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol.DSL, only: :macros
|
||||
Protocols do not allow functions to be defined directly, instead, the
|
||||
regular `Kernel.def/*` macros are replaced by this macro which
|
||||
defines the protocol functions with the appropriate callbacks.
|
||||
"""
|
||||
defmacro def(signature)
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
# == Deprecated records handling ==
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when
|
||||
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for defimpl.
|
||||
@doc false
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour unquote(protocol)
|
||||
@protocol unquote(protocol)
|
||||
@for unquote(for)
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the given module is a protocol. Raises an error
|
||||
# if not loaded or not a protocol.
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, Atom.to_char_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
{arg, impl_for}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, "protocol functions expect at least one argument"
|
||||
end
|
||||
@@ -251,7 +43,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
|
||||
Protocol.__spec__?(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
@@ -260,21 +52,588 @@ defmodule Protocol.DSL do
|
||||
raise ArgumentError, "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{:module, Kernel.Typespec} ->
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
@doc """
|
||||
Checks if the given module is loaded and is protocol.
|
||||
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_protocol!(module) :: :ok | no_return
|
||||
def assert_protocol!(module) do
|
||||
assert_protocol!(module, "")
|
||||
end
|
||||
|
||||
found != []
|
||||
{:error, _} ->
|
||||
true
|
||||
defp assert_protocol!(module, extra) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> :ok
|
||||
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given module is loaded and is an implementation
|
||||
of the given protocol.
|
||||
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_impl!(module, module) :: :ok | no_return
|
||||
def assert_impl!(protocol, base) do
|
||||
assert_impl!(protocol, base, "")
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, base, extra) do
|
||||
impl = Module.concat(protocol, base)
|
||||
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError,
|
||||
"#{inspect impl} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
impl.__impl__(:protocol)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError,
|
||||
"#{inspect impl} is not an implementation of a protocol" <> extra
|
||||
else
|
||||
^protocol ->
|
||||
:ok
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Derive the `protocol` for `module` with the given options.
|
||||
"""
|
||||
defmacro derive(protocol, module, options \\ []) do
|
||||
quote do
|
||||
module = unquote(module)
|
||||
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
## Consolidation
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the protocols.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the implementations.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the protocol was consolidated.
|
||||
"""
|
||||
@spec consolidated?(module) :: boolean
|
||||
def consolidated?(protocol) do
|
||||
protocol.__info__(:attributes)[:protocol][:consolidated]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol.
|
||||
|
||||
Consolidation happens by changing the protocol `impl_for`
|
||||
in the abstract format to have fast lookup rules. Usually
|
||||
the list of implementations to use during consolidation
|
||||
are retrieved with the help of `extract_impls/2`.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Protocol.consolidated?(Enumerable)
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
However each implementation must be available and
|
||||
it will be loaded.
|
||||
"""
|
||||
@spec consolidate(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def consolidate(protocol, types) when is_atom(protocol) do
|
||||
beam_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
|
||||
opts = [:allow_missing_chunks]
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{@docs_chunk, docs}]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code, docs}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
atom when is_atom(atom) -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code, docs}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
structs = types -- all
|
||||
case change_impl_for(code, protocol, types, structs, false, []) do
|
||||
{:ok, ret} -> {:ok, {ret, docs}}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, load_impl(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, load_impl(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
defp load_impl(protocol, for) do
|
||||
Module.concat(protocol, for).__impl__(:target)
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({{protocol, code}, docs}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
unless docs == :missing_chunk do
|
||||
binary = :elixir_module.add_beam_chunk(binary, @docs_chunk, docs)
|
||||
end
|
||||
{:ok, binary}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
|
||||
@doc false
|
||||
def __protocol__(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol, only: [def: 1]
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote bind_quoted: [builtin: builtin] do
|
||||
@spec impl_for(term) :: atom() | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: atom() | no_return()
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:target)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
@spec __protocol__(:name) :: __MODULE__
|
||||
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __functions_spec__([]),
|
||||
do: []
|
||||
def __functions_spec__([h|t]),
|
||||
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
|
||||
|
||||
@doc false
|
||||
def __impl__(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
# Unquote the implementation just later
|
||||
# when all variables will already be injected
|
||||
# into the module body.
|
||||
__impl__ =
|
||||
quote unquote: false do
|
||||
@doc false
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
@spec __impl__(:target) :: __MODULE__
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
end
|
||||
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol!(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour protocol
|
||||
@protocol protocol
|
||||
@for for
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
unquote(__impl__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
|
||||
struct =
|
||||
if for == env.module do
|
||||
Module.get_attribute(for, :struct) ||
|
||||
raise "struct is not defined for #{inspect for}"
|
||||
else
|
||||
for.__struct__
|
||||
end
|
||||
|
||||
:lists.foreach(fn
|
||||
proto when is_atom(proto) ->
|
||||
derive(proto, for, struct, [], env)
|
||||
{proto, opts} when is_atom(proto) ->
|
||||
derive(proto, for, struct, opts, env)
|
||||
end, :lists.flatten(derives))
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
assert_impl!(protocol, Map, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
impl = Module.concat(protocol, Map)
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
if function_exported?(mod, fun, length(args)) do
|
||||
apply(mod, fun, args)
|
||||
else
|
||||
Module.create(Module.concat(protocol, for), quote do
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: unquote(protocol), for: unquote(for)]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(:target) :: unquote(impl)
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end, Macro.Env.location(env))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
defmodule Protocol.Consolidation do
|
||||
@moduledoc """
|
||||
Module responsible for consolidating protocols and helpers for
|
||||
extracting protocols and implementations from code paths for
|
||||
consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol. Consolidation happens
|
||||
by changing the protocol `impl_for` in the abstract
|
||||
format to have fast lookup rules.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Enumerable.__info__(:attributes)[:protocol]
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
|
||||
attributes: attributes]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({protocol, code}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
{:ok, binary}
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
|
||||
A Range are represented internally as a struct. However,
|
||||
A Range is represented internally as a struct. However,
|
||||
the most common form of creating and matching on ranges
|
||||
is via the `../2` macro, auto-imported from Kernel:
|
||||
|
||||
@@ -18,7 +18,8 @@ defmodule Range do
|
||||
|
||||
defstruct first: nil, last: nil
|
||||
|
||||
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
|
||||
@type t :: %Range{}
|
||||
@type t(first, last) :: %Range{first: first, last: last}
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
|
||||
+75
-37
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? {User, "jose", 27}
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -14,12 +14,26 @@ defmodule Record do
|
||||
|
||||
In Elixir, records are used mostly in two situations:
|
||||
|
||||
1. To work with short, internal data;
|
||||
2. To interface with Erlang records;
|
||||
1. to work with short, internal data
|
||||
2. to interface with Erlang records
|
||||
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
records while `extract/2` can be used to extract records from Erlang
|
||||
files.
|
||||
|
||||
## Types
|
||||
|
||||
Types can be defined for tuples with the `record/2` macro (only available
|
||||
in typespecs). Like with the generated record macros it will expand to
|
||||
a tuple.
|
||||
|
||||
defmodule MyModule do
|
||||
require Record
|
||||
Record.defrecord :user name: "john", age: 25
|
||||
|
||||
@type user :: record(:user, name: String.t, age: integer)
|
||||
# expands to: `@type user :: {:user, String.t, integer}`
|
||||
end
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -49,12 +63,12 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record, User)
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -77,15 +91,15 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record)
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
iex> Record.is_record(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -101,21 +115,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defmacros(name, values, env, tag \\ nil) do
|
||||
Record.Deprecated.defmacros(name, values, env, tag)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftypes(values, types, env) do
|
||||
Record.Deprecated.deftypes(values, types, env)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deffunctions(values, env) do
|
||||
Record.Deprecated.deffunctions(values, env)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a set of macros to create and access a record.
|
||||
|
||||
@@ -126,26 +125,31 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
defmodule User do
|
||||
Record.defrecord :user, [name: "José", age: "25"]
|
||||
require Record
|
||||
Record.defrecord :user, [name: "meg", age: "25"]
|
||||
end
|
||||
|
||||
In the example above, a set of macros named `user` but with different
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
user() #=> {:user, "José", 25}
|
||||
user(age: 26) #=> {:user, "José", 26}
|
||||
record = user() #=> {:user, "meg", 25}
|
||||
record = user(age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
user(record, :name) #=> "meg"
|
||||
|
||||
# To update the record
|
||||
user(record, age: 26) #=> {:user, "José", 26}
|
||||
user(record, age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# Convert a record to a keyword list
|
||||
user(record) #=> [name: "meg", age: 26]
|
||||
|
||||
By default, Elixir uses the record name as the first element of
|
||||
the tuple (the tag). But it can be changed to something else:
|
||||
|
||||
defmodule User do
|
||||
require Record
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
@@ -156,7 +160,7 @@ defmodule Record do
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecord, kv)
|
||||
fields = Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -174,7 +178,7 @@ defmodule Record do
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecordp, kv)
|
||||
fields = Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -190,9 +194,19 @@ defmodule Record do
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
:lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
|
||||
else
|
||||
val -> {key, val}
|
||||
end
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
other ->
|
||||
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
@@ -205,8 +219,15 @@ defmodule Record do
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
case Macro.expand(args, caller) do
|
||||
{:{}, _, [^atom|list]} when length(list) == length(fields) ->
|
||||
record = List.to_tuple([atom|list])
|
||||
Macro.escape(Record.__keyword__(atom, fields, record))
|
||||
{^atom, arg} when length(fields) == 1 ->
|
||||
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
|
||||
_ ->
|
||||
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -294,4 +315,21 @@ defmodule Record do
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
# Returns a keyword list of the record
|
||||
@doc false
|
||||
def __keyword__(atom, fields, record) do
|
||||
if is_record(record, atom) do
|
||||
[_tag|values] = Tuple.to_list(record)
|
||||
join_keyword(fields, values, [])
|
||||
else
|
||||
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
defp join_keyword([{field, _default}|fields], [value|values], acc),
|
||||
do: join_keyword(fields, values, [{field, value}| acc])
|
||||
defp join_keyword([], [], acc),
|
||||
do: :lists.reverse(acc)
|
||||
end
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defexception(name, fields, opts) do
|
||||
quote do
|
||||
fields = unquote(fields)
|
||||
defmodule unquote(name) do
|
||||
defexception fields
|
||||
unquote(Keyword.get opts, :do)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
|
||||
raise ArgumentError, "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{:ok, fields, types, def_type} ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = String.to_atom(Atom.to_string(name) <> "_t")
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
|
||||
recordp_split(t, [{field, default}|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = for value <- values do
|
||||
{name, default} = convert_value(value)
|
||||
{name, default, find_spec(types, name)}
|
||||
end
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = for value <- values do
|
||||
{key, value} = convert_value(value)
|
||||
{key, Macro.escape(value)}
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
@spec __record__(atom) :: term
|
||||
@spec __record__(atom, term) :: term
|
||||
@spec __record__(atom, term, term) :: term
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = {name, length(args)}
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end
|
||||
|
||||
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, "expected arguments to be a compile time atom or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for {_, value} <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(Atom.to_string(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{k, quote(do: :erlang.element(unquote(index + 2), record))}
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{:__exception__, _}|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{key, _default}|t], i) do
|
||||
update = String.to_atom "update_" <> Atom.to_string(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(Atom.to_string(key), key, values)
|
||||
|
||||
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
|
||||
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile {unquote(__MODULE__), :__before_compile__}
|
||||
@on_definition {unquote(__MODULE__), :__on_definition__}
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for {_, _, spec} <- values, do: spec
|
||||
options = for {k, _, v} <- values, do: {k, v}
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: {__MODULE__, unquote_splicing(types)}
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{key, _default, spec}|t], i, acc) do
|
||||
update = String.to_atom "update_" <> Atom.to_string(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
|
||||
|
||||
defp convert_value({atom, other}) when is_atom(atom), do:
|
||||
{atom, check_value(atom, other)}
|
||||
|
||||
defp convert_value({field, _}), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
for(i <- other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- Tuple.to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == {:env, []} and
|
||||
:erlang.fun_info(other, :type) == {:type, :external} do
|
||||
raise ArgumentError, "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = for {k, v} <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
+130
-75
@@ -22,53 +22,63 @@ defmodule Regex do
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
|
||||
strings to be given on match
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
Use `\A` and `\z` to match the end or beginning of the string
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped and
|
||||
allow `#` to delimit comments
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
|
||||
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
|
||||
It expects valid unicode strings to be given on match
|
||||
|
||||
* `caseless` (i) - add case insensitivity
|
||||
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to
|
||||
anycrlf; the new line setting can be overridden by setting `(*CR)` or
|
||||
`(*LF)` or `(*CRLF)` or `(*ANY)` according to re documentation
|
||||
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of
|
||||
each line; use `\A` and `\z` to match the end or beginning of the string
|
||||
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped
|
||||
and allow `#` to delimit comments
|
||||
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the
|
||||
first newline, though the matched text may continue over the newline
|
||||
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
|
||||
The options not available are:
|
||||
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or
|
||||
`(*ANYCRLF)` or `(*ANY)` at the beginning of the regexp according to the
|
||||
re documentation
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
* `:all` - all captured subpatterns including the complete matching string
|
||||
(this is the default)
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
* `:first` - only the first captured subpattern, which is always the
|
||||
complete matching part of the string; all explicitly captured subpatterns
|
||||
are discarded
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all
|
||||
explicitly captured subpatterns, but not the complete matching part of
|
||||
the string
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
* `:none` - do not return matching subpatterns at all
|
||||
|
||||
* `:all_names` - captures all names in the Regex;
|
||||
* `:all_names` - captures all names in the Regex
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
* `list(binary)` - a list of named captures to capture
|
||||
|
||||
"""
|
||||
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
defstruct re_pattern: nil, source: "", opts: ""
|
||||
|
||||
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
@@ -97,7 +107,7 @@ defmodule Regex do
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
case translate_options(options, []) do
|
||||
{:error, rest} ->
|
||||
{:error, {:invalid_option, rest}}
|
||||
|
||||
@@ -123,6 +133,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{:ok, regex} -> regex
|
||||
@@ -142,6 +153,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
@@ -158,6 +170,8 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec regex?(t) :: true
|
||||
@spec regex?(any) :: false
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@@ -167,9 +181,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -183,6 +197,7 @@ defmodule Regex do
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -213,6 +228,7 @@ defmodule Regex do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec named_captures(t, String.t, [term]) :: map | nil
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
@@ -223,6 +239,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
@spec re_pattern(t) :: term
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
@@ -236,6 +253,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec source(t) :: String.t
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
@@ -249,6 +267,7 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
@spec opts(t) :: String.t
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
@@ -262,6 +281,7 @@ defmodule Regex do
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
@@ -275,9 +295,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -291,6 +311,7 @@ defmodule Regex do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t, [term]) :: [[String.t]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -310,13 +331,17 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the
|
||||
given number of parts. If not specified, `:parts`
|
||||
is defaulted to `:infinity`, which will split the
|
||||
string into the maximum number of parts possible
|
||||
based on the given pattern.
|
||||
* `:parts` - when specified, splits the string into the given number of
|
||||
parts. If not specified, `:parts` defaults to `:infinity`, which will
|
||||
split the string into the maximum number of parts possible based on the
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
* `:trim` - when true, remove blank strings from the result.
|
||||
|
||||
* `:on` - specifies which captures and order to split the string
|
||||
on. Check the moduledoc for `Regex` to see the possible capture
|
||||
values. Defaults to `:first` which means captures inside the
|
||||
Regex does not affect the split result.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -332,27 +357,61 @@ defmodule Regex do
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc")
|
||||
["", ""]
|
||||
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
|
||||
["a", "c"]
|
||||
|
||||
"""
|
||||
|
||||
@spec split(t, String.t, [term]) :: [String.t]
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
opts = [return: :binary, parts: zero_to_infinity(parts)]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
def split(%Regex{}, "", _opts), do: [""]
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
do_split(matches, string, 0,
|
||||
parts_to_index(Keyword.get(opts, :parts, :infinity)),
|
||||
Keyword.get(opts, :trim, false))
|
||||
:match ->
|
||||
[string]
|
||||
:nomatch ->
|
||||
[string]
|
||||
end
|
||||
end
|
||||
|
||||
defp zero_to_infinity(0), do: :infinity
|
||||
defp zero_to_infinity(n), do: n
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp do_split(_, string, offset, _counter, true) when byte_size(string) <= offset,
|
||||
do: []
|
||||
|
||||
defp do_split(_, string, offset, 1, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([], string, offset, _counter, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
|
||||
do: do_split([h|t], string, offset, counter, trim)
|
||||
|
||||
defp do_split([[]|t], string, offset, counter, trim),
|
||||
do: do_split(t, string, offset, counter, trim)
|
||||
|
||||
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
|
||||
new_offset = pos + length
|
||||
keep = pos - offset
|
||||
|
||||
if keep == 0 and (length == 0 or trim) do
|
||||
do_split([h|t], string, new_offset, counter, trim)
|
||||
else
|
||||
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
|
||||
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
@@ -370,8 +429,8 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -391,6 +450,7 @@ defmodule Regex do
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
@@ -465,12 +525,12 @@ defmodule Regex do
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched :: [size(length), binary], rest :: binary>> = string
|
||||
<<untouched :: binary-size(length), rest :: binary>> = string
|
||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
|
||||
<<_ :: [size(length), binary], rest :: binary>> = string
|
||||
<<_ :: size(length)-binary, rest :: binary>> = string
|
||||
new_data = apply_replace(whole, replacement, head)
|
||||
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
||||
end
|
||||
@@ -503,7 +563,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp get_index(string, {pos, len}) do
|
||||
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
|
||||
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
|
||||
res
|
||||
end
|
||||
|
||||
@@ -553,18 +613,13 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp translate_options(<<?g, t :: binary>>) do
|
||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: {:error, rest}
|
||||
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
|
||||
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
|
||||
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
|
||||
defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
|
||||
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||
defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
|
||||
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
|
||||
defp translate_options(<<>>, acc), do: acc
|
||||
defp translate_options(rest, _acc), do: {:error, rest}
|
||||
end
|
||||
|
||||
@@ -198,7 +198,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+180
-60
@@ -76,8 +76,8 @@ defmodule Stream do
|
||||
There are many functions in Elixir's standard library that return
|
||||
streams, some examples are:
|
||||
|
||||
* `IO.stream/2` - Streams input lines, one by one;
|
||||
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
|
||||
* `IO.stream/2` - streams input lines, one by one
|
||||
* `URI.query_decoder/1` - decodes a query string, pair by pair
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
@@ -89,12 +89,14 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
@type acc :: any
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@type index :: non_neg_integer
|
||||
@type index :: non_neg_integer
|
||||
@type default :: any
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
# Require Stream.Reducers and its callbacks
|
||||
require Stream.Reducers, as: R
|
||||
@@ -154,18 +156,22 @@ defmodule Stream do
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
fn(f1) -> &do_chunk(&1, n, pad, f1) end
|
||||
if is_nil(pad) do
|
||||
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
|
||||
else
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
&do_chunk(&1, n, pad, &2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
|
||||
{:cont, acc}
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -184,7 +190,7 @@ defmodule Stream do
|
||||
def chunk_by(enum, fun) do
|
||||
lazy enum, nil,
|
||||
fn(f1) -> R.chunk_by(fun, f1) end,
|
||||
fn(f1) -> &do_chunk_by(&1, f1) end
|
||||
&do_chunk_by(&1, &2)
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
|
||||
@@ -336,6 +342,25 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a value every `n` milliseconds.
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.interval(10) |> Enum.take(10)
|
||||
[0,1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t
|
||||
def interval(n) do
|
||||
unfold 0, fn (count) ->
|
||||
:timer.sleep(n)
|
||||
{count, count + 1}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
@@ -420,13 +445,14 @@ defmodule Stream do
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|> Stream.run
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -560,6 +586,22 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t
|
||||
def timer(n) do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -574,7 +616,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
`Stream.transform/3` is a useful as it can be used as basis to implement
|
||||
`Stream.transform/3` is useful as it can be used as the basis to implement
|
||||
many of the functions defined in this module. For example, we can implement
|
||||
`Stream.take(enum, n)` as follows:
|
||||
|
||||
@@ -615,12 +657,14 @@ defmodule Stream do
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
{list, user_acc} when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{:halt, _user_acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, elem(inner_acc, 1)}
|
||||
{other, user_acc} ->
|
||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
||||
do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||
&Enumerable.reduce(other, &1, inner))
|
||||
end
|
||||
{reason, _} ->
|
||||
{reason, elem(inner_acc, 1)}
|
||||
@@ -632,8 +676,9 @@ defmodule Stream do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
@@ -645,29 +690,31 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
|
||||
defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
reduce.({op, [:outer|inner_acc]})
|
||||
catch
|
||||
{:stream_transform, h} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{_, acc} ->
|
||||
{:halted, [:outer|acc]} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
{:halted, [:inner|acc]} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:done, [_|acc]} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_each(x, acc, f) do
|
||||
defp do_transform_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, h} -> throw({:stream_transform, h})
|
||||
{_, _} = o -> o
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -839,6 +886,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable)
|
||||
|
||||
def cycle(enumerable) when is_list(enumerable) do
|
||||
unfold {enumerable, enumerable}, fn
|
||||
{source, [h | t]} -> {h, {source, t}}
|
||||
{source = [h | t], []} -> {h, {source, t}}
|
||||
end
|
||||
end
|
||||
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
@@ -926,14 +982,15 @@ defmodule Stream do
|
||||
@doc """
|
||||
Emits a sequence of values for the given resource.
|
||||
|
||||
Similar to `unfold/2` but the initial value is computed lazily via
|
||||
`start_fun` and executes an `after_fun` at the end of enumeration
|
||||
(both in cases of success and failure).
|
||||
Similar to `transform/2` but the initial accumulated value is
|
||||
computed lazily via `start_fun` and executes an `after_fun` at
|
||||
the end of enumeration (both in cases of success and failure).
|
||||
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple with the current and
|
||||
next accumulator. The enumeration finishes if it returns `nil`.
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
resources.
|
||||
@@ -943,8 +1000,8 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
data when is_binary(data) -> {[data], file}
|
||||
_ -> {:halt, file}
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
@@ -966,9 +1023,11 @@ defmodule Stream do
|
||||
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
# Optimize the most common cases
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
|
||||
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
|
||||
{_, _} = other -> other
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -976,11 +1035,62 @@ defmodule Stream do
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
{:halt, next_acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{list, next_acc} when is_list(list) ->
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{enum, next_acc} ->
|
||||
inner = &do_resource_each(&1, &2, fun)
|
||||
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.reduce(enum, &1, inner))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.({op, [:outer|acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, [:inner|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:done, [_|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_resource_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -988,7 +1098,7 @@ defmodule Stream do
|
||||
Emits a sequence of values for the given accumulator.
|
||||
|
||||
Successive values are generated by calling `next_fun` with the previous
|
||||
accumulator and it must return a tuple with the current and next
|
||||
accumulator and it must return a tuple with the current value and next
|
||||
accumulator. The enumeration finishes if it returns `nil`.
|
||||
|
||||
## Examples
|
||||
@@ -1021,12 +1131,12 @@ defmodule Stream do
|
||||
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(%Stream{funs: funs} = lazy, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: %Stream{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
|
||||
@@ -1065,19 +1175,29 @@ defimpl Enumerable, for: Stream do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
{:halted, accs} ->
|
||||
do_done {:halted, accs}, done
|
||||
{:done, accs} ->
|
||||
do_done {:done, accs}, done
|
||||
end
|
||||
end
|
||||
|
||||
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
|
||||
defp do_done({reason, [acc|t]}, {done, fun}) do
|
||||
[h|_] = Enum.reverse(t)
|
||||
case done.([acc, h], fun) do
|
||||
{:cont, [acc|_]} -> {reason, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Stream do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(%{enum: enum, funs: funs}, opts) do
|
||||
inner = [enum: enum, funs: Enum.reverse(funs)]
|
||||
concat ["#Stream<", to_doc(inner, opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+279
-169
@@ -4,28 +4,12 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
In addition to this module, Elixir provides more low-level
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.As per the standard, a codepoint is
|
||||
an Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
@@ -66,34 +50,110 @@ defmodule String do
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
To act accordingly to the Unicode Standard, many functions
|
||||
in this module runs in linear time, as it needs to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
the input grows. On the other hand, `byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
This means often there are performance costs in using the
|
||||
functions in this module, compared to the more low-level
|
||||
operations that work directly with binaries:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
There are many situations where using the `String` module can
|
||||
be avoided in favor of binary functions or pattern matching.
|
||||
For example, imagine you have a string `prefix` and you want to
|
||||
remove this prefix from another string named `full`.
|
||||
|
||||
One may be tempted to write:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base, String.length(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Although the function above works, it performs poorly. To
|
||||
calculate the length of the string, we need to traverse it
|
||||
fully, so we traverse both `prefix` and `full` strings, then
|
||||
slice the `full` one, traversing it again.
|
||||
|
||||
A first attempting at improving it could be with ranges:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base..-1)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
While this is much better (we don't traverse `full` twice),
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `byte_size/1`
|
||||
and `binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> binary_part(full, base, byte_size(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint in multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` special macro:
|
||||
codepoint. One may use the `?` construct:
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
iex> ?o
|
||||
111
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
iex> ?á
|
||||
225
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
iex> << eacute :: utf8 >> = "á"
|
||||
iex> eacute
|
||||
233
|
||||
225
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
"jos\x{0065}\x{0301}" #=>
|
||||
"josé"
|
||||
"ol\x{0061}\x{0301}" #=>
|
||||
"olá"
|
||||
|
||||
## Self-synchronization
|
||||
|
||||
@@ -129,7 +189,7 @@ defmodule String do
|
||||
@spec printable?(t) :: boolean
|
||||
|
||||
def printable?(<< h :: utf8, t :: binary >>)
|
||||
when h in ?\040..?\176
|
||||
when h in 0x20..0x7E
|
||||
when h in 0xA0..0xD7FF
|
||||
when h in 0xE000..0xFFFD
|
||||
when h in 0x10000..0x10FFFF do
|
||||
@@ -227,40 +287,53 @@ defmodule String do
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options \\ [])
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
def split(string, "", options) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
|
||||
end
|
||||
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
def split(string, pattern, options) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
Regex.split(pattern, string, options)
|
||||
else
|
||||
splits =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
_ ->
|
||||
:binary.split(binary, pattern, [:global])
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
if parts == :infinity and trim == false do
|
||||
:binary.split(string, pattern, [:global])
|
||||
else
|
||||
splits
|
||||
split_parts(string, pattern, parts_to_index(parts), trim)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp split_parts("", _pattern, _num), do: [""]
|
||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
case :binary.split(binary, pattern) do
|
||||
[head] -> Enum.reverse([head|parts])
|
||||
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp split_codepoints(binary, 1, _trim), do: [binary]
|
||||
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
|
||||
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
|
||||
defp split_codepoints(<<h, t :: binary>>, count, trim),
|
||||
do: [<<h>>|split_codepoints(t, count - 1, trim)]
|
||||
defp split_codepoints(<<>>, _, true), do: []
|
||||
defp split_codepoints(<<>>, _, false), do: [""]
|
||||
|
||||
defp split_parts("", _pattern, _num, true), do: []
|
||||
defp split_parts("", _pattern, _num, _trim), do: [""]
|
||||
defp split_parts(string, _pattern, 1, _trim), do: [string]
|
||||
defp split_parts(string, pattern, num, trim) do
|
||||
case :binary.split(string, pattern) do
|
||||
[""] when trim ->
|
||||
[]
|
||||
[head] ->
|
||||
[head]
|
||||
[head, tail] ->
|
||||
if trim and head == "" do
|
||||
split_parts(tail, pattern, num, trim)
|
||||
else
|
||||
[head|split_parts(tail, pattern, num-1, trim)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,8 +395,8 @@ defmodule String do
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
iex> String.upcase("olá")
|
||||
"OLÁ"
|
||||
|
||||
"""
|
||||
@spec upcase(t) :: t
|
||||
@@ -340,8 +413,8 @@ defmodule String do
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
iex> String.downcase("OLÁ")
|
||||
"olá"
|
||||
|
||||
"""
|
||||
@spec downcase(t) :: t
|
||||
@@ -364,8 +437,8 @@ defmodule String do
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
iex> String.capitalize("olá")
|
||||
"Olá"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
@@ -405,7 +478,7 @@ defmodule String do
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) when char in 0..127 do
|
||||
if :binary.last(string) == char do
|
||||
do_rstrip(string, "", char)
|
||||
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
|
||||
else
|
||||
string
|
||||
end
|
||||
@@ -599,8 +672,8 @@ defmodule String do
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
@spec replace(t, t | Regex.t, t) :: t
|
||||
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
@@ -672,8 +745,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
@@ -699,8 +772,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_codepoint("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_codepoint("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_codepoint: 1}
|
||||
@@ -767,6 +840,65 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc ~S"""
|
||||
Splits the string into chunks of characters that share a common trait.
|
||||
|
||||
The trait can be one of two options:
|
||||
|
||||
* `:valid` – the string is split into chunks of valid and invalid character
|
||||
sequences
|
||||
|
||||
* `:printable` – the string is split into chunks of printable and
|
||||
non-printable character sequences
|
||||
|
||||
Returns a list of binaries each of which contains only one kind of
|
||||
characters.
|
||||
|
||||
If the given string is empty, an empty list is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
|
||||
["abc\0"]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
|
||||
["abc\0", <<0x0ffff::utf8>>]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
|
||||
["abc", <<0, 0x0ffff::utf8>>]
|
||||
|
||||
"""
|
||||
@spec chunk(t, :valid | :printable) :: [t]
|
||||
|
||||
def chunk(string, trait)
|
||||
|
||||
def chunk("", _), do: []
|
||||
|
||||
def chunk(str, trait) when trait in [:valid, :printable] do
|
||||
{cp, _} = next_codepoint(str)
|
||||
pred_fn = make_chunk_pred(trait)
|
||||
do_chunk(str, pred_fn.(cp), pred_fn)
|
||||
end
|
||||
|
||||
|
||||
defp do_chunk(str, flag, pred_fn), do: do_chunk(str, [], <<>>, flag, pred_fn)
|
||||
|
||||
defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc)
|
||||
|
||||
defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk])
|
||||
|
||||
defp do_chunk(str, acc, chunk, flag, pred_fn) do
|
||||
{cp, rest} = next_codepoint(str)
|
||||
if pred_fn.(cp) != flag do
|
||||
do_chunk(rest, [chunk|acc], cp, not flag, pred_fn)
|
||||
else
|
||||
do_chunk(rest, acc, chunk <> cp, flag, pred_fn)
|
||||
end
|
||||
end
|
||||
|
||||
defp make_chunk_pred(:valid), do: &valid?/1
|
||||
defp make_chunk_pred(:printable), do: &printable?/1
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
@@ -790,8 +922,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_grapheme("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_grapheme("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_grapheme: 1}
|
||||
@@ -868,7 +1000,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
If `position` is greater than `string` length, then it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -915,7 +1047,12 @@ defmodule String do
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
a length given by the second.
|
||||
If the offset is greater than string length, than it returns `nil`.
|
||||
|
||||
If the offset is greater than string length, then it returns `""`.
|
||||
|
||||
Remember this function works with unicode codepoints and consider
|
||||
the slices to represent codepoints offsets. If you want to split
|
||||
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -926,13 +1063,13 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
@@ -941,36 +1078,53 @@ defmodule String do
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
@spec slice(t, integer, integer) :: grapheme
|
||||
|
||||
def slice(string, start, 0) do
|
||||
case abs(start) <= length(string) do
|
||||
true -> ""
|
||||
false -> nil
|
||||
end
|
||||
def slice(_, _, 0) do
|
||||
""
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
|
||||
case do_count_bytes(next_grapheme(string), start, 0) do
|
||||
{nil, _} -> ""
|
||||
{next, start_bytes} ->
|
||||
{_, len_bytes} = do_count_bytes(next, len, 0)
|
||||
binary_part(string, start_bytes, len_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
false -> nil
|
||||
start = length(string) + start
|
||||
case start >= 0 do
|
||||
true -> slice(string, start, len)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_count_bytes(next, 0, acc), do: {next, acc}
|
||||
defp do_count_bytes(nil, _, acc), do: {nil, acc}
|
||||
|
||||
defp do_count_bytes({char, rest}, counter, acc) do
|
||||
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a substring from the offset given by the start of the
|
||||
range to the offset given by the end of the range.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `nil`.
|
||||
string or if the range is in reverse order, returns `""`.
|
||||
|
||||
If the start or end of the range are negative, the whole string
|
||||
is traversed first in order to convert the negative indexes into
|
||||
positive ones.
|
||||
|
||||
Remember this function works with unicode codepoints and consider
|
||||
the slices to represent codepoints offsets. If you want to split
|
||||
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -981,7 +1135,7 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
@@ -993,10 +1147,10 @@ defmodule String do
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
@@ -1004,59 +1158,51 @@ defmodule String do
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t | nil
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
def slice("", _.._), do: ""
|
||||
|
||||
def slice(string, first..-1) when first >= 0 do
|
||||
case do_count_bytes(next_grapheme(string), first, 0) do
|
||||
{nil, _} -> ""
|
||||
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
if last >= first do
|
||||
slice(string, first, last - first + 1)
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, first..last) do
|
||||
total = length(string)
|
||||
{bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
|
||||
|
||||
if first < 0 do
|
||||
first = total + first
|
||||
end
|
||||
if first < 0, do: first = length + first
|
||||
if last < 0, do: last = length + last
|
||||
|
||||
if last < 0 do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
if first < 0 or first > last or first > length do
|
||||
""
|
||||
else
|
||||
last = min(last + 1, length)
|
||||
bytes = Enum.drop(bytes, length - last)
|
||||
first = last - first
|
||||
{length_bytes, start_bytes} = Enum.split(bytes, first)
|
||||
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
nil
|
||||
defp do_acc_bytes({char, rest}, bytes, length) do
|
||||
do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
|
||||
end
|
||||
|
||||
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
|
||||
end
|
||||
|
||||
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
|
||||
end
|
||||
|
||||
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
end
|
||||
defp do_acc_bytes(nil, bytes, length) do
|
||||
{bytes, length}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1124,8 +1270,8 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
string_size = byte_size(string)
|
||||
suffix_size = byte_size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
@@ -1277,7 +1423,7 @@ defmodule String do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
@spec to_integer(String.t, 2..36) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
@@ -1297,40 +1443,4 @@ defmodule String do
|
||||
def to_float(string) do
|
||||
:erlang.binary_to_float(string)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
GenServer.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +68,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
GenServer.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
@@ -199,7 +199,7 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
<<chars :: binary-size(n), rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
@@ -209,7 +209,7 @@ defmodule StringIO do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
<<chars :: binary-size(split_pos), rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
defmodule Supervisor do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing supevision functionality.
|
||||
A behaviour module for implementing supervision functionality.
|
||||
|
||||
A supervisor is a process which supervises other processes called
|
||||
child processes. Supervisors are used to build an hierarchical process
|
||||
structure called a supervision tree, a nice way to structure a fault
|
||||
tolerant applications.
|
||||
structure called a supervision tree, a nice way to structure fault-tolerant
|
||||
applications.
|
||||
|
||||
A supervisor implemented using this module will have a standard set
|
||||
of interface functions and include functionality for tracing and error
|
||||
@@ -28,7 +28,7 @@ defmodule Supervisor do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, _from, t) do
|
||||
def handle_cast({:push, h}, t) do
|
||||
{:noreply, [h|t]}
|
||||
end
|
||||
end
|
||||
@@ -44,7 +44,7 @@ defmodule Supervisor do
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one children
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
@@ -73,12 +73,12 @@ defmodule Supervisor do
|
||||
|
||||
GenServer.call(:sup_stack, :pop) == :hello
|
||||
|
||||
Supervisors support different strategies, in the example above, we
|
||||
Supervisors support different strategies; in the example above, we
|
||||
have chosen `:one_for_one`. Furthermore, each supervisor can have many
|
||||
workers and supervisors as children, each of them with their specific
|
||||
configuration, shutdown values and restart strategies.
|
||||
configuration, shutdown values, and restart strategies.
|
||||
|
||||
Continue reading this module to learn more about supervision strategies
|
||||
Continue reading this moduledoc to learn more about supervision strategies
|
||||
and then follow to the `Supervisor.Spec` module documentation to learn
|
||||
about the specification for workers and supervisors.
|
||||
|
||||
@@ -86,7 +86,7 @@ defmodule Supervisor do
|
||||
|
||||
In the example above, a supervisor was dynamically created by passing
|
||||
the supervision structure to `start_link/2`. However, supervisors
|
||||
can also be created by explicitly defining a supevision module:
|
||||
can also be created by explicitly defining a supervision module:
|
||||
|
||||
defmodule MyApp.Supervisor do
|
||||
use Supervisor
|
||||
@@ -96,51 +96,49 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
supervisor(children, strategy: :one_for_one)
|
||||
supervise(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table;
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table.
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps;
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
* `:one_for_one` - if a child process terminates, only that
|
||||
process is restarted.
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
* `:one_for_all` - if a child process terminates, all other child
|
||||
processes are terminated and then all child processes (including
|
||||
the terminated one) are restarted.
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
* `:rest_for_one` - if a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
one in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted.
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used;
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one child. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Name registering
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registering rules as a `GenServer`.
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@@ -181,22 +179,26 @@ defmodule Supervisor do
|
||||
the `:max_restarts` and `:max_seconds` value can be configured
|
||||
as described in `Supervisor.Spec.supervise/2` docs.
|
||||
|
||||
The options can also be used to register a supervisor name,
|
||||
the supported values are described under the `Name Registering`
|
||||
The options can also be used to register a supervisor name.
|
||||
the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the supervisor and its child processes are successfully created
|
||||
(i.e. if all child process start functions return `{:ok, child}`,
|
||||
(i.e. if the start function of all child processes returns `{:ok, child}`,
|
||||
`{:ok, child, info}`, or `:ignore`) the function returns
|
||||
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
|
||||
already exists a process with the specified name the function returns
|
||||
already exists a process with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}`, where pid is the pid of that
|
||||
process.
|
||||
|
||||
If any child process start function fails or returns an error tuple or
|
||||
If any of the child process start functions fail or return an error tuple or
|
||||
an erroneous value, the supervisor will first terminate all already
|
||||
started child processes with reason `:shutdown` and then terminate
|
||||
itself and return `{:error, {:shutdown, reason}}`.
|
||||
|
||||
Note that the `Supervisor` is linked to the parent process
|
||||
and will exit not only on crashes but also if the parent process
|
||||
exits with `:normal` reason.
|
||||
"""
|
||||
@spec start_link([tuple], options) :: on_start
|
||||
def start_link(children, options) when is_list(children) do
|
||||
@@ -215,11 +217,11 @@ defmodule Supervisor do
|
||||
If the `init/1` callback returns `:ignore`, this function returns
|
||||
`:ignore` as well and the supervisor terminates with reason `:normal`.
|
||||
If it fails or returns an incorrect value, this function returns
|
||||
`{:error, term} where term is a term with information about the
|
||||
`{:error, term}` where `term` is a term with information about the
|
||||
error, and the supervisor terminates with reason `term`.
|
||||
|
||||
The `:name` option can also be given in order to register a supervisor
|
||||
name, the supported values are described under the `Name Registering`
|
||||
name, the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
Other failure conditions are specified in `start_link/2` docs.
|
||||
@@ -243,7 +245,7 @@ defmodule Supervisor do
|
||||
is a `:simple_one_for_one` supervisor, see below). The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
In the case of a `:simple_one_for_one`, the child specification defined in
|
||||
In the case of `:simple_one_for_one`, the child specification defined in
|
||||
the supervisor will be used and instead of a `child_spec`, an arbitrary list
|
||||
of terms is expected. The child process will then be started by appending
|
||||
the given list to the existing function arguments in the child specification.
|
||||
@@ -262,33 +264,37 @@ defmodule Supervisor do
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the child specification is discarded and the function returns
|
||||
`{:error, error}` where error is a term containing information about the error
|
||||
`{:error, error}` where `error` is a term containing information about the error
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
|
||||
If the supervisor is not a `simple_one_for_one`, the child id is expected
|
||||
and the process, if there is one, is terminated and, the child specification is
|
||||
and the process, if there is one, is terminated; the child specification is
|
||||
kept unless the child is temporary.
|
||||
|
||||
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
|
||||
specification identifier is given instead instead of a `pid`, the function will
|
||||
specification identifier is given instead of a `pid`, the function will
|
||||
return `{:error, :simple_one_for_one}`.
|
||||
|
||||
Non-temporary child process may later be restarted by the supervisor. The child
|
||||
A non-temporary child process may later be restarted by the supervisor. The child
|
||||
process can also be restarted explicitly by calling `restart_child/2`. Use
|
||||
`delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, the function returns ok. If there is no child specification or
|
||||
If successful, the function returns `:ok`. If there is no child specification or
|
||||
pid, the function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
@@ -304,7 +310,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
def delete_child(supervisor, child_id) do
|
||||
call(supervisor, {:delete_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
@@ -333,7 +341,9 @@ defmodule Supervisor do
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
@@ -343,41 +353,53 @@ defmodule Supervisor do
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
* the `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor;
|
||||
* `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor
|
||||
|
||||
* the `child` - the pid of the corresponding child process, the atom `:restarting`
|
||||
if the process is about to be restarted or `:undefined` if there is no such process;
|
||||
* `child` - the pid of the corresponding child process, the atom
|
||||
`:restarting` if the process is about to be restarted, or `:undefined` if
|
||||
there is no such process
|
||||
|
||||
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
|
||||
* `type` - `:worker` or `:supervisor` as defined in the child specification
|
||||
|
||||
* the `modules` as defined in the child specification;
|
||||
* `modules` – as defined in the child specification
|
||||
"""
|
||||
@spec which_children(supervisor) ::
|
||||
[{Supervisor.Spec.child_id | :undefined,
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count value for the supervisor.
|
||||
Returns a map containing count values for the supervisor.
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - the total count of children, dead or alive;
|
||||
* `:specs` - the total count of children, dead or alive
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by this supervisor;
|
||||
* `:active` - the count of all actively running child processes managed by
|
||||
this supervisor
|
||||
|
||||
* `:supervisors` - the count of all supervisors whether or not the child process is still alive;
|
||||
* `:supervisors` - the count of all supervisors whether or not the child
|
||||
process is still alive
|
||||
|
||||
* `:workers` - the count of all workers, whether or not the child process is still alive;
|
||||
* `:workers` - the count of all workers, whether or not the child process
|
||||
is still alive
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
%{specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer}
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
defmodule Supervisor.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :supervisor
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of children (worker or supervisors) to
|
||||
supervise and a set of options. Returns a tuple containing
|
||||
the supervisor specification.
|
||||
|
||||
## Examples
|
||||
|
||||
supervise children, strategy: :one_for_one
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`;
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children;
|
||||
|
||||
"""
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given to supervise"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
@child_doc """
|
||||
## Options
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the module name;
|
||||
|
||||
* `:function` - the function to invoke on the child to start it.
|
||||
Defaults to `:start_link`;
|
||||
|
||||
* `:restart` - defines when the child process should restart.
|
||||
Defaults to `:permanent`;
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated.
|
||||
Defaults to `5000` for a worker and `:infinity` for a supervisor;
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a supervisor, `gen_server` or `gen_fsm`. If the
|
||||
child process is a gen_event, modules should be `:dynamic`.
|
||||
Defaults to a list with the given module;
|
||||
|
||||
## Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
## Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `exit(child, :shutdown)` and
|
||||
then waits for an exit signal back. If no exit signal is received within
|
||||
the specified time (in miliseconds), the child process is unconditionally
|
||||
terminated using `exit(child, :kill)`;
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a worker which will be started
|
||||
with the given arguments.
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a supervisor which will be started
|
||||
with the given arguments.
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, [module])
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
end
|
||||
@@ -40,64 +40,65 @@ defmodule Supervisor.Spec do
|
||||
|
||||
Explicit supervisors as above are required when there is a need to:
|
||||
|
||||
1. partialy change the supervision tree during hot-code swaps;
|
||||
1. Partialy change the supervision tree during hot-code swaps.
|
||||
|
||||
2. define supervisors inside other supervisors;
|
||||
2. Define supervisors inside other supervisors.
|
||||
|
||||
3. perform actions inside the supervision `init/1` callback.
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too);
|
||||
3. Perform actions inside the supervision `init/1` callback.
|
||||
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too).
|
||||
|
||||
## Supervisor and worker options
|
||||
|
||||
In the example above, we have defined workers and supervisors
|
||||
and each accepts the following options:
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the given module
|
||||
name;
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor; defaults to the given module
|
||||
name
|
||||
|
||||
* `:function` - the function to invoke on the child to start it;
|
||||
* `:function` - the function to invoke on the child to start it
|
||||
|
||||
* `:restart` - defines when the child process should restart;
|
||||
* `:restart` - defines when the child process should restart
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated;
|
||||
* `:shutdown` - defines how a child process should be terminated
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer` If the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`;
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer`; if the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`
|
||||
|
||||
### Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
* `:permanent` - the child process is always restarted
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`
|
||||
|
||||
### Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`.
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care.
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`;
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`.
|
||||
"""
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@@ -138,19 +139,19 @@ defmodule Supervisor.Spec do
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module;
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 3.
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds. Please check the `Supervisor` module for
|
||||
The `:strategy` option is required and by default maximum 3 restarts
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@spec supervise([spec], strategy: strategy,
|
||||
@@ -161,7 +162,7 @@ defmodule Supervisor.Spec do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxR = Keyword.get(options, :max_restarts, 3)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
@@ -188,7 +189,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
@@ -212,7 +213,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
|
||||
+153
-51
@@ -60,9 +60,9 @@ defmodule System do
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
%{version: version, tag: get_describe, date: get_date}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,11 +94,20 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
@@ -153,11 +162,11 @@ defmodule System do
|
||||
Returns a writable temporary directory.
|
||||
Searches for directories in the following order:
|
||||
|
||||
1. The directory named by the TMPDIR environment variable
|
||||
2. The directory named by the TEMP environment variable
|
||||
3. The directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. As a last resort, the current working directory
|
||||
1. the directory named by the TMPDIR environment variable
|
||||
2. the directory named by the TEMP environment variable
|
||||
3. the directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. as a last resort, the current working directory
|
||||
|
||||
Returns `nil` if none of the above are writable.
|
||||
"""
|
||||
@@ -202,59 +211,32 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register a program exit handler function.
|
||||
Registers a program exit handler function.
|
||||
|
||||
Registers a function that will be invoked
|
||||
at the end of program execution. Useful for
|
||||
invoking a hook in "script" mode.
|
||||
Registers a function that will be invoked at the end of program execution.
|
||||
Useful for invoking a hook in "script" mode.
|
||||
|
||||
The function must receive the exit status code
|
||||
as an argument.
|
||||
The handler always executes in a different process from the one it was
|
||||
registered in. As a consequence, any resources managed by the calling process
|
||||
(ETS tables, open files, etc.) won't be available by the time the handler
|
||||
function is invoked.
|
||||
|
||||
The function must receive the exit status code as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute a system command.
|
||||
|
||||
Executes `command` in a command shell of the target OS,
|
||||
captures the standard output of the command and returns
|
||||
the result as a binary.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Otherwise a string, correctly encoded in UTF-8, is expected.
|
||||
"""
|
||||
@spec cmd(String.t) :: String.t
|
||||
@spec cmd(char_list) :: char_list
|
||||
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
List.to_string :os.cmd(String.to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locate an executable on the system.
|
||||
Locates an executable on the system.
|
||||
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
|
||||
If `program` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(binary) :: binary | nil
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
case :os.find_executable(String.to_char_list(program)) do
|
||||
false -> nil
|
||||
@@ -355,14 +337,14 @@ defmodule System do
|
||||
Halts the Erlang runtime system where the argument `status` must be a
|
||||
non-negative integer, the atom `:abort` or a binary.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system;
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system.
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system.
|
||||
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1.
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
@@ -387,4 +369,124 @@ defmodule System do
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(String.to_char_list(status))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Executes the given `command` with `args`.
|
||||
|
||||
`command` is expected to be an executable available in PATH
|
||||
unless an absolute path is given.
|
||||
|
||||
`args` must be a list of strings which are not expanded
|
||||
in any way. For example, this means wildcard expansion will
|
||||
not happen unless `Path.wildcard/2` is used. On Windows though,
|
||||
wildcard expansion is up to the program.
|
||||
|
||||
A set of options are also supported and described below.
|
||||
|
||||
## Options
|
||||
|
||||
* `:into` - injects the result into the given collectable, defaults to `""`
|
||||
* `:cd` - the directory to run the command in
|
||||
* `:env` - an enumerable of tuples containing environment key-value as binary
|
||||
* `:arg0` - set the command arg0
|
||||
* `:stderr_to_stdout` - redirects stderr to stdout when true
|
||||
* `:parallelism` - when true, the VM will schedule port tasks to improve
|
||||
parallelism in the system. If set to false, the VM will try to perform
|
||||
commands immediately, improving latency at the expense of parallelism.
|
||||
The default can be set on system startup by passing the "+spp" argument
|
||||
to `--erl`.
|
||||
|
||||
## Error reasons
|
||||
|
||||
If invalid arguments are given, `ArgumentError` is raised by
|
||||
`System.cmd/3`. `System.cmd/3` also expects a strict set of
|
||||
options and will raise if unknown or invalid options are given.
|
||||
|
||||
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
|
||||
detailed below:
|
||||
|
||||
* `:system_limit` - all available ports in the Erlang emulator are in use
|
||||
|
||||
* `:enomem` - there was not enough memory to create the port
|
||||
|
||||
* `:eagain` - there are no more available operating system processes
|
||||
|
||||
* `:enametoolong` - the external command given was too long
|
||||
|
||||
* `:emfile` - there are no more available file descriptors
|
||||
(for the operating system process that the Erlang emulator runs in)
|
||||
|
||||
* `:enfile` - the file table is full (for the entire operating system)
|
||||
|
||||
* `:eacces` - the command does not point to an executable file
|
||||
|
||||
* `:enoent` - the command does not point to an existing file
|
||||
|
||||
## Shell commands
|
||||
|
||||
If you desire to execute a trusted command inside a shell, with pipes,
|
||||
redirecting and so on, please check
|
||||
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
|
||||
"""
|
||||
@spec cmd(binary, [binary], Keyword.t) ::
|
||||
{Collectable.t, exit_status :: non_neg_integer}
|
||||
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||
cmd = String.to_char_list(command)
|
||||
|
||||
cmd =
|
||||
if Path.type(cmd) == :absolute do
|
||||
cmd
|
||||
else
|
||||
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
|
||||
end
|
||||
|
||||
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
|
||||
{initial, fun} = Collectable.into(into)
|
||||
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
|
||||
end
|
||||
|
||||
defp do_cmd(port, acc, fun) do
|
||||
receive do
|
||||
{^port, {:data, data}} ->
|
||||
do_cmd(port, fun.(acc, {:cont, data}), fun)
|
||||
{^port, {:exit_status, status}} ->
|
||||
{fun.(acc, :done), status}
|
||||
end
|
||||
end
|
||||
|
||||
defp cmd_opts([{:into, any}|t], opts, _into),
|
||||
do: cmd_opts(t, opts, any)
|
||||
|
||||
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:cd, bin}|opts], into)
|
||||
|
||||
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:arg0, bin}|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, true}|t], opts, into),
|
||||
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, false}|t], opts, into),
|
||||
do: cmd_opts(t, opts, into)
|
||||
|
||||
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
|
||||
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
|
||||
|
||||
defp cmd_opts([{:env, enum}|t], opts, into),
|
||||
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
|
||||
|
||||
defp cmd_opts([{key, val}|_], _opts, _into),
|
||||
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
|
||||
|
||||
defp cmd_opts([], opts, into),
|
||||
do: {into, opts}
|
||||
|
||||
defp validate_env(enum) do
|
||||
Enum.map enum, fn
|
||||
{k, v} ->
|
||||
{String.to_char_list(k), String.to_char_list(v)}
|
||||
other ->
|
||||
raise ArgumentError, "invalid environment key-value #{inspect other}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+83
-56
@@ -2,42 +2,35 @@ defmodule Task do
|
||||
@moduledoc """
|
||||
Conveniences for spawning and awaiting for tasks.
|
||||
|
||||
Tasks are processes that meant to execute one particular
|
||||
action throughout their life-cycle, often with little
|
||||
explicit communication with other processes. The most common
|
||||
use case for tasks is to compute a value asynchronously:
|
||||
Tasks are processes meant to execute one particular
|
||||
action throughout their life-cycle, often with little or no
|
||||
communication with other processes. The most common use case
|
||||
for tasks is to compute a value asynchronously:
|
||||
|
||||
task = Task.async(fn -> do_some_work() end)
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with async can be awaited on by its caller
|
||||
Tasks spawned with `async` can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/1`, tasks can also be used as part
|
||||
of supervision trees and dynamically spawned in remote nodes.
|
||||
We will explore all three scenarios next.
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
## async and await
|
||||
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created and this process is linked and monitored
|
||||
by the caller. However, the processes are unlinked right before
|
||||
the task finishes, allowing the proper error to be triggered only
|
||||
on `await/1`.
|
||||
process will be created, linked and monitored by the caller. Once
|
||||
the task action finishes, a message will be sent to the caller
|
||||
with the result.
|
||||
|
||||
This implies three things:
|
||||
|
||||
1) In case the caller crashes, the task will be killed and its
|
||||
computation will abort;
|
||||
|
||||
2) In case the task crashes due to an error, the parent will
|
||||
crash only on `await/1`;
|
||||
|
||||
3) In case the task crashes because a linked process caused
|
||||
it to crash, the parent will crash immediately;
|
||||
`Task.await/2` is used to read the message sent by the task. On
|
||||
`await`, Elixir will also setup a monitor to verify if the process
|
||||
exited for any abnormal reason (or in case exits are being
|
||||
trapped by the caller).
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
@@ -46,7 +39,7 @@ defmodule Task do
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such can be mounted in your supervision tree as:
|
||||
Such tasks can be mounted in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
@@ -54,15 +47,10 @@ defmodule Task do
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Since such tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. For such reason,
|
||||
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
|
||||
(which is the result expected by supervision trees).
|
||||
|
||||
Such tasks are useful as workers that run during your application
|
||||
life-cycle and rarely communicate with other workers. For example,
|
||||
a worker that pushes data to another server or a worker that consumes
|
||||
events from an event manager and writes it to a log file.
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
## Supervision trees
|
||||
|
||||
@@ -70,7 +58,7 @@ defmodule Task do
|
||||
that dynamically supervise tasks:
|
||||
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
Task.Supervisor.async(pid, fn -> do_work() end)
|
||||
Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
|
||||
long as the supervisor is registered locally or globally:
|
||||
@@ -78,8 +66,8 @@ defmodule Task do
|
||||
# In the remote node
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# On the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
# In the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
|
||||
@@ -89,7 +77,15 @@ defmodule Task do
|
||||
supervisor(Task.Supervisor, [[name: :tasks_sup]])
|
||||
]
|
||||
|
||||
Check `Task.Supervisor` for other operations supported by the Task supervisor.
|
||||
Note that, when working with distributed tasks, one should use the `async/3` API,
|
||||
that expects explicit module, function and arguments, instead of `async/1` that
|
||||
works with anonymous functions. That's because the anonymous function API expects
|
||||
the same module version to exist on all involved nodes. Check the `Agent` module
|
||||
documentation for more information on distributed processes, as the limitations
|
||||
described in the agents documentation apply to the whole ecosystem.
|
||||
|
||||
Finally, check `Task.Supervisor` for other operations supported by the Task
|
||||
supervisor.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -97,14 +93,16 @@ defmodule Task do
|
||||
|
||||
It contains two fields:
|
||||
|
||||
* `:pid` - the proces reference of the task process. It may be a pid
|
||||
or a tuple containing the process and node names;
|
||||
* `:pid` - the process reference of the task process; it may be a pid
|
||||
or a tuple containing the process and node names
|
||||
|
||||
* `:ref` - the task monitor reference;
|
||||
* `:ref` - the task monitor reference
|
||||
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@@ -118,20 +116,44 @@ defmodule Task do
|
||||
"""
|
||||
@spec start_link(module, atom, [term]) :: {:ok, pid}
|
||||
def start_link(mod, fun, args) do
|
||||
Task.Supervised.start_link(:undefined, {mod, fun, args})
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
def start(fun) do
|
||||
start(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
def start(mod, fun, args) do
|
||||
Task.Supervised.start(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
This function spawns a process that is linked and monitored
|
||||
to the caller process. A `Task` struct is returned containing
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
|
||||
## Task's message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference hold by the task.
|
||||
where `ref` is the monitoring reference held by the task.
|
||||
"""
|
||||
@spec async(fun) :: t
|
||||
def async(fun) do
|
||||
@@ -147,16 +169,24 @@ defmodule Task do
|
||||
@spec async(module, atom, [term]) :: t
|
||||
def async(mod, fun, args) do
|
||||
mfa = {mod, fun, args}
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self(), mfa])
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {self(), ref})
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
|
||||
A timeout, in miliseconds, can be given with default value
|
||||
@doc """
|
||||
Awaits a task reply.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
"""
|
||||
@@ -168,7 +198,7 @@ defmodule Task do
|
||||
reply
|
||||
{:DOWN, ^ref, _, _, :noconnection} ->
|
||||
mfa = {__MODULE__, :await, [task, timeout]}
|
||||
exit({{:nodedown, get_node(task.pid)}, mfa})
|
||||
exit({{:nodedown, node(task.pid)}, mfa})
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :await, [task, timeout]}})
|
||||
after
|
||||
@@ -185,13 +215,13 @@ defmodule Task do
|
||||
This function returns a tuple with the task and the
|
||||
returned value in case the message matches a task that
|
||||
exited with success, it raises in case the found task
|
||||
failed or nil if no task was found.
|
||||
failed or `nil` if no task was found.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected just
|
||||
later on. For example, a GenServer can spawn tasks,
|
||||
tasks are spawned and their results are collected
|
||||
later on. For example, a `GenServer` can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if upcoming messages are from any of the tasks.
|
||||
to see if incoming messages are from any of the tasks.
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
@@ -210,7 +240,7 @@ defmodule Task do
|
||||
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
|
||||
case Enum.find(tasks, find) do
|
||||
%Task{pid: pid} when reason == :noconnection ->
|
||||
exit({{:nodedown, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
%Task{} ->
|
||||
exit({reason, {__MODULE__, :find, [tasks, msg]}})
|
||||
nil ->
|
||||
@@ -221,7 +251,4 @@ defmodule Task do
|
||||
def find(_tasks, _msg) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp get_node({_, n}) when is_atom(n), do: n
|
||||
defp get_node(pid) when is_pid(pid), do: pid
|
||||
end
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start_link(:undefined, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :noreply, [fun])
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(caller, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def async(caller, {module, fun, args}) do
|
||||
def start_link(caller, info, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
|
||||
end
|
||||
|
||||
def async(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
ref = receive do: ({^caller, ref} -> ref)
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
|
||||
ref =
|
||||
# There is a race condition on this operation when working accross
|
||||
# node that manifests if a `Task.Supervisor.async/1` call is made
|
||||
@@ -33,36 +47,65 @@ defmodule Task.Supervised do
|
||||
5000 -> exit(:timeout)
|
||||
end
|
||||
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
else
|
||||
result ->
|
||||
send caller, {ref, result}
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
after
|
||||
:erlang.unlink(caller)
|
||||
end
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, mfa) do
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
async(caller, mfa)
|
||||
def noreply(info, mfa) do
|
||||
initial_call(mfa)
|
||||
do_apply(info, mfa)
|
||||
end
|
||||
|
||||
def noreply({module, fun, args}) do
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
defp initial_call(mfa) do
|
||||
Process.put(:"$initial_call", get_initial_call(mfa))
|
||||
end
|
||||
|
||||
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
|
||||
{:module, module} = :erlang.fun_info(fun, :module)
|
||||
{:name, name} = :erlang.fun_info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
defp get_initial_call({mod, fun, args}) do
|
||||
{mod, fun, length(args)}
|
||||
end
|
||||
|
||||
defp do_apply(info, {module, fun, args} = mfa) do
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:error, value ->
|
||||
exit(info, mfa, {value, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
exit(info, mfa, {{:nocatch, value}, System.stacktrace()})
|
||||
:exit, value ->
|
||||
exit(info, mfa, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp exit(_info, _mfa, reason)
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown do
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp exit(info, mfa, reason) do
|
||||
{fun, args} = get_running(mfa)
|
||||
|
||||
:error_logger.format(
|
||||
'** Task ~p terminating~n' ++
|
||||
'** Started from ~p~n' ++
|
||||
'** When function == ~p~n' ++
|
||||
'** arguments == ~p~n' ++
|
||||
'** Reason for termination == ~n' ++
|
||||
'** ~p~n', [self, get_from(info), fun, args, reason])
|
||||
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp get_from({node, pid_or_name}) when node == node(), do: pid_or_name
|
||||
defp get_from(other), do: other
|
||||
|
||||
defp get_running({:erlang, :apply, [fun, []]}) when is_function(fun, 0), do: {fun, []}
|
||||
defp get_running({mod, fun, args}), do: {:erlang.make_fun(mod, fun, length(args)), args}
|
||||
end
|
||||
|
||||
@@ -7,12 +7,12 @@ defmodule Task.Supervisor do
|
||||
`:simple_one_for_one` supervisor where the workers are temporary
|
||||
(i.e. they are not restarted after they die).
|
||||
|
||||
The functions in this module allow tasks can be spawned and awaited
|
||||
The functions in this module allow tasks to be spawned and awaited
|
||||
from a supervisor, similar to the functions defined in the `Task` module.
|
||||
|
||||
## Name registering
|
||||
## Name Registration
|
||||
|
||||
A `Task.Supervisor` is bound to the same name registering rules as a
|
||||
A `Task.Supervisor` is bound to the same name registration rules as a
|
||||
`GenServer`. Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@@ -22,17 +22,26 @@ defmodule Task.Supervisor do
|
||||
The supported options are:
|
||||
|
||||
* `:name` - used to register a supervisor name, the supported values are
|
||||
described under the `Name Registering` section in the `GenServer` module
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:restart` - the restart strategy, may be `:temporary` (the default),
|
||||
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
|
||||
Defaults to temporary as most tasks can't be effectively restarted after
|
||||
a crash;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 miliseconds;
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
|
||||
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
|
||||
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
import Supervisor.Spec
|
||||
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
|
||||
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
|
||||
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
|
||||
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
|
||||
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
|
||||
end
|
||||
|
||||
@@ -55,14 +64,15 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async(supervisor, module, fun, args) do
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
|
||||
args = [self, get_info(self), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {self(), ref}
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child at pid.
|
||||
Terminates the child with the given `pid`.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
@@ -80,9 +90,9 @@ defmodule Task.Supervisor do
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Note the spawned process is not linked to the caller but
|
||||
Note that the spawned process is not linked to the caller, but
|
||||
only to the supervisor. This command is useful in case the
|
||||
task needs to emit side-effects (like I/O) and does not need
|
||||
task needs to perform side-effects (like I/O) and does not need
|
||||
to report back to the caller.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
|
||||
@@ -98,6 +108,14 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
|
||||
def start_child(supervisor, module, fun, args) do
|
||||
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
|
||||
Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}])
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+117
-40
@@ -7,6 +7,8 @@ defmodule URI do
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports %{
|
||||
@@ -66,7 +68,7 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
keys and values are URL encoded as per `encode/2`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
@@ -126,8 +128,10 @@ defmodule URI do
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> {decode(key), decode(value)}
|
||||
[ key ] -> {decode(key), nil}
|
||||
[key, value] ->
|
||||
{decode_www_form(key), decode_www_form(value)}
|
||||
[key] ->
|
||||
{decode_www_form(key), nil}
|
||||
end
|
||||
|
||||
{current, next}
|
||||
@@ -142,33 +146,80 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
encode_www_form(to_string(k)) <>
|
||||
"=" <> encode_www_form(to_string(v))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "reserved" character in a URI.
|
||||
|
||||
Reserved characters are specified in RFC3986, section 2.2.
|
||||
"""
|
||||
def char_reserved?(c) do
|
||||
c in ':/?#[]@!$&\'()*+,;='
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "unreserved" character in a URI.
|
||||
|
||||
Unreserved characters are specified in RFC3986, section 2.3.
|
||||
"""
|
||||
def char_unreserved?(c) do
|
||||
c in ?0..?9 or
|
||||
c in ?a..?z or
|
||||
c in ?A..?Z or
|
||||
c in '~_-.'
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is allowed unescaped in a URI.
|
||||
|
||||
This is the default used by `URI.encode/2` where both
|
||||
reserved and unreserved characters are kept unescaped.
|
||||
"""
|
||||
def char_unescaped?(c) do
|
||||
char_reserved?(c) or char_unreserved?(c)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent-escape a URI.
|
||||
Accepts `predicate` function as an argument to specify if char can be left as is.
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
|
||||
"ftp://s-ite.tld/?value=put%20it+%D0%B9"
|
||||
|
||||
"""
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
defp percent(?_), do: <<?_>>
|
||||
defp percent(?.), do: <<?.>>
|
||||
|
||||
defp percent(c)
|
||||
when c >= ?0 and c <= ?9
|
||||
when c >= ?a and c <= ?z
|
||||
when c >= ?A and c <= ?Z do
|
||||
<<c>>
|
||||
def encode(str, predicate \\ &char_unescaped?/1) when is_binary(str) do
|
||||
for <<c <- str>>, into: "", do: percent(c, predicate)
|
||||
end
|
||||
|
||||
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
@doc """
|
||||
Encode a string as "x-www-urlencoded".
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode_www_form("put: it+й")
|
||||
"put%3A+it%2B%D0%B9"
|
||||
|
||||
"""
|
||||
def encode_www_form(str) when is_binary(str) do
|
||||
for <<c <- str>>, into: "" do
|
||||
case percent(c, &char_unreserved?/1) do
|
||||
"%20" -> "+"
|
||||
pct -> pct
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp percent(c, predicate) do
|
||||
if predicate.(c) do
|
||||
<<c>>
|
||||
else
|
||||
"%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
end
|
||||
end
|
||||
|
||||
defp hex(n) when n <= 9, do: <<n + ?0>>
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
@@ -183,28 +234,44 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
unpercent(uri)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
@doc """
|
||||
Decode a string as "x-www-urlencoded".
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_www_form("%3Call+in%2F")
|
||||
"<all in/"
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
end
|
||||
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
|
||||
defp hex_to_dec(_n), do: throw(:malformed_uri)
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
@@ -222,6 +289,8 @@ defmodule URI do
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
@@ -240,7 +309,7 @@ defmodule URI do
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
if is_nil(port) and not is_nil(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
@@ -267,7 +336,7 @@ defmodule URI do
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
if byte_size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -280,12 +349,20 @@ defimpl String.Chars, for: URI do
|
||||
if uri.port == port, do: uri = %{uri | port: nil}
|
||||
end
|
||||
|
||||
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
|
||||
|
||||
if uri.host do
|
||||
authority = uri.host
|
||||
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
|
||||
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
|
||||
else
|
||||
authority = uri.authority
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
|
||||
if uri.scheme, do: result = result <> uri.scheme <> ":"
|
||||
if authority, do: result = result <> "//" <> authority
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
@@ -58,18 +58,35 @@ defmodule Version do
|
||||
|
||||
@type version :: String.t | t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
@type matchable :: {major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t]}
|
||||
@type major :: String.t | non_neg_integer
|
||||
@type minor :: non_neg_integer | nil
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t]
|
||||
@type build :: String.t | nil
|
||||
@type matchable :: {major :: major,
|
||||
minor :: minor,
|
||||
patch :: patch,
|
||||
pre :: pre}
|
||||
@type t :: %__MODULE__{
|
||||
major: major,
|
||||
minor: minor,
|
||||
patch: patch,
|
||||
pre: pre,
|
||||
build: build}
|
||||
|
||||
defmodule Requirement do
|
||||
@moduledoc false
|
||||
defstruct [:source, :matchspec]
|
||||
@type t :: %__MODULE__{}
|
||||
end
|
||||
|
||||
defexception InvalidRequirementError, [:message]
|
||||
defexception InvalidVersionError, [:message]
|
||||
defmodule InvalidRequirementError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule InvalidVersionError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
@@ -152,7 +169,8 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
iex> {:ok, version} = Version.parse("2.0.1-alpha1")
|
||||
iex> version
|
||||
#Version<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
@@ -176,7 +194,8 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
||||
iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
|
||||
iex> req
|
||||
#Version.Requirement<== 2.0.1>
|
||||
|
||||
iex> Version.parse_requirement("== == 2.0.1")
|
||||
@@ -305,7 +324,7 @@ defmodule Version do
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
if is_nil(minor) or (is_nil(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = String.to_integer(major)
|
||||
|
||||
+1
-4
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
|
||||
def project do
|
||||
[app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n"]
|
||||
build_per_environment: false]
|
||||
end
|
||||
end
|
||||
|
||||
+53
-34
@@ -2,7 +2,7 @@
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
-export([start_cli/0,
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
@@ -18,21 +18,30 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
%% has encoding set to latin1.
|
||||
Opts =
|
||||
case init:get_argument(noshell) of
|
||||
{ok, _} -> [binary,{encoding,utf8}];
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
%% Must use undocument {unicode, true} to set unicode on standard_error, more
|
||||
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
|
||||
ok = io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
@@ -41,16 +50,21 @@ stop(_S) ->
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
%% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
application:start(?MODULE),
|
||||
{ok, _} = application:ensure_all_started(?MODULE),
|
||||
|
||||
%% We start the Logger so tools that depend on Elixir
|
||||
%% always have the Logger directly accessible. However
|
||||
%% Logger is not a dependency of the Elixir application,
|
||||
%% which means releases that want to use Logger must
|
||||
%% always list it as part of its applications.
|
||||
_ = case code:ensure_loaded('Elixir.Logger') of
|
||||
{module, _} -> application:start(logger);
|
||||
{error, _} -> ok
|
||||
end,
|
||||
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% EVAL HOOKS
|
||||
@@ -65,37 +79,37 @@ env_for_eval(Opts) ->
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
@@ -108,7 +122,7 @@ env_for_eval(Env, Opts) ->
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
@@ -146,14 +160,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{atom, _, Atom} ->
|
||||
{Atom, Binding, NewEnv, NewScope};
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
|
||||
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
||||
end.
|
||||
|
||||
erl_eval(Erl, ParsedBinding) ->
|
||||
erl_eval(Erl, ParsedBinding, E) ->
|
||||
case erl_eval:check_command([Erl], ParsedBinding) of
|
||||
ok -> ok;
|
||||
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
|
||||
end,
|
||||
|
||||
% Below must be all one line for locations to be the same when the stacktrace
|
||||
% needs to be extended to the full stacktrace.
|
||||
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
|
||||
get_stacktrace() ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=false,
|
||||
keep=false,
|
||||
file=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -41,28 +41,29 @@
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
|
||||
-define(is_bin(S), (S >= $0 andalso S =< $1)).
|
||||
-define(is_octal(S), (S >= $0 andalso S =< $7)).
|
||||
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_digit(S), (S >= $0 andalso S =< $9)).
|
||||
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
|
||||
-define(is_downcase(S), (S >= $a andalso S =< $z)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_quote(S), (S == $" orelse S == $')).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
|
||||
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
|
||||
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
|
||||
-define(is_invalid_space(S), (S == 16#A0)).
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
|
||||
@@ -35,70 +35,99 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, default, [], E);
|
||||
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
|
||||
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
|
||||
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
|
||||
case Size of
|
||||
default ->
|
||||
{[EArg], EE} = elixir_exp:expand_args(Args, E),
|
||||
|
||||
case EArg of
|
||||
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
|
||||
ok;
|
||||
_ when is_integer(EArg) ->
|
||||
ok;
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts",
|
||||
['Elixir.Macro':to_string(EArg)])
|
||||
end,
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
|
||||
|
||||
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"duplicated size definition in bitstring")
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
case expand_bit_type(Expr, Args) of
|
||||
type ->
|
||||
{EArgs, EE} = elixir_exp:expand_args(Args, E),
|
||||
validate_bit_type_args(Meta, Expr, EArgs, EE),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
case Size of
|
||||
expand_bit_info(Meta, [], Size, Types, _) ->
|
||||
[H|T] = case Size of
|
||||
default -> lists:reverse(Types);
|
||||
_ -> [Size|lists:reverse(Types)]
|
||||
end.
|
||||
_ -> lists:reverse(Types, [Size])
|
||||
end,
|
||||
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
|
||||
|
||||
expand_bit_type_or_size(binary, []) -> type;
|
||||
expand_bit_type_or_size(integer, []) -> type;
|
||||
expand_bit_type_or_size(float, []) -> type;
|
||||
expand_bit_type_or_size(bitstring, []) -> type;
|
||||
expand_bit_type_or_size(bytes, []) -> type;
|
||||
expand_bit_type_or_size(bits, []) -> type;
|
||||
expand_bit_type_or_size(utf8, []) -> type;
|
||||
expand_bit_type_or_size(utf16, []) -> type;
|
||||
expand_bit_type_or_size(utf32, []) -> type;
|
||||
expand_bit_type_or_size(signed, []) -> type;
|
||||
expand_bit_type_or_size(unsigned, []) -> type;
|
||||
expand_bit_type_or_size(big, []) -> type;
|
||||
expand_bit_type_or_size(little, []) -> type;
|
||||
expand_bit_type_or_size(native, []) -> type;
|
||||
expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
expand_bit_type(binary, []) -> type;
|
||||
expand_bit_type(integer, []) -> type;
|
||||
expand_bit_type(float, []) -> type;
|
||||
expand_bit_type(bitstring, []) -> type;
|
||||
expand_bit_type(bytes, []) -> type;
|
||||
expand_bit_type(bits, []) -> type;
|
||||
expand_bit_type(utf8, []) -> type;
|
||||
expand_bit_type(utf16, []) -> type;
|
||||
expand_bit_type(utf32, []) -> type;
|
||||
expand_bit_type(signed, []) -> type;
|
||||
expand_bit_type(unsigned, []) -> type;
|
||||
expand_bit_type(big, []) -> type;
|
||||
expand_bit_type(little, []) -> type;
|
||||
expand_bit_type(native, []) -> type;
|
||||
expand_bit_type(unit, [_]) -> type;
|
||||
expand_bit_type(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
|
||||
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unit in bitstring expects an integer as argument, got: ~ts",
|
||||
['Elixir.Macro':to_string(Unit)]);
|
||||
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, ?m(E, file),
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
||||
Info ->
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
unpack_bit_info({'-', _, [H, T]}, Acc) ->
|
||||
unpack_bit_info(H, unpack_bit_info(T, Acc));
|
||||
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
|
||||
[{unit, [], [Unit]}|Acc];
|
||||
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
|
||||
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
|
||||
unpack_bit_info(Size, Acc) when is_integer(Size) ->
|
||||
[{size, [], [Size]}|Acc];
|
||||
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
[{Expr, Meta, ListArgs}|Acc];
|
||||
unpack_bit_info(Other, Acc) ->
|
||||
[Other|Acc].
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({bin, _, Elements}) ->
|
||||
@@ -124,7 +153,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{Acc, S};
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
{Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
@@ -201,23 +230,20 @@ unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
|
||||
{Size, extract_bit_type(Meta, T, S)};
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{default, extract_bit_type(Meta, T, S)}.
|
||||
extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
|
||||
{extract_bit_size(Size, S), extract_bit_type(L, [])};
|
||||
extract_bit_info({size, _, [Size]}, S) ->
|
||||
{extract_bit_size(Size, S), []};
|
||||
extract_bit_info(L, _S) ->
|
||||
{default, extract_bit_type(L, [])}.
|
||||
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
|
||||
[{unit, Arg}|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
extract_bit_size(Size, S) ->
|
||||
{TSize, _} = elixir_translator:translate(Size, S),
|
||||
TSize.
|
||||
|
||||
extract_bit_type({'-', _, [L, R]}, Acc) ->
|
||||
extract_bit_type(L, extract_bit_type(R, Acc));
|
||||
extract_bit_type({unit, _, [Arg]}, Acc) ->
|
||||
[{unit, Arg}|Acc];
|
||||
extract_bit_type({Other, _, []}, Acc) ->
|
||||
[Other|Acc].
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
get_pairs(Key, Clauses, As) ->
|
||||
get_pairs(Key, Clauses, As, false).
|
||||
get_pairs(Key, Clauses, As, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
@@ -79,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
{TX, TS} = each_clause(Meta, X, Return, SAcc),
|
||||
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
@@ -140,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
|
||||
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
|
||||
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
wrap_export_fun(Meta, Fun) ->
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, S) ->
|
||||
{TArgs, TS} = Fun(Args, S),
|
||||
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
|
||||
end
|
||||
end.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
% existing assignments instead of creating new ones every time.
|
||||
@@ -181,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool={[],0},
|
||||
paths={[],[]},
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
erl_compiler_options=nil
|
||||
}).
|
||||
@@ -27,9 +28,13 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||
|
||||
init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
%% We attempt to load those modules here so throughout
|
||||
%% the codebase we can use a code:is_loaded/1 check on
|
||||
%% bootstrap instead of using the more expensive
|
||||
%% code:ensure_loaded/1 multiple times.
|
||||
_ = code:ensure_loaded('Elixir.Macro.Env'),
|
||||
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ok, #elixir_code_server{}}.
|
||||
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
@@ -64,7 +69,8 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
{reply, CompilationStatus,
|
||||
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
@@ -83,6 +89,9 @@ handle_call(erl_compiler_options, _From, Config) ->
|
||||
{reply, Opts, Config}
|
||||
end;
|
||||
|
||||
handle_call(paths, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.paths, Config};
|
||||
|
||||
handle_call(Request, _From, Config) ->
|
||||
{stop, {badcall, Request}, Config}.
|
||||
|
||||
@@ -115,7 +124,7 @@ handle_cast({loaded, Path}, Config) ->
|
||||
{ok, true} ->
|
||||
{noreply, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||
error ->
|
||||
@@ -131,6 +140,9 @@ handle_cast({unload_files, Files}, Config) ->
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||
|
||||
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
|
||||
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
|
||||
|
||||
handle_cast(Request, Config) ->
|
||||
{stop, {badcast, Request}, Config}.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user