Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b338092b6 | ||
|
|
557b779bd4 | ||
|
|
ece40480f6 | ||
|
|
a77445c46b | ||
|
|
4d55fa6814 | ||
|
|
b9d6f4f9c1 | ||
|
|
b0cf76c74c | ||
|
|
7841fbb8e0 | ||
|
|
f5773fed42 | ||
|
|
e2b8ab9a0d | ||
|
|
1dc6d10f30 | ||
|
|
e7138fb2c6 | ||
|
|
04ecc05adc | ||
|
|
8aab53b941 | ||
|
|
07c276ab5c | ||
|
|
89e3a81adc | ||
|
|
9752f1ce05 | ||
|
|
06e5ec2d4c | ||
|
|
b6d3f29b69 | ||
|
|
bfdaf3f8d1 | ||
|
|
bc26f10040 | ||
|
|
52868e556d | ||
|
|
04c45b031a | ||
|
|
d410466ce4 | ||
|
|
fe3c263599 | ||
|
|
7558c9a023 | ||
|
|
0d30dbfd24 | ||
|
|
793bbe6e3c | ||
|
|
3318d33d2e | ||
|
|
b6d7769674 | ||
|
|
dc3a56c44f | ||
|
|
ea9cb84eb1 | ||
|
|
b7b7282a22 | ||
|
|
d3d1833d47 | ||
|
|
7d427719fe | ||
|
|
9812b74a2c | ||
|
|
aebc7e2fe1 | ||
|
|
f0aeb7136f | ||
|
|
d92a450637 | ||
|
|
afb4667cd0 | ||
|
|
eb50a09679 | ||
|
|
2f8ecec32c | ||
|
|
4354938168 | ||
|
|
b06d82c93e | ||
|
|
3dbe8a6661 | ||
|
|
6932e25159 | ||
|
|
22abd6a48e | ||
|
|
05f5073cd1 | ||
|
|
18d96c2fd5 | ||
|
|
ea04ec8f81 | ||
|
|
0a5d633a5e | ||
|
|
7392d736c4 | ||
|
|
1164784b8e | ||
|
|
6694ddbd2f | ||
|
|
85a4d55efb | ||
|
|
9b238e0316 | ||
|
|
ca82388792 | ||
|
|
b33dd12d86 | ||
|
|
c36b2c9070 | ||
|
|
ac75cdbe05 | ||
|
|
2dc48b465d | ||
|
|
16b358cd54 | ||
|
|
61d42c2a55 | ||
|
|
24c6974667 | ||
|
|
1d0423caf5 | ||
|
|
69c08447e8 | ||
|
|
17e819d6fa | ||
|
|
43f80d382b | ||
|
|
d33bac539c | ||
|
|
8d082ad3b3 | ||
|
|
ca4a38f7cd | ||
|
|
494de0fef3 | ||
|
|
0267ae2c7e | ||
|
|
ec0435b84f | ||
|
|
94348db5f4 | ||
|
|
74933b013a | ||
|
|
12a9d55e52 | ||
|
|
d10514c69b | ||
|
|
b0d8b64837 | ||
|
|
f2ff2c8c8c | ||
|
|
c6ffbe9fe5 | ||
|
|
473a83b651 | ||
|
|
ba6cfea701 | ||
|
|
88ef0053d9 | ||
|
|
2583b1da17 | ||
|
|
7d18f748be | ||
|
|
7c182d86bb | ||
|
|
62e310584f | ||
|
|
b38491c6e9 | ||
|
|
d5e244611c | ||
|
|
260dde330f | ||
|
|
8847bc7a9e | ||
|
|
34a4a49af0 | ||
|
|
9442ab3cff | ||
|
|
1fe7847f0e | ||
|
|
bed2547895 | ||
|
|
c519d7bec8 | ||
|
|
1aff3b1370 | ||
|
|
cf68b9f652 | ||
|
|
f303ea6135 | ||
|
|
e16775d5a4 | ||
|
|
beb596d37c | ||
|
|
7b67b7d491 | ||
|
|
bc447886ad | ||
|
|
02a327d3c4 | ||
|
|
4db36e08d7 | ||
|
|
8d95a19f02 | ||
|
|
9bf4826210 | ||
|
|
6cf3d8c20a | ||
|
|
9cd93a80d5 | ||
|
|
dd880e209c | ||
|
|
9afb27a4e2 | ||
|
|
fd27c2053f | ||
|
|
c7d3552767 | ||
|
|
680631ae7a | ||
|
|
13c80f0834 | ||
|
|
991db52707 | ||
|
|
045aa012f1 | ||
|
|
8a971fcb44 | ||
|
|
720dbb3457 | ||
|
|
1b152f2656 | ||
|
|
94da6bbff9 | ||
|
|
904d05a750 | ||
|
|
17ed43a15d | ||
|
|
cc1feca0b0 | ||
|
|
83e6358b14 | ||
|
|
ed28d35eb5 | ||
|
|
32450a0fd9 | ||
|
|
dc9cbb6e5d | ||
|
|
3f39110aa6 | ||
|
|
caa90b563a | ||
|
|
f84eff9648 | ||
|
|
383066aef5 | ||
|
|
cd0cb7cca4 | ||
|
|
2ecb5d00bd | ||
|
|
b0cc54468e | ||
|
|
4416854a9d | ||
|
|
56b22b4bf2 | ||
|
|
8641947714 | ||
|
|
005f432671 | ||
|
|
e7bd7c98e0 | ||
|
|
2ece95c1db | ||
|
|
9707843134 | ||
|
|
c1d6bd3e60 | ||
|
|
3a7a95104e | ||
|
|
693e051c0e | ||
|
|
6b5cb7014e | ||
|
|
ec0ec5620e | ||
|
|
a62a55587b | ||
|
|
92a0c787b3 | ||
|
|
7d621f60a9 | ||
|
|
eab075fba0 | ||
|
|
4f25ccd2d0 | ||
|
|
374480e2e1 | ||
|
|
e3b2b09cce | ||
|
|
e07ccb1df5 | ||
|
|
1eae754d07 | ||
|
|
4bab217b84 | ||
|
|
2a610bd005 | ||
|
|
08e8f61e01 | ||
|
|
69944c4f35 | ||
|
|
5fbc8676c2 | ||
|
|
5f14dbe817 | ||
|
|
c4a96a29b1 | ||
|
|
ec4bafb19f | ||
|
|
a4b0e4f901 | ||
|
|
59a87155ec | ||
|
|
eb218cb2c1 | ||
|
|
094eb6a86d | ||
|
|
d225f4a373 | ||
|
|
e80be3d3e8 | ||
|
|
77ba9fd3b0 | ||
|
|
c3b0caa8c9 | ||
|
|
072eee8ea6 | ||
|
|
f730125b44 | ||
|
|
f17c1a6881 | ||
|
|
2f268088af | ||
|
|
95acb1f751 | ||
|
|
9411636d12 | ||
|
|
baf4a49b92 | ||
|
|
3674328f4b | ||
|
|
5ab60299b5 | ||
|
|
1e79cca1a6 | ||
|
|
896e0664a0 | ||
|
|
d2799bade3 | ||
|
|
22245a44fe | ||
|
|
4ee414e835 | ||
|
|
faa0e84fa6 | ||
|
|
969b9e1d2a | ||
|
|
5246eeabf6 | ||
|
|
cc7d491061 | ||
|
|
08eabd803e | ||
|
|
6827a98cce | ||
|
|
c024b0eeb2 | ||
|
|
5f31e5b146 | ||
|
|
0be63ad5da | ||
|
|
d27062c8ec | ||
|
|
940bfa2d11 | ||
|
|
70743104d5 | ||
|
|
63aadf0a9f | ||
|
|
0b9b023001 | ||
|
|
282a6941a8 | ||
|
|
0a9272d94a | ||
|
|
41e4a41a9e | ||
|
|
2de07ba2ab | ||
|
|
a7936a879f | ||
|
|
9de1e942f1 | ||
|
|
5ba9c6d93f | ||
|
|
1927f53776 | ||
|
|
8a64261b22 | ||
|
|
6f837790ee | ||
|
|
6eff1c403b | ||
|
|
cd2a8f08ae | ||
|
|
1c5ed3840d | ||
|
|
071c41b2ce | ||
|
|
0e793796e5 | ||
|
|
b53d7398e2 | ||
|
|
1451797b91 | ||
|
|
06dd76626b | ||
|
|
e35f42f222 | ||
|
|
4927b6e09a | ||
|
|
b29ae38f81 | ||
|
|
2a69c0afa1 | ||
|
|
01d141d1c2 | ||
|
|
396e0bff9f | ||
|
|
47f1107a7a | ||
|
|
5edb1d2739 | ||
|
|
3f18cec52c | ||
|
|
36a72d2349 | ||
|
|
e1c903a595 | ||
|
|
c995ba968f | ||
|
|
1ade3c0dd9 | ||
|
|
76e5a3237d | ||
|
|
1db1f28b35 | ||
|
|
93b13ba48b | ||
|
|
cd235357ac | ||
|
|
385f8d1312 | ||
|
|
76382fca51 | ||
|
|
39416f21ab | ||
|
|
f0b6727b9d | ||
|
|
9800408ebc | ||
|
|
0ade3ab053 | ||
|
|
4b357bf079 | ||
|
|
35dff1ca9b | ||
|
|
4ed5e8e669 | ||
|
|
9c7c1e98d7 | ||
|
|
98a73f5e73 | ||
|
|
6fd0cd72ef | ||
|
|
4fad9921a6 | ||
|
|
5b26070789 | ||
|
|
6d43930586 | ||
|
|
20e1d75394 | ||
|
|
ca21a7b12c | ||
|
|
daf5ed3d5c | ||
|
|
fa0f23867b | ||
|
|
95ffb4cd55 | ||
|
|
c11e49cc11 | ||
|
|
e853a38405 | ||
|
|
32485ecd3d | ||
|
|
f9d7e72c18 | ||
|
|
aa52eedbf9 | ||
|
|
2a2f665fd9 | ||
|
|
f72dd0a0c4 | ||
|
|
54a29ddde0 | ||
|
|
68a1893715 | ||
|
|
c4a1cd0b29 | ||
|
|
733a96e652 | ||
|
|
a2e931c036 | ||
|
|
26d53e60a7 | ||
|
|
519fb4f396 | ||
|
|
5b672b4b31 | ||
|
|
73201caca0 | ||
|
|
be73e8c9db | ||
|
|
cd35e84576 | ||
|
|
f0ada33979 | ||
|
|
7976771b30 | ||
|
|
dfabb6cc54 | ||
|
|
e079a9331c | ||
|
|
19105a3bd2 | ||
|
|
d1e3f3acd2 | ||
|
|
4bc3527134 | ||
|
|
72f037e592 | ||
|
|
44f9635539 | ||
|
|
60bc0aeae9 | ||
|
|
70f2d469ff | ||
|
|
9130a388ee | ||
|
|
36a6ebd590 | ||
|
|
08d8c8fa57 | ||
|
|
f564ae9f8d | ||
|
|
04c5123b10 | ||
|
|
c980b20239 | ||
|
|
27a869bda2 | ||
|
|
33e161fe73 | ||
|
|
56f0e57db8 | ||
|
|
d01715074e | ||
|
|
3108265d90 | ||
|
|
2a7abb7f72 | ||
|
|
f84770ebf0 | ||
|
|
75541cf472 | ||
|
|
2c770ba9d6 | ||
|
|
ae0389eea9 | ||
|
|
a87b4b4474 | ||
|
|
a2b27f4d02 | ||
|
|
f6b9ad96bd | ||
|
|
fe798f8fc9 | ||
|
|
f51865bd14 | ||
|
|
5facf07200 | ||
|
|
608d7f814f | ||
|
|
11a0b39ad4 | ||
|
|
2c02836931 | ||
|
|
aa8be83e7a | ||
|
|
e28931fdaa | ||
|
|
4b4e9e9987 | ||
|
|
f1b8613162 | ||
|
|
463b11edc4 | ||
|
|
c0859c8714 | ||
|
|
4cf0c8c9db | ||
|
|
2f9c7217f8 | ||
|
|
33367893d7 | ||
|
|
2ba20b6afa | ||
|
|
55543b8826 | ||
|
|
371423a93f | ||
|
|
e2e065dacb | ||
|
|
722aef3791 | ||
|
|
6ad47b28f6 | ||
|
|
ff31f4d663 | ||
|
|
e8b0806031 | ||
|
|
5e987a702b | ||
|
|
5c97279b57 | ||
|
|
8329e7b827 | ||
|
|
d17ac42a5f | ||
|
|
ab302d23e4 | ||
|
|
05e087d252 | ||
|
|
cb616c767a | ||
|
|
afae403b7a | ||
|
|
1aba6f72c6 | ||
|
|
485510dd31 | ||
|
|
4398927789 | ||
|
|
dccb537b6b | ||
|
|
61a96348c6 | ||
|
|
2921f0165b | ||
|
|
36d4cbf384 | ||
|
|
ebbdf2215e | ||
|
|
1c18c0d9d0 | ||
|
|
b087754ecb | ||
|
|
cccbcdf27b | ||
|
|
76f3a106ba | ||
|
|
96237954f0 | ||
|
|
3b2eb3a0e4 | ||
|
|
d4da92bbc7 | ||
|
|
fe5ebc9dd5 | ||
|
|
230d980cf7 | ||
|
|
2f5c147ac1 | ||
|
|
7289e23b0d | ||
|
|
329ed8319a | ||
|
|
3898fef9d5 | ||
|
|
87f88614d3 | ||
|
|
ed766858b9 | ||
|
|
aa023d61cc | ||
|
|
9656b4b65b | ||
|
|
384751fd8e | ||
|
|
74e6661569 | ||
|
|
9087de290a | ||
|
|
0f2fb10c4f | ||
|
|
c6bfb7b0c0 | ||
|
|
adbd8da0f6 | ||
|
|
53a2cf74d8 | ||
|
|
2ebf14c431 | ||
|
|
78e34a1ce5 | ||
|
|
24a958db0a | ||
|
|
14fd1d31ed | ||
|
|
05656c4937 | ||
|
|
8803748319 | ||
|
|
644cce6db7 | ||
|
|
a6ab0d99ae | ||
|
|
2865ded07e | ||
|
|
19ff039a70 | ||
|
|
322df35130 | ||
|
|
90f4453604 | ||
|
|
7a51e516c0 | ||
|
|
214bf61b4d | ||
|
|
fd2b20f2f4 | ||
|
|
36c56d7962 | ||
|
|
60acb96fce | ||
|
|
f27e4cc950 | ||
|
|
a7219b6c93 | ||
|
|
d3d94502e0 | ||
|
|
cfaf9dbbcd | ||
|
|
fc857d6ff1 | ||
|
|
27f046321d | ||
|
|
ba5e7c2b57 | ||
|
|
8a824aa42d | ||
|
|
15d0d345f0 | ||
|
|
ae89ac8525 | ||
|
|
cb67649dff | ||
|
|
584799809a | ||
|
|
24bf72eb64 | ||
|
|
f20774547b | ||
|
|
901752a4fe | ||
|
|
fd844b7125 | ||
|
|
8b52c6d223 | ||
|
|
27e9486b47 | ||
|
|
87d1299887 | ||
|
|
8f967df99b | ||
|
|
d3e04afc38 | ||
|
|
51ff5db9cf | ||
|
|
49132ad6c1 | ||
|
|
b81e35c737 | ||
|
|
7d58bb89d7 | ||
|
|
0547e80d38 | ||
|
|
af705d97f3 | ||
|
|
98d5a45cb3 | ||
|
|
1048b34d6c | ||
|
|
9f97f798e6 | ||
|
|
3900d2c203 | ||
|
|
e0b3209e23 | ||
|
|
628ab6c3fe | ||
|
|
08c2d0885f | ||
|
|
c319da92e8 | ||
|
|
2cbdc17cf4 | ||
|
|
c9f1f039c8 | ||
|
|
f963013036 | ||
|
|
8d47d0d238 | ||
|
|
edd56b4709 | ||
|
|
740a815bb4 | ||
|
|
52e6d70944 | ||
|
|
05e24c440e | ||
|
|
ae6fb479b5 | ||
|
|
91e8a76bfb | ||
|
|
f64f957167 | ||
|
|
0757cf8028 | ||
|
|
d70222f03a | ||
|
|
58c2035e97 | ||
|
|
399bd14f0e | ||
|
|
09e25a75d0 | ||
|
|
27c15d04fb | ||
|
|
cf6e610afe | ||
|
|
a4ed1c93f9 | ||
|
|
10b538c23b | ||
|
|
4923b8708f | ||
|
|
add3a9666f | ||
|
|
946455ec42 | ||
|
|
c2e92c2847 | ||
|
|
5c814ae038 | ||
|
|
7a829c5735 | ||
|
|
5947ed751e | ||
|
|
d0378be1e7 | ||
|
|
ac1af198c8 | ||
|
|
e703bb89f9 | ||
|
|
a5a8ad3eaa | ||
|
|
0ce9ea6188 | ||
|
|
d297fe5195 | ||
|
|
eb2418d4b1 | ||
|
|
1d14366ba4 | ||
|
|
8c85557346 | ||
|
|
3c1a6db968 | ||
|
|
8958f9eea6 | ||
|
|
0995a5a14f | ||
|
|
4f14aea9f6 | ||
|
|
43fd9f4b5a | ||
|
|
b5e1a76699 | ||
|
|
e54791c56a | ||
|
|
918fd9928c | ||
|
|
68f31fb2fb | ||
|
|
2318dd3bd4 | ||
|
|
b42aa5739b | ||
|
|
141c327685 | ||
|
|
538bd67b8e | ||
|
|
d0bd8692dd | ||
|
|
14147ed883 | ||
|
|
42dd2e5870 | ||
|
|
972bca67c4 | ||
|
|
e1f711b4cb | ||
|
|
15969b93e5 | ||
|
|
d7791a1eef | ||
|
|
17e1e204ea | ||
|
|
37c3104d5e | ||
|
|
bc7043cbe9 | ||
|
|
6b968fdef9 | ||
|
|
1bfee449f9 | ||
|
|
6cbe849d58 | ||
|
|
bf956e2afb | ||
|
|
875ce67673 | ||
|
|
53edf94714 | ||
|
|
24790a80ba | ||
|
|
2dee728a3e | ||
|
|
56c5353f99 | ||
|
|
03d46deddf | ||
|
|
0126cdd253 | ||
|
|
9ce15e568c | ||
|
|
e4c155a4bb | ||
|
|
ef62d36c5f | ||
|
|
d63c16c070 | ||
|
|
88ebdfdf68 | ||
|
|
2c464855cd | ||
|
|
594f778fff | ||
|
|
214871bac5 | ||
|
|
6dab4d2db2 | ||
|
|
d91aed218e | ||
|
|
d8dbc6f2cb | ||
|
|
3cd378cd90 | ||
|
|
7cbba67461 | ||
|
|
db017e7f18 | ||
|
|
f0a7f19ff4 | ||
|
|
2b38f8a3e0 | ||
|
|
6d94ee76e5 | ||
|
|
e4449907bb | ||
|
|
ed6c62fd83 | ||
|
|
f99ee87f3c | ||
|
|
2948556e9e | ||
|
|
cb9bf1b497 | ||
|
|
d94a9ccf33 | ||
|
|
4f03ec6540 | ||
|
|
4e3e9cefc8 | ||
|
|
239def6845 | ||
|
|
83262e0e7c | ||
|
|
ad01452787 | ||
|
|
14b2fc8a56 | ||
|
|
a4ddaa3a7b | ||
|
|
15bbc3ff9b | ||
|
|
a5cad91907 | ||
|
|
266719f7b6 | ||
|
|
62c92e27b3 | ||
|
|
699231dcad | ||
|
|
3cb41d27ba | ||
|
|
c8db03468d | ||
|
|
f1c0fc8b5c | ||
|
|
fa1191be8a | ||
|
|
3e89e42a9d | ||
|
|
5cb5435c64 | ||
|
|
2f03f4603e | ||
|
|
5405a165d8 | ||
|
|
33306d7313 | ||
|
|
74946ae5c7 | ||
|
|
36395a248c | ||
|
|
68b88ce9eb | ||
|
|
c0f184557a | ||
|
|
a87046a2cf | ||
|
|
8f0fe3d23f | ||
|
|
c86191eed1 | ||
|
|
e788026f41 | ||
|
|
ee61bc4789 | ||
|
|
8f59c314e2 | ||
|
|
25b9220100 | ||
|
|
0ae082de54 | ||
|
|
43f959a09a | ||
|
|
f30b29aca1 | ||
|
|
44f87e1336 | ||
|
|
f7eb97cf7d | ||
|
|
f416a685fb | ||
|
|
2e6704de70 | ||
|
|
f46e81f958 | ||
|
|
dc1f53cb91 | ||
|
|
f3ff47ca16 | ||
|
|
936172228f | ||
|
|
f0ed3e39f0 | ||
|
|
bed0c2983c | ||
|
|
2d4be9850b | ||
|
|
179566d9f3 | ||
|
|
ff5594332a | ||
|
|
adf2f7fd2b | ||
|
|
752c0b71c2 | ||
|
|
2cbd5cee83 | ||
|
|
b8bf33beff | ||
|
|
a979992b97 | ||
|
|
5e99c6badf | ||
|
|
7e0acd983a | ||
|
|
deb2aa50dd | ||
|
|
e0bd2f56da | ||
|
|
a5a6d7d635 | ||
|
|
fa369089c9 | ||
|
|
7b56fbdcb8 | ||
|
|
5a112daf37 | ||
|
|
086b4bacf0 | ||
|
|
408bbbce4a | ||
|
|
ae8502d8cd | ||
|
|
0bed0d01cb | ||
|
|
157913c87a | ||
|
|
6548704fa6 | ||
|
|
197a9f2c10 | ||
|
|
d894dcca33 | ||
|
|
42b62067da | ||
|
|
b3331e823b | ||
|
|
2516ecf047 | ||
|
|
252974ed16 | ||
|
|
f0ad7edf41 | ||
|
|
e9dfa50c74 | ||
|
|
f15e1db577 | ||
|
|
fca649e909 | ||
|
|
b33f43d287 | ||
|
|
f1fd4ef4e3 | ||
|
|
73eced6faf | ||
|
|
cf7f2e493c | ||
|
|
77c9d4872d | ||
|
|
a044559e82 | ||
|
|
097de7d308 | ||
|
|
84dcbbb4d1 | ||
|
|
7ef2ed4fe4 | ||
|
|
90ea5cf064 | ||
|
|
dc6ee0be2f | ||
|
|
2dee0a8e8b | ||
|
|
4a4542d369 | ||
|
|
ef797dce40 | ||
|
|
8e4251d7c6 | ||
|
|
f835702d86 | ||
|
|
891f590479 | ||
|
|
7c142fbc86 | ||
|
|
d8a57371d4 | ||
|
|
83c2791263 | ||
|
|
1d0ac1caac | ||
|
|
a25a796642 | ||
|
|
fe1319e393 | ||
|
|
72c3540a32 | ||
|
|
b3e7587bf8 | ||
|
|
2b482cde0d | ||
|
|
66bd86501d | ||
|
|
c42df09f3c | ||
|
|
dcbd276526 | ||
|
|
986e08b9bf | ||
|
|
56354e9074 | ||
|
|
eb5e809f10 | ||
|
|
d58b315d94 | ||
|
|
4ef61b81c2 | ||
|
|
56d03c65df | ||
|
|
ced1adfe30 | ||
|
|
e0c112370e | ||
|
|
059196431c | ||
|
|
627660d742 | ||
|
|
7ff0d4b137 | ||
|
|
b915b8ec23 | ||
|
|
29a74f8ad4 | ||
|
|
fb33b81696 | ||
|
|
4b134c8cf5 | ||
|
|
97723bab43 | ||
|
|
2a1ca02622 | ||
|
|
fcb2ac593c | ||
|
|
4f9143843b | ||
|
|
6dcc82738c | ||
|
|
7dda3ed43d | ||
|
|
4444467c81 | ||
|
|
7e76ece1c8 | ||
|
|
00c7a3bc60 | ||
|
|
ffbd96ff53 | ||
|
|
83e6546f85 | ||
|
|
8bfdcd0f8b | ||
|
|
e0dc8b0051 | ||
|
|
1fcc16f346 | ||
|
|
106efa2ace | ||
|
|
464f4a0a49 | ||
|
|
d05e3612ab | ||
|
|
12153869c5 | ||
|
|
3281bbb4c7 | ||
|
|
edb52b7ca5 | ||
|
|
1b8b1ed9b3 | ||
|
|
00ebdd9d3f | ||
|
|
8c8d9271b1 | ||
|
|
e07a41d9d0 | ||
|
|
8a4038850a | ||
|
|
8a23ec182c | ||
|
|
9688cd64fe | ||
|
|
fd5adc15ff | ||
|
|
1781638bde | ||
|
|
642eea87b3 | ||
|
|
6edee91eec | ||
|
|
9ef646eed5 | ||
|
|
c133bdd7a1 | ||
|
|
5dedbed881 | ||
|
|
054f9172fa | ||
|
|
219c6cc867 | ||
|
|
1233a6f644 | ||
|
|
239db307a2 | ||
|
|
f99bb7c0e5 | ||
|
|
fd149eeb56 | ||
|
|
1ba04d15d8 | ||
|
|
113d2c69c9 | ||
|
|
212bfae6fd | ||
|
|
716a3a7cc9 | ||
|
|
be466d57ba | ||
|
|
d9625f9a67 | ||
|
|
abb929eda4 | ||
|
|
c38fa49a74 | ||
|
|
97ff1ed89f | ||
|
|
69bee9a472 | ||
|
|
bfa3cfba19 | ||
|
|
582e8210da | ||
|
|
44e86616da | ||
|
|
c480b68b2d | ||
|
|
1419a3d126 | ||
|
|
90ce44a614 | ||
|
|
8b07d0e2c2 | ||
|
|
ef1f406d25 | ||
|
|
97cf609594 | ||
|
|
b3da75e711 | ||
|
|
47076c0f3c | ||
|
|
f36bb56288 | ||
|
|
4175ddebd9 | ||
|
|
3142df4dd1 | ||
|
|
ca3b8415e5 | ||
|
|
b9591267dc | ||
|
|
9d63a57d5d | ||
|
|
c6a9bf37dc | ||
|
|
17a0278a28 | ||
|
|
3a597c7655 | ||
|
|
684397f59c | ||
|
|
cbcdcb1623 | ||
|
|
a719605abf | ||
|
|
4e01713973 | ||
|
|
3b2b09d473 | ||
|
|
abd121d2cd | ||
|
|
ce15d018d8 | ||
|
|
2266e33ea5 | ||
|
|
df13e1aaa4 | ||
|
|
8ece3b9748 | ||
|
|
b144c81e9d | ||
|
|
a7eb9adfe9 | ||
|
|
436a6b096f | ||
|
|
8c05bb078d | ||
|
|
df1a7d3647 | ||
|
|
31f80966be | ||
|
|
f34d74913c | ||
|
|
b66622e8f4 | ||
|
|
621ef77f4a | ||
|
|
0da2738dff | ||
|
|
df17e81d16 | ||
|
|
f518f20372 | ||
|
|
6fa3881beb | ||
|
|
d96088e166 | ||
|
|
e044b661b9 | ||
|
|
8f5ee5b34c | ||
|
|
7853132edb | ||
|
|
471c87d044 | ||
|
|
f73daf7ffb | ||
|
|
db0204d50c | ||
|
|
889ae9afbb | ||
|
|
a01f704b19 | ||
|
|
ebe33f1f2f | ||
|
|
cbde356d10 | ||
|
|
1bdfade2a3 | ||
|
|
b74de02190 | ||
|
|
7d6852ac76 | ||
|
|
e4a82d5f88 | ||
|
|
d9c7deb667 | ||
|
|
c270e7211f | ||
|
|
44a8f665b1 | ||
|
|
9e232a60a1 | ||
|
|
2f5cc73e52 | ||
|
|
dc5d36a55e | ||
|
|
e99095626d | ||
|
|
0db863d33f | ||
|
|
0d0af06623 | ||
|
|
48e914dd3a | ||
|
|
3a37fdb890 | ||
|
|
d18030ffef | ||
|
|
443c2e5dc0 | ||
|
|
ff153b786b | ||
|
|
85d1a25458 | ||
|
|
9e1485f29a |
+3
-3
@@ -11,8 +11,8 @@ test_script:
|
||||
environment:
|
||||
ELIXIR_ASSERT_TIMEOUT: 2000
|
||||
|
||||
configuration: Test
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- configuration: Test
|
||||
- platform: x86
|
||||
- platform: x64
|
||||
- platform: Any CPU
|
||||
|
||||
+1
-5
@@ -12,10 +12,6 @@
|
||||
assert_same: 2,
|
||||
|
||||
# Errors tests
|
||||
assert_eval_raise: 3,
|
||||
|
||||
# Mix tests
|
||||
in_fixture: 2,
|
||||
in_tmp: 2
|
||||
assert_eval_raise: 3
|
||||
]
|
||||
]
|
||||
|
||||
+28
-18
@@ -1,29 +1,39 @@
|
||||
language: erlang
|
||||
language: bash
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
otp_release: 19.0
|
||||
- os: linux
|
||||
otp_release: 19.1
|
||||
- os: linux
|
||||
otp_release: 19.2
|
||||
- os: linux
|
||||
otp_release: 19.3
|
||||
- os: linux
|
||||
otp_release: 20.0
|
||||
- os: linux
|
||||
otp_release: 20.1
|
||||
|
||||
env:
|
||||
- ELIXIR_ASSERT_TIMEOUT=2000
|
||||
global:
|
||||
- ELIXIR_ASSERT_TIMEOUT=2000
|
||||
matrix:
|
||||
- OTP_RELEASE=OTP-19.0
|
||||
- OTP_RELEASE=OTP-19.1
|
||||
- OTP_RELEASE=OTP-19.2
|
||||
- OTP_RELEASE=OTP-19.3
|
||||
- OTP_RELEASE=OTP-20.0
|
||||
- OTP_RELEASE=OTP-20.1
|
||||
- OTP_RELEASE=OTP-20.2
|
||||
- OTP_RELEASE=OTP-20.3
|
||||
- OTP_RELEASE=OTP-21.0
|
||||
- OTP_RELEASE=maint
|
||||
- OTP_RELEASE=master
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: OTP_RELEASE=maint
|
||||
- env: OTP_RELEASE=master
|
||||
|
||||
install:
|
||||
- wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-14.04/${OTP_RELEASE}.tar.gz
|
||||
- mkdir -p otp
|
||||
- tar zxf otp.tar.gz -C otp --strip-components=1
|
||||
- otp/Install -minimal $(pwd)/otp
|
||||
- PATH=$(pwd)/otp/bin:$PATH
|
||||
|
||||
script:
|
||||
- make compile
|
||||
- rm -rf .git
|
||||
- make test
|
||||
- bin/elixir bin/mix format --dry-run --check-formatted
|
||||
- dialyzer -pa lib/elixir/ebin --build_plt --output_plt elixir.plt --apps lib/elixir/ebin/elixir.beam lib/elixir/ebin/Elixir.Kernel.beam
|
||||
|
||||
notifications:
|
||||
|
||||
+228
-121
@@ -1,165 +1,272 @@
|
||||
# Changelog for Elixir v1.6
|
||||
# Changelog for Elixir v1.7
|
||||
|
||||
## Compiler diagnostics
|
||||
Elixir v1.7 is the last release to support Erlang/OTP 19. We recommend everyone to migrate to Erlang/OTP 20+.
|
||||
|
||||
TODO.
|
||||
## Documentation metadata
|
||||
|
||||
## Code formatter
|
||||
Elixir v1.7 implements [EEP 48](http://erlang.org/eep/eeps/eep-0048.html). EEP 48 aims to bring documentation interoperability across all languages running on the Erlang VM. The documentation format proposed by EEP 48 also supports metadata, which is now fully exposed to Elixir developers:
|
||||
|
||||
TODO.
|
||||
```elixir
|
||||
@moduledoc "A brand new module"
|
||||
@moduledoc authors: ["Jane", "Mary"], since: "1.4.0"
|
||||
```
|
||||
|
||||
## Stream data and property testing
|
||||
Passing metadata is supported on `@doc`, `@moduledoc` and `@typedoc`.
|
||||
|
||||
TODO.
|
||||
To access the new documentation format, developers should use `Code.fetch_docs/1`. The old documentation format is no longer available and the old `Code.get_docs/2` function will return `nil` accordingly.
|
||||
|
||||
## mix xref
|
||||
Tools like IEx and ExDoc have been updated to leverage the new format and show relevant metadata to users. While Elixir allows any metadata to be given, those tools currently exhibit only `:deprecated` and `:since`. Other keys may be shown in the future.
|
||||
|
||||
`mix xref` is a task added in Elixir v1.3 which provides general information about how modules and files in an application depend on each other. This release brings many improvements to `xref`, extending the reach of the analysis and helping developers digest the vast amount of data it produces.
|
||||
## The `__STACKTRACE__` construct
|
||||
|
||||
One of such additions is the `--include-siblings` option that can be given to all `xref` commands inside umbrella projects. For example, to find all of the callers of a given module or function in an umbrella:
|
||||
Erlang/OTP 21.0 introduces a new way to retrieve the stacktrace that is lexically scoped and no longer relies on side-effects like `System.stacktrace/0` does. Before one would write:
|
||||
|
||||
$ mix xref callers SomeModule --include-siblings
|
||||
```elixir
|
||||
try do
|
||||
... something that may fail ...
|
||||
rescue
|
||||
e ->
|
||||
log(e, System.stacktrace())
|
||||
reraise(e, System.stacktrace())
|
||||
end
|
||||
```
|
||||
|
||||
The `graph` command in `mix xref` can also output general statistics about the graph. In the hexpm project, you would get:
|
||||
In Elixir v1.7, this can be written as:
|
||||
|
||||
$ mix xref graph --format stats
|
||||
Tracked files: 129 (nodes)
|
||||
Compile dependencies: 256 (edges)
|
||||
Structs dependencies: 46 (edges)
|
||||
Runtime dependencies: 266 (edges)
|
||||
```elixir
|
||||
try do
|
||||
... something that may fail ...
|
||||
rescue
|
||||
e ->
|
||||
log(e, __STACKTRACE__)
|
||||
reraise(e, __STACKTRACE__)
|
||||
end
|
||||
```
|
||||
|
||||
Top 10 files with most outgoing dependencies:
|
||||
* test/support/factory.ex (18)
|
||||
* lib/hexpm/accounts/user.ex (13)
|
||||
* lib/hexpm/accounts/audit_log.ex (12)
|
||||
* lib/hexpm/web/controllers/dashboard_controller.ex (12)
|
||||
* lib/hexpm/repository/package.ex (12)
|
||||
* lib/hexpm/repository/releases.ex (11)
|
||||
* lib/hexpm/repository/release.ex (10)
|
||||
* lib/hexpm/web/controllers/package_controller.ex (10)
|
||||
* lib/mix/tasks/hexpm.stats.ex (9)
|
||||
* lib/hexpm/repository/registry_builder.ex (9)
|
||||
This change may also yield performance improvements in the future, since the lexical scope allows us to track precisely when a stacktrace is used and we no longer need to keep references to stacktrace entries after the `try` construct finishes.
|
||||
|
||||
Top 10 files with most incoming dependencies:
|
||||
* lib/hexpm/web/web.ex (84)
|
||||
* lib/hexpm/web/router.ex (29)
|
||||
* lib/hexpm/web/controllers/controller_helpers.ex (29)
|
||||
* lib/hexpm/web/controllers/auth_helpers.ex (28)
|
||||
* lib/hexpm/web/views/view_helpers.ex (27)
|
||||
* lib/hexpm/web/views/icons.ex (27)
|
||||
* lib/hexpm/web/endpoint.ex (23)
|
||||
* lib/hexpm/ecto/changeset.ex (22)
|
||||
* lib/hexpm/accounts/user.ex (19)
|
||||
* lib/hexpm/repo.ex (19)
|
||||
Other parts of the exception system have been improved. For example, more information is provided in certain occurrences of `ArgumentError`, `ArithmeticError` and `KeyError` messages.
|
||||
|
||||
`mix xref graph` also get the `--only-nodes` and `--label` options. The former asks Mix to only output file names (nodes) without the edges. The latter allows you to focus on certain relationships:
|
||||
## Erlang/OTP logger integration
|
||||
|
||||
# To get all files that depend on lib/foo.ex
|
||||
mix xref graph --sink lib/foo.ex --only-nodes
|
||||
Erlang/OTP 21 includes a new `:logger` module. Elixir v1.7 fully integrates with the new `:logger` and leverages its metadata system. The `Logger.Translator` mechanism has also been improved to export metadata, allowing custom Logger backends to leverage information such as:
|
||||
|
||||
# To get all files that depend on lib/foo.ex at compile time
|
||||
mix xref graph --label compile --sink lib/foo.ex --only-nodes
|
||||
* `:crash_reason` - a two-element tuple with the throw/error/exit reason as first argument and the stacktrace as second
|
||||
|
||||
# To get all files lib/foo.ex depends on
|
||||
mix xref graph --source lib/foo.ex --only-nodes
|
||||
* `:initial_call` - the initial call that started the process
|
||||
|
||||
# To limit statistics only to compile time dependencies
|
||||
mix xref graph --format stats --label compile
|
||||
* `:registered_name` - the process registered name as an atom
|
||||
|
||||
Those improvements will help developers better understand the relationship between files and reveal potentially complex parts of their systems.
|
||||
We recommend Elixir libraries that previously hooked into Erlang's `:error_logger` to hook into `Logger` instead, in order to support all current and future Erlang/OTP versions.
|
||||
|
||||
## v1.6.0-dev
|
||||
## Other Logger improvements
|
||||
|
||||
### 1. Enhancements
|
||||
Previously, Logger macros such as `debug`, `info`, and so on would always evaluate their arguments, even when nothing would be logged. From Elixir v1.7, the arguments are only evaluated when the message is logged.
|
||||
|
||||
#### EEx
|
||||
The Logger configuration system also accepts a new option called `:compile_time_purge_matching` that allows you to remove log calls with specific compile-time metadata. For example, to remove all logger calls from application `:foo` with level lower than `:info`, as well as remove all logger calls from `Bar.foo/3`, you can use the following configuration:
|
||||
|
||||
* [EEx] Allow markers `/` and `|` to be used in a custom EEx engine
|
||||
```elixir
|
||||
config :logger,
|
||||
compile_time_purge_matching: [
|
||||
[application: :foo, level_lower_than: :info],
|
||||
[module: Bar, function: "foo/3"]
|
||||
]
|
||||
```
|
||||
|
||||
## ExUnit improvements
|
||||
|
||||
ExUnit has also seen its own share of improvements. Assertions such as `assert some_fun(arg1, arg2, arg3)` will now include the value of each argument in the failure report:
|
||||
|
||||
```
|
||||
1) test function call arguments (TestOneOfEach)
|
||||
lib/ex_unit/examples/one_of_each.exs:157
|
||||
Expected truthy, got false
|
||||
code: assert some_vars(1 + 2, 3 + 4)
|
||||
arguments:
|
||||
|
||||
# 1
|
||||
3
|
||||
|
||||
# 2
|
||||
7
|
||||
|
||||
stacktrace:
|
||||
lib/ex_unit/examples/one_of_each.exs:158: (test)
|
||||
```
|
||||
|
||||
Furthermore, failures in doctests are now colored and diffed.
|
||||
|
||||
On the `mix test` side of things, there is a new `--failed` flag that runs all tests that failed the last time they ran. Finally, coverage reports generated with `mix test --cover` include a summary out of the box:
|
||||
|
||||
```
|
||||
Generating cover results ...
|
||||
|
||||
Percentage | Module
|
||||
-----------|--------------------------
|
||||
100.00% | Plug.Exception.Any
|
||||
100.00% | Plug.Adapters.Cowboy2.Stream
|
||||
100.00% | Collectable.Plug.Conn
|
||||
100.00% | Plug.Crypto.KeyGenerator
|
||||
100.00% | Plug.Parsers
|
||||
100.00% | Plug.Head
|
||||
100.00% | Plug.Router.Utils
|
||||
100.00% | Plug.RequestId
|
||||
... | ...
|
||||
-----------|--------------------------
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.2 (2018-08-05)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Add `format_string!/2` and `format_file!/2` for automatic code formatting
|
||||
* [Code] Support column annotations in quoted expressions with `columns: true` in `Code.string_to_quoted/2`
|
||||
* [Enumerable] Add `Enumerable.slice/1` and optimize many `Enum` operations with the new protocol. This allows data-structures with index-based random access to provide a non-linear implementation
|
||||
* [Inspect.Algebra] Add `:strict` and `:flex` breaks
|
||||
* [Inspect.Algebra] Allow a group to inherit the parent group break
|
||||
* [Inspect.Algebra] Add `force_unfit/1` and `next_break_fits/2` which give more control over document fitting
|
||||
* [Inspect.Algebra] Add `collapse_lines/1` for collapsing multiple lines to a maximum value
|
||||
* [Inspect.Algebra] Allow `nest/2` to be `:reset` or be set to the current `:cursor` position
|
||||
* [Kernel] Prefix variables with V when emitting Erlang code. This improves the integration with tools such as Erlang code formatters and the GUI debugger
|
||||
* [Kernel] Warn on the use of `length(x) == 0` in guards
|
||||
* [Kernel] Warn if `catch` comes before `rescue` in try
|
||||
* [Kernel.ParallelCompiler] Add `compile/2`, `compile_to_path/3` and `require/2` which provide detailed information about warnings and errors
|
||||
* [Stream] Add `Stream.intersperse/2`
|
||||
* [String] Update to Unicode 10
|
||||
* [String] Allow passing empty string `match` to `String.replace/4`
|
||||
* [Task] Allow a custom supervisor to be given to `Task.Supervisor.async_stream/3`
|
||||
* [Time] Add `Time.add/3`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Callbacks] Add `ExUnit.Callbacks.start_supervised!/2`
|
||||
* [ExUnit.Case] Generate a random seed per test based on the test suite seed
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Automatically include specs when showing documentation for functions/macros
|
||||
* [IEx.Helpers] Improve formatting of behaviours and typespecs by using the formatter
|
||||
* [DateTime] Take negative years into account in `DateTime.from_iso8601/1`
|
||||
* [Kernel] Do not emit warnings for repeated docs over different clauses due to false positives
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.build] Allow `mix archive.build` to bundle dot files via an option
|
||||
* [mix compile] Define a behavior for Mix compiler tasks and return diagnostics from compiler tasks
|
||||
* [mix compile] Track struct dependencies between files and recompile them only if the struct changes
|
||||
* [mix deps] Support `:system_env` option when specifying dependencies
|
||||
* [mix format] Add a `mix format` task that formats the given files (or the files specified in a `.formatter.exs` file)
|
||||
* [mix profile.eprof] Add a new task for time-based profiling with eprof
|
||||
* [mix test] Run all functions in a describe block by giving the `file:line` the describe block starts
|
||||
* [mix test] Report the top N slowest tests with the `--slowest N` flag
|
||||
* [mix xref] Support `--include-siblings` in reports for umbrella support
|
||||
* [mix xref] Add `mix xref graph --format stats`
|
||||
* [mix xref] Add `--only-nodes` and `--label` filters to mix xref graph
|
||||
* [mix compile] Properly mark top-level dependencies as optional and as runtime. This fixes a bug where Mix attempted to start optional dependencies of a package when those optional dependencies were not available
|
||||
* [mix compile] Avoid deadlock when a config has a timestamp later than current time
|
||||
* [mix help] Show task and alias help when both are available
|
||||
* [mix test] Do not fail suite if there are no test files
|
||||
|
||||
## v1.7.1 (2018-07-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Work-around a Dialyzer bug that causes it to loop for a long time, potentially indefinitely
|
||||
|
||||
## v1.7.0 (2018-07-25)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar.ISO] Support negative dates in `Calendar.ISO`
|
||||
* [Calendar] Add `Calendar.months_in_year/1` callback
|
||||
* [Code] Add `Code.compile_file/2` that compiles files without leaving footprints on the system
|
||||
* [Code] Add `Code.purge_compiler_modules/0` that purges any compiler module left behind. This is useful for live systems dynamically compiling code
|
||||
* [Code] Add `Code.fetch_docs/1` that returns docs in the [EEP 48](http://erlang.org/eep/eeps/eep-0048.html) format
|
||||
* [Date] Add `Date.months_in_year/1` function
|
||||
* [DynamicSupervisor] Use the name of the `DynamicSupervisor` as the ID whenever possible
|
||||
* [Exception] Provide "did you mean" suggestions on KeyError
|
||||
* [Exception] Provide more information on ArithmeticError on Erlang/OTP 21+
|
||||
* [Function] Add `Function` module with `capture/3`, `info/1` and `info/2` functions
|
||||
* [GenServer] Support the new `handle_continue/2` callback on Erlang/OTP 21+
|
||||
* [IO.ANSI] Add cursor movement to `IO.ANSI`
|
||||
* [Kernel] Support adding arbitrary documentation metadata by passing a keyword list to `@doc`, `@moduledoc` and `@typedoc`
|
||||
* [Kernel] Introduce `__STACKTRACE__` to retrieve the current stacktrace inside `catch`/`rescue` (this will be a requirement for Erlang/OTP 21+)
|
||||
* [Kernel] Raise on unsafe variables in order to allow us to better track unused variables
|
||||
* [Kernel] Warn when using `length` to check if a list is not empty on guards
|
||||
* [Kernel] Add hints on mismatched `do`/`end` and others pairs
|
||||
* [Kernel] Warn when comparing structs using the `>`, `<`, `>=` and `<=` operators
|
||||
* [Kernel] Warn on unsupported nested comparisons such as `x < y < z`
|
||||
* [Kernel] Warn if redefining documentation across clauses of the same definition
|
||||
* [Kernel] Warn on unnecessary quotes around atoms, keywords and calls
|
||||
* [Macro] Add `Macro.special_form?/2` and `Macro.operator?/2` that returns `true` if the given name/arity is a special form or operator respectively
|
||||
* [Macro.Env] Add `Macro.Env.vars/1` and `Macro.Env.has_var?/2` that gives access to environment data without accessing private fields
|
||||
* [Regex] Include endianness in the regex version. This allows regexes to be recompiled when an archive is installed in a system with a different endianness
|
||||
* [Registry] Add `Registry.count/1` and `Registry.count_match/4`
|
||||
* [String] Update to Unicode 11
|
||||
* [StringIO] Add `StringIO.open/3`
|
||||
* [System] Use ISO 8601 in `System.build_info/0`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertion] Print the arguments in error reports when asserting on a function call. For example, if `assert is_list(arg)` fails, the argument will be shown in the report
|
||||
* [ExUnit.Diff] Improve diffing of lists when one list is a subset of the other
|
||||
* [ExUnit.DocTest] Show colored diffs on failed doctests
|
||||
* [ExUnit.Formatter] Excluded tests, via the `--exclude` and `--only` flags, are now shown as "Excluded" in reports. Tests skipped via `@tag :skip` are now exclusively shown as "Skipped" and in yellow
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Add `use_if_available/2`
|
||||
* [IEx.Helpers] Allow `force: true` option in `recompile/1`
|
||||
* [IEx.Helpers] Add `:allocators` pane to `runtime_info/1`
|
||||
* [IEx.Helpers] Show documentation metadata in `h/1` helpers
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Ensure nil metadata is always pruned
|
||||
* [Logger] Only evaluate Logger macro arguments when the message will be logged
|
||||
* [Logger] Add `:compile_time_purge_matching` to purge logger calls that match certain compile time metadata, such as module names and application names
|
||||
* [Logger] Log to `:stderr` if a backend fails and there are no other backends
|
||||
* [Logger] Allow translators to return custom metadata
|
||||
* [Logger] Return `:crash_reason`, `:initial_call` and `:registered_name` as metadata in crash reports coming from Erlang/OTP
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.install] Add support for the Hex organization via `--organization`
|
||||
* [mix archive.uninstall] Support `--force` flag
|
||||
* [mix compile] Improve support for external build tools such as `rebar`
|
||||
* [mix deps] Include `override: true` in rebar dependencies to make the behaviour closer to how rebar3 works (although diverged deps are still marked as diverged)
|
||||
* [mix escript.install] Add support for the Hex organization via `--organization`
|
||||
* [mix escript.uninstall] Support `--force` flag
|
||||
* [mix help] Also list aliases
|
||||
* [mix local] Use ipv6 with auto fallback to ipv4 when downloading data
|
||||
* [mix profile] Allow all profiling tasks to run programatically
|
||||
* [mix test] Add `--failed` option that only runs previously failed tests
|
||||
* [mix test] Print coverage summary by default when the `--cover` flag is given
|
||||
* [Mix.Project] Add `Mix.Project.clear_deps_cache/0`
|
||||
* [Mix.Project] Add `Mix.Project.config_mtime/0` that caches the config mtime values to avoid filesystem access
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Support path with spaces as argument to elixir.bat
|
||||
* [Kernel] Solve a precedence issue between `&` and `|`, such as `[&Foo.bar/1 | &Baz.bat/2]`
|
||||
* [Kernel] Do not load dynamic Elixir modules as `:in_memory` as this value is not officially supported by the code server. Instead, use an empty list, which is the same value used by Erlang.
|
||||
* [Kernel] Validate variable struct name is atom when used in pattern matching
|
||||
* [Macro] Fix `Macro.to_string/2` for tuple calls, such as `alias Foo.{Bar, Baz}`
|
||||
* [MapSet] Return valid MapSet when unioning a legacy MapSet
|
||||
* [String] Properly downcase the greek sigma letter in `String.downcase/1`
|
||||
* [URI] Preserve empty fragments in `URI.parse/1`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps] Ensure optional dependencies in umbrella applications are loaded
|
||||
* [mix xref] Take compile dependencies with higher priority than runtime ones when building a graph
|
||||
* [mix xref] Handle external files for xref callers and warnings
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect.Algebra] `surround/3` and `surround_many/6` are deprecated in favor of `container_doc/6`
|
||||
* [Kernel.ParallelCompiler] `files/2` and `files_to_path/3` are deprecated in favor of `compile/2` and `compile_to_path/3`
|
||||
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
|
||||
* [IO.ANSI.Docs] Fix table column alignment when converting docs to ANSI escapes
|
||||
* [Code] Ensure `string_to_quoted` returns error tuples instead of raising in certain constructs
|
||||
* [Code.Formatter] Consistently format keyword lists in function calls with and without parens
|
||||
* [Code.Formatter] Do not break after `->` when there are only comments and one-line clauses
|
||||
* [File] Allow the `:trim_bom` option to be used with `:encoding`
|
||||
* [Kernel] Raise on unsafe variables as some of the code emitted with unsafe variables would not correctly propagate variables or would disable tail call optimization semantics
|
||||
* [Kernel] Do not crash on dynamic sizes in binary generators with collectable into in comprehensions
|
||||
* [Kernel] Do not crash on literals with non-unary size in binary generators with collectable into in comprehensions
|
||||
* [Task] Improve error reports and exit reasons for failed tasks on Erlang/OTP 20+
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Formatter] `:case_started` and `:case_finished` events are deprecated in favor of `:module_started` and `:module_finished`
|
||||
* [ExUnit.Case] Raise proper error if `@tag` and `@moduletag` are used before `use ExUnit.Case`
|
||||
* [ExUnit.Case] Raise proper error if `@describetag` is used outside of `describe/2` blocks
|
||||
* [ExUnit.DocTest] Emit proper assertion error on doctests with invalid UTF-8
|
||||
|
||||
### 4. Deprecations
|
||||
#### Mix
|
||||
|
||||
* [mix archive.install] Fetch optional dependencies when installing an archive from Git/Hex
|
||||
* [mix compile] Properly track config files in umbrella projects and recompile when any relevant umbrella configuration changes
|
||||
* [mix deps] Ensure the same dependency from different SCMs are tagged as diverged when those SCMs are remote and non-remote
|
||||
* [mix deps] Ensure we re-run dependency resolution when overriding a skipped dep in umbrella
|
||||
* [mix deps.compile] Perform clean builds for dependencies on outdated locks to avoid old modules from affecting future compilation
|
||||
* [mix escript.install] Fetch optional dependencies when installing an escript from Git/Hex
|
||||
|
||||
### 3. Soft-deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] `Enum.partition/2` is deprecated in favor of `Enum.split_with/2`
|
||||
* [Keyword] `Keyword.replace/3` is deprecated in favor of `Keyword.fetch/2` and `Keyword.put/3`
|
||||
* [Map] `Map.replace/3` is deprecated in favor of `Map.fetch/2` and `Map.put/3`
|
||||
* [Range] Deprecate `Range.range?/1` in favor of pattern matching on `_ .. _`
|
||||
* [Code] Deprecate `Code.load_file/2` in favor of `Code.compile_file/2`
|
||||
* [Code] Deprecate `Code.loaded_files/0` in favor of `Code.required_files/0`
|
||||
* [Code] Deprecate `Code.unload_files/1` in favor of `Code.unrequire_files/1`
|
||||
|
||||
## v1.5
|
||||
#### Logger
|
||||
|
||||
The CHANGELOG for v1.5 releases can be found [in the v1.5 branch](https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md).
|
||||
* [Logger] `compile_time_purge_level` is deprecated in favor of `compile_time_purge_matching`
|
||||
|
||||
### 4. Hard-deprecations
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] `Code.get_docs/2` is deprecated in favor of `Code.fetch_docs/1`
|
||||
* [Enum] `Enum.chunk/2/3/4` is deprecated in favor of `Enum.chunk_every/2/3/4` - notice `chunk_every` does not discard incomplete chunks by default
|
||||
* [GenServer] Warn if `super` is used in any of the GenServer callbacks
|
||||
* [Kernel] `not left in right` is ambiguous and is deprecated in favor of `left not in right`
|
||||
* [Kernel] Warn on confusing operator sequences, such as `1+++1` meaning `1 ++ +1` or `........` meaning `... .. ...`
|
||||
* [OptionParser] Deprecate dynamic option parser mode that depended on atoms to be previously loaded and therefore behaved inconsistently
|
||||
* [Stream] `Stream.chunk/2/3/4` is deprecated in favor of `Stream.chunk_every/2/3/4` - notice `chunk_every` does not discard incomplete chunks by default
|
||||
|
||||
## v1.6
|
||||
|
||||
The CHANGELOG for v1.6 releases can be found [in the v1.6 branch](https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md).
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
### Environment
|
||||
|
||||
* Elixir & Erlang versions (elixir --version):
|
||||
* Elixir & Erlang/OTP versions (elixir --version):
|
||||
* Operating system:
|
||||
|
||||
### Current behavior
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
REBAR ?= "$(CURDIR)/rebar"
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
CANONICAL := master/
|
||||
CANONICAL := v1.7/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
GENERATE_APP := $(CURDIR)/lib/elixir/generate_app.escript
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
Q := @
|
||||
LIBDIR := lib
|
||||
@@ -16,7 +16,7 @@ INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
GIT_REVISION = $(strip $(shell git rev-parse HEAD 2> /dev/null ))
|
||||
GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | tail -1) )
|
||||
|
||||
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean clean_residual_files install_man clean_man docs Docs.zip Precompiled.zip zips
|
||||
.PHONY: install compile erlang elixir unicode app build_plt clean_plt dialyze test clean clean_residual_files install_man clean_man docs Docs.zip Precompiled.zip zips
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Functions
|
||||
@@ -24,7 +24,7 @@ GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$hea
|
||||
define CHECK_ERLANG_RELEASE
|
||||
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 19)])' -s erlang halt | grep -q '^true'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 19.0 is required to build Elixir"; \
|
||||
echo "At least Erlang/OTP 19.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
||||
@@ -51,25 +51,30 @@ endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
APP := lib/elixir/ebin/elixir.app
|
||||
PARSER := lib/elixir/src/elixir_parser.erl
|
||||
KERNEL := lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE := lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
compile: erlang elixir
|
||||
compile: erlang $(APP) elixir
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
erlang: $(PARSER)
|
||||
$(Q) if [ ! -f $(APP) ]; then $(call CHECK_ERLANG_RELEASE); fi
|
||||
$(Q) cd lib/elixir && mkdir -p ebin && erl -make
|
||||
|
||||
$(PARSER): lib/elixir/src/elixir_parser.yrl
|
||||
$(Q) erlc -o $@ +'{verbose,true}' +'{report,true}' $<
|
||||
|
||||
# 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
|
||||
# then Mix and then compile EEx fully
|
||||
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 lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
$(call CHECK_ERLANG_RELEASE); \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler bootstrap -s erlang halt; \
|
||||
fi
|
||||
@@ -77,8 +82,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/kernel.ex" -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -f lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) $(MAKE) app
|
||||
|
||||
app: $(APP)
|
||||
$(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP)
|
||||
$(Q) $(GENERATE_APP) $< $@ $(VERSION)
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@@ -109,9 +117,9 @@ install: compile
|
||||
$(MAKE) install_man
|
||||
|
||||
clean:
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf $(PARSER)
|
||||
$(Q) $(MAKE) clean_residual_files
|
||||
|
||||
clean_elixir:
|
||||
@@ -184,10 +192,18 @@ Precompiled.zip: build_man compile
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
@ echo ""
|
||||
@ echo "## Checksums"
|
||||
@ echo ""
|
||||
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ echo ""
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
test: test_formatted test_erlang test_elixir
|
||||
|
||||
test_windows: test test_taskkill
|
||||
|
||||
@@ -199,6 +215,9 @@ TEST_ERL = lib/elixir/test/erlang
|
||||
TEST_EBIN = lib/elixir/test/ebin
|
||||
TEST_ERLS = $(addprefix $(TEST_EBIN)/, $(addsuffix .beam, $(basename $(notdir $(wildcard $(TEST_ERL)/*.erl)))))
|
||||
|
||||
test_formatted: compile
|
||||
bin/elixir bin/mix format --check-formatted
|
||||
|
||||
test_erlang: compile $(TEST_ERLS)
|
||||
@ echo "==> elixir (eunit)"
|
||||
$(Q) $(ERL) -pa $(TEST_EBIN) -s test_helper test;
|
||||
@@ -243,13 +262,13 @@ build_man: man/iex.1 man/elixir.1
|
||||
|
||||
man/iex.1:
|
||||
$(Q) cp man/iex.1.in man/iex.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r common" man/iex.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r man/common" man/iex.1
|
||||
$(Q) sed -i.bak "/{COMMON}/d" man/iex.1
|
||||
$(Q) rm -f man/iex.1.bak
|
||||
|
||||
man/elixir.1:
|
||||
$(Q) cp man/elixir.1.in man/elixir.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r common" man/elixir.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r man/common" man/elixir.1
|
||||
$(Q) sed -i.bak "/{COMMON}/d" man/elixir.1
|
||||
$(Q) rm -f man/elixir.1.bak
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
=========
|
||||
[](https://travis-ci.org/elixir-lang/elixir)
|
||||
[](https://ci.appveyor.com/project/josevalim/elixir)
|
||||
|
||||
|
||||
Elixir is a dynamic, functional language designed for building scalable and maintainable applications.
|
||||
|
||||
@@ -31,11 +29,11 @@ If Elixir fails to build (specifically when pulling in a new version via
|
||||
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.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version
|
||||
(Elixir requires Erlang 18.0 or later). You can check your Erlang version
|
||||
However, if tests fail, it is likely you have an outdated Erlang/OTP version
|
||||
(Elixir requires Erlang/OTP 19.0 or later). You can check your Erlang/OTP version
|
||||
by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang/OTP 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
Erlang/OTP 19 [erts-8.0] [smp:2:2] [async-threads:10] [kernel-poll:false]
|
||||
|
||||
If you have properly set up your dependencies and tests still fail,
|
||||
you may want to open up a bug report, as explained next.
|
||||
@@ -88,7 +86,7 @@ also run tests for a specific framework `make test_#{NAME}`, for example,
|
||||
`make test_ex_unit`. If you just changed something in the Elixir's standard
|
||||
library, you can run only that portion through `make test_stdlib`.
|
||||
|
||||
In case you are changing a single file, you can compile and run tests only
|
||||
If you are changing just one file, you can choose to compile and run tests only
|
||||
for that particular file for fast development cycles. For example, if you
|
||||
are changing the String module, you can compile it and run its tests as:
|
||||
|
||||
@@ -144,7 +142,7 @@ rather manage all changes yourself, you can disable "Allow edits from maintainer
|
||||
feature when submitting your pull request.
|
||||
|
||||
The Elixir team may optionally assign someone to review a pull request.
|
||||
In case someone is assigned, they must explicitly approve the code before
|
||||
If someone is assigned, they must explicitly approve the code before
|
||||
another team member can merge it.
|
||||
|
||||
When the review finishes, your pull request will be squashed and merged
|
||||
|
||||
+23
-19
@@ -1,37 +1,41 @@
|
||||
# Release process
|
||||
|
||||
## All releases
|
||||
|
||||
This document simply outlines the release process:
|
||||
## Shipping a new version
|
||||
|
||||
1. Ensure you are running on the oldest supported Erlang version
|
||||
|
||||
2. Remove all `-dev` extension from versions (see below for all files)
|
||||
2. Update version in /VERSION
|
||||
|
||||
3. Ensure CHANGELOG is updated and add current date
|
||||
3. Ensure /CHANGELOG.md is updated, versioned and add the current date
|
||||
|
||||
4. If a new `vMAJOR.MINOR`, replace "master" with "vVERSION" in the "Compatibility and Deprecations" page and commit
|
||||
4. Update "Compatibility and Deprecations" if a new OTP version is supported
|
||||
|
||||
5. If a new `vMAJOR.MINOR`, create a new branch "vMAJOR.MINOR" and set `CANONICAL=` in Makefile
|
||||
5. Commit changes above with title "Release vVERSION" and generate a new tag
|
||||
|
||||
6. Add an entry for the new version to the OTP compatibility table in the "Compatibility and Deprecations" page
|
||||
6. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
7. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
7. Recompile an existing project (for example, Ecto) to ensure manifests can be upgraded
|
||||
|
||||
8. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
8. Push branch and the new tag
|
||||
|
||||
9. Recompile an existing project (for example, Ecto) to ensure manifests can be upgraded
|
||||
9. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases, and include SHAs+CHANGELOG
|
||||
|
||||
10. Push branch and the new tag
|
||||
10. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases
|
||||
## Creating a new vMAJOR.MINOR branch
|
||||
|
||||
12. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
### In the new branch
|
||||
|
||||
13. After a new `vMAJOR.MINOR`, move back to master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vMAJOR.MINOR+1"
|
||||
1. Set `CANONICAL=` in /Makefile
|
||||
|
||||
## Places where version is mentioned
|
||||
2. Update **all** tables in "Compatibility and Deprecations"
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG.md
|
||||
* lib/elixir/src/elixir.app.src
|
||||
3. Commit "Prepare vMAJOR.MINOR for release"
|
||||
|
||||
### Back in master
|
||||
|
||||
1. Bump /VERSION file
|
||||
|
||||
2. Start new /CHANGELOG.md
|
||||
|
||||
3. Commit "Start vMAJOR.MINOR+1"
|
||||
+1
-5
@@ -117,11 +117,7 @@ if [ "$OS" = "Windows_NT" ] && [ $USE_WERL ]; then
|
||||
fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
ERL_PATH="$SCRIPT_PATH"/"$ERL_EXEC"
|
||||
else
|
||||
ERL_PATH="$ERL_EXEC"
|
||||
fi
|
||||
ERL_PATH="$ERL_EXEC"
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message, :file, :line]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"#{exception.file}:#{exception.line}: #{exception.message}"
|
||||
end
|
||||
@@ -11,7 +12,7 @@ defmodule EEx do
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way.
|
||||
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
iex> EEx.eval_string("foo <%= bar %>", bar: "baz")
|
||||
"foo baz"
|
||||
|
||||
## API
|
||||
@@ -83,7 +84,7 @@ defmodule EEx do
|
||||
An example is the `@` macro which allows easy data access
|
||||
in a template:
|
||||
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
In other words, `<%= @foo %>` translates to:
|
||||
@@ -186,7 +187,7 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
iex> EEx.eval_string("foo <%= bar %>", bar: "baz")
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
|
||||
@@ -4,8 +4,7 @@ require EEx
|
||||
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(get_stacktrace()))}
|
||||
end
|
||||
|
||||
EEx.function_from_string(:def, :string_sample, "<%= a + b %>", [:a, :b])
|
||||
@@ -19,21 +18,19 @@ defmodule EExTest.Compiled do
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(get_stacktrace()))}
|
||||
end
|
||||
|
||||
@file "unknown"
|
||||
def unknown do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(get_stacktrace()))}
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
defp get_stacktrace do
|
||||
try do
|
||||
:erlang.error("failed")
|
||||
catch
|
||||
:error, _ -> System.stacktrace()
|
||||
rescue
|
||||
_ -> __STACKTRACE__
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -447,13 +444,13 @@ defmodule EExTest do
|
||||
file = to_charlist(Path.relative_to_cwd(__ENV__.file))
|
||||
|
||||
assert EExTest.Compiled.before_compile() ==
|
||||
{8, {EExTest.Compiled, :before_compile, 0, [file: file, line: 7]}}
|
||||
{7, {EExTest.Compiled, :before_compile, 0, [file: file, line: 7]}}
|
||||
|
||||
assert EExTest.Compiled.after_compile() ==
|
||||
{23, {EExTest.Compiled, :after_compile, 0, [file: file, line: 22]}}
|
||||
{21, {EExTest.Compiled, :after_compile, 0, [file: file, line: 21]}}
|
||||
|
||||
assert EExTest.Compiled.unknown() ==
|
||||
{29, {EExTest.Compiled, :unknown, 0, [file: 'unknown', line: 28]}}
|
||||
{26, {EExTest.Compiled, :unknown, 0, [file: 'unknown', line: 26]}}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{erl_opts, [
|
||||
{'src/*', [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
@@ -14,10 +14,6 @@
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
debug_info,
|
||||
{outdir, "ebin/"}
|
||||
]}.
|
||||
+21
-23
@@ -4,36 +4,38 @@
|
||||
groups_for_modules: [
|
||||
# [Kernel, Kernel.SpecialForms],
|
||||
|
||||
"Data & Behaviours": [
|
||||
Access,
|
||||
"Basic Types": [
|
||||
Atom,
|
||||
Base,
|
||||
Bitwise,
|
||||
Calendar,
|
||||
Calendar.ISO,
|
||||
Date,
|
||||
Date.Range,
|
||||
DateTime,
|
||||
Enum,
|
||||
Exception,
|
||||
Float,
|
||||
Function,
|
||||
Integer,
|
||||
Keyword,
|
||||
List,
|
||||
Map,
|
||||
MapSet,
|
||||
NaiveDateTime,
|
||||
Range,
|
||||
Record,
|
||||
Regex,
|
||||
Stream,
|
||||
String,
|
||||
Time,
|
||||
Tuple,
|
||||
URI,
|
||||
Version,
|
||||
Version
|
||||
],
|
||||
"Collections & Enumerables": [
|
||||
Access,
|
||||
Date.Range,
|
||||
Enum,
|
||||
Keyword,
|
||||
List,
|
||||
Map,
|
||||
MapSet,
|
||||
Range,
|
||||
Stream
|
||||
],
|
||||
|
||||
"IO & System": [
|
||||
File,
|
||||
File.Stat,
|
||||
@@ -45,17 +47,15 @@
|
||||
Path,
|
||||
Port,
|
||||
StringIO,
|
||||
System,
|
||||
System
|
||||
],
|
||||
|
||||
"Modules & Code": [
|
||||
Code,
|
||||
Kernel.ParallelCompiler,
|
||||
Macro,
|
||||
Macro.Env,
|
||||
Module,
|
||||
Module
|
||||
],
|
||||
|
||||
"Processes & Applications": [
|
||||
Agent,
|
||||
Application,
|
||||
@@ -66,10 +66,9 @@
|
||||
Registry,
|
||||
Supervisor,
|
||||
Task,
|
||||
Task.Supervisor,
|
||||
Task.Supervisor
|
||||
],
|
||||
|
||||
"Protocols": [
|
||||
Protocols: [
|
||||
Collectable,
|
||||
Enumerable,
|
||||
Inspect,
|
||||
@@ -77,10 +76,9 @@
|
||||
Inspect.Opts,
|
||||
List.Chars,
|
||||
Protocol,
|
||||
String.Chars,
|
||||
String.Chars
|
||||
],
|
||||
|
||||
"Deprecated": [
|
||||
Deprecated: [
|
||||
Behaviour,
|
||||
Dict,
|
||||
GenEvent,
|
||||
@@ -88,6 +86,6 @@
|
||||
HashSet,
|
||||
Set,
|
||||
Supervisor.Spec
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env escript
|
||||
%% -*- erlang -*-
|
||||
|
||||
main([Source, Target, Version]) ->
|
||||
{ok, [{application, Name, Props0}]} = file:consult(Source),
|
||||
Ebin = filename:dirname(Target),
|
||||
Files = filelib:wildcard(filename:join(Ebin, "*.beam")),
|
||||
Mods = [list_to_atom(filename:basename(F, ".beam")) || F <- Files],
|
||||
Props1 = lists:keyreplace(modules, 1, Props0, {modules, Mods}),
|
||||
Props = lists:keyreplace(vsn, 1, Props1, {vsn, Version}),
|
||||
AppDef = io_lib:format("~tp.~n", [{application, Name, Props}]),
|
||||
ok = file:write_file(Target, AppDef),
|
||||
io:format("Generated ~ts.app~n", [Name]).
|
||||
+143
-128
@@ -1,27 +1,53 @@
|
||||
defmodule Access do
|
||||
@moduledoc """
|
||||
Key-based access to data structures using the `data[key]` syntax.
|
||||
Key-based access to data structures.
|
||||
|
||||
Elixir provides two syntaxes for accessing values. `user[:name]`
|
||||
is used by dynamic structures, like maps and keywords, while
|
||||
`user.name` is used by structs. The main difference is that
|
||||
`user[:name]` won't raise if the key `:name` is missing but
|
||||
`user.name` will raise if there is no `:name` key.
|
||||
Elixir supports three main key-value constructs: keywords,
|
||||
maps, and structs. It also supports two mechanisms to access those keys:
|
||||
by brackets (via `data[key]`) and by dot-syntax (via `data.field`).
|
||||
|
||||
Besides the cases above, this module provides convenience
|
||||
functions for accessing other structures, like `at/1` for
|
||||
lists and `elem/1` for tuples. Those functions can be used
|
||||
by the nested update functions in `Kernel`, such as
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3`,
|
||||
`Kernel.get_and_update_in/3` and friends.
|
||||
In the next section we will briefly recap the key-value constructs and then
|
||||
discuss the access mechanisms.
|
||||
|
||||
## Dynamic lookups
|
||||
## Key-value constructs
|
||||
|
||||
Out of the box, `Access` works with `Keyword` and `Map`:
|
||||
Elixir provides three main key-value constructs, summarized below:
|
||||
|
||||
* keyword lists - they are lists of two-element tuples where
|
||||
the first element is an atom. Commonly written in the
|
||||
`[key: value]` syntax, they support only atom keys. Keyword
|
||||
lists are used almost exclusively to pass options to functions
|
||||
and macros. They keep the user ordering and allow duplicate
|
||||
keys. See the `Keyword` module.
|
||||
|
||||
* maps - they are the "go to" key-value data structure in Elixir.
|
||||
They are capable of supporting billions of keys of any type. They are
|
||||
written using the `%{key => value}` syntax and also support the
|
||||
`%{key: value}` syntax when the keys are atoms. They do not
|
||||
have any specified ordering and do not allow duplicate keys.
|
||||
See the `Map` module.
|
||||
|
||||
* structs - they are named maps with a pre-determined set of keys.
|
||||
They are defined with `defstruct/1` and written using the
|
||||
`%StructName{key: value}` syntax.
|
||||
|
||||
## Key-based accessors
|
||||
|
||||
Elixir provides two mechanisms to access data structures by key,
|
||||
described next.
|
||||
|
||||
### Bracket-based access
|
||||
|
||||
The `data[key]` syntax is used to access data structures with a
|
||||
dynamic number of keys, such as keywords and maps. The key can
|
||||
be of any type. The bracket-based access syntax returns `nil`
|
||||
if the key does not exist:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
1
|
||||
iex> keywords[:c]
|
||||
nil
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> map[:a]
|
||||
@@ -31,110 +57,91 @@ defmodule Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
Note that the dynamic lookup syntax (`term[key]`) roughly translates to
|
||||
`Access.get(term, key, nil)`.
|
||||
|
||||
`Access` can be combined with `Kernel.put_in/3` to put a value
|
||||
in a given key:
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> put_in map[:a], 3
|
||||
%{a: 3, b: 2}
|
||||
|
||||
This syntax is very convenient as it can be nested arbitrarily:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in users["john"][:age], 28
|
||||
iex> put_in(users["john"][:age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
Furthermore, `Access` transparently ignores `nil` values:
|
||||
Furthermore, the bracket-based access syntax transparently ignores
|
||||
`nil` values. When trying to access anything on a `nil` value, `nil`
|
||||
is returned:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:c][:unknown]
|
||||
nil
|
||||
|
||||
Since `Access` is a behaviour, it can be implemented for key-value
|
||||
data structures. The implementation should be added to the
|
||||
module that defines the struct being accessed. `Access` requires the
|
||||
key comparison to be implemented using the `===` operator.
|
||||
iex> nil[:a]
|
||||
nil
|
||||
|
||||
## Static lookups
|
||||
Internally, `data[key]` translates to `Access.get(term, key, nil)`.
|
||||
Developers interested in implementing their own key-value data
|
||||
structures can implement the `Access` behaviour to provide the
|
||||
bracket-based access syntax. `Access` requires the key comparison
|
||||
to be implemented using the `===/2` operator.
|
||||
|
||||
The `Access` syntax (`data[key]`) cannot be used to access fields in
|
||||
structs, since structs do not implement the `Access` behaviour by
|
||||
default. It is also a design decision: the dynamic access lookup
|
||||
is meant to be used for dynamic key-value structures, like maps
|
||||
and keywords, and not by static ones like structs (where fields are
|
||||
known and not dynamic).
|
||||
### Dot-based syntax
|
||||
|
||||
Therefore Elixir provides a static lookup for struct fields and for atom
|
||||
fields in maps. Imagine a struct named `User` with a `:name` field.
|
||||
The following would raise:
|
||||
The `data.field` syntax is used exclusively to access atom fields
|
||||
in maps and structs. If the accessed field does not exist, an error is
|
||||
raised. This is a deliberate decision: since all of the
|
||||
fields in a struct are pre-determined, structs support only the
|
||||
dot-based syntax and not the access one.
|
||||
|
||||
Imagine a struct named `User` with a `:name` field. The following would raise:
|
||||
|
||||
user = %User{name: "John"}
|
||||
user[:name]
|
||||
# ** (UndefinedFunctionError) undefined function User.fetch/2 (User does not implement the Access behaviour)
|
||||
|
||||
Structs instead use the `user.name` syntax to access fields:
|
||||
Instead we should use the `user.name` syntax to access fields:
|
||||
|
||||
user.name
|
||||
#=> "John"
|
||||
|
||||
The same `user.name` syntax can also be used by `Kernel.put_in/2`
|
||||
for updating structs fields:
|
||||
|
||||
put_in user.name, "Mary"
|
||||
#=> %User{name: "Mary"}
|
||||
|
||||
Differently from `user[:name]`, `user.name` is not extensible via
|
||||
a behaviour and is restricted only to structs and atom keys in maps.
|
||||
|
||||
As mentioned above, this works for atom keys in maps as well. Refer to the
|
||||
`Map` module for more information on this.
|
||||
### Summing up
|
||||
|
||||
Summing up:
|
||||
The bracket-based syntax, `user[:name]`, is used by dynamic structures,
|
||||
is extensible and returns nil on misisng keys.
|
||||
|
||||
* `user[:name]` is used by dynamic structures, is extensible and
|
||||
does not raise on missing keys
|
||||
* `user.name` is used by static structures, it is not extensible
|
||||
and it will raise on missing keys
|
||||
The dot-based syntax, `user.name`, is used exclusively to access atom
|
||||
keys in maps and structs, and it raises on missing keys.
|
||||
|
||||
## Accessors
|
||||
## Nested data structures
|
||||
|
||||
While Elixir provides built-in syntax only for traversing dynamic
|
||||
and static key-value structures, this module provides convenience
|
||||
functions for traversing other structures, like tuples and lists,
|
||||
to be used alongside `Kernel.put_in/2` in others.
|
||||
Both key-based access syntaxes can be used with the nested update
|
||||
functions and macros in `Kernel`, such as `Kernel.get_in/2`, `Kernel.put_in/3`,
|
||||
`Kernel.update_in/3`, `Kernel.pop_in/2`, and `Kernel.get_and_update_in/3`.
|
||||
|
||||
For instance, given a user map with `:name` and `:languages` keys, here is how
|
||||
to deeply traverse the map and convert all language names to uppercase:
|
||||
For example, to update a map inside another map:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"].age, 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
This module provides convenience functions for traversing other
|
||||
structures, like tuples and lists. These functions can be used
|
||||
in all the `Access`-related functions and macros in `Kernel`.
|
||||
|
||||
For instance, given a user map with the `:name` and `:languages` keys,
|
||||
here is how to deeply traverse the map and convert all language names
|
||||
to uppercase:
|
||||
|
||||
iex> languages = [
|
||||
...> %{name: "elixir", type: :functional},
|
||||
...> %{name: "c", type: :procedural},
|
||||
...> ]
|
||||
iex> user = %{name: "john", languages: languages}
|
||||
iex> update_in user, [:languages, Access.all(), :name], &String.upcase/1
|
||||
iex> update_in(user, [:languages, Access.all(), :name], &String.upcase/1)
|
||||
%{name: "john",
|
||||
languages: [%{name: "ELIXIR", type: :functional},
|
||||
%{name: "C", type: :procedural}]}
|
||||
|
||||
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for some of the
|
||||
available accessors.
|
||||
|
||||
## Implementing the Access behaviour for custom data structures
|
||||
|
||||
In order to be able to use the `Access` behaviour with custom data structures
|
||||
(which have to be structs), such structures have to implement the `Access`
|
||||
behaviour. For example, for a `User` struct, this would have to be done:
|
||||
|
||||
defmodule User do
|
||||
defstruct [:name, :email]
|
||||
|
||||
@behaviour Access
|
||||
# Implementation of the Access callbacks...
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
@type container :: keyword | struct | map
|
||||
@@ -174,28 +181,6 @@ defmodule Access do
|
||||
"""
|
||||
@callback fetch(term :: t, key) :: {:ok, value} | :error
|
||||
|
||||
@doc """
|
||||
Invoked in order to access the value stored under `key` in the given term `term`,
|
||||
defaulting to `default` if not present.
|
||||
|
||||
This function should return the value under `key` in `term` if there's
|
||||
such key, otherwise `default`.
|
||||
|
||||
For most data structures, this can be implemented using `fetch/2` internally;
|
||||
for example:
|
||||
|
||||
def get(structure, key, default) do
|
||||
case fetch(structure, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
See the `Map.get/3` and `Keyword.get/3` implementations for examples of
|
||||
how to implement this callback.
|
||||
"""
|
||||
@callback get(term :: t, key, default :: value) :: value
|
||||
|
||||
@doc """
|
||||
Invoked in order to access the value under `key` and update it at the same time.
|
||||
|
||||
@@ -205,9 +190,12 @@ defmodule Access do
|
||||
|
||||
If the passed function returns `{get_value, update_value}`,
|
||||
the return value of this callback should be `{get_value, new_data}`, where:
|
||||
- `get_value` is the retrieved value (which can be operated on before being returned)
|
||||
- `update_value` is the new value to be stored under `key`
|
||||
- `new_data` is `data` after updating the value of `key` with `update_value`.
|
||||
|
||||
* `get_value` is the retrieved value (which can be operated on before being returned)
|
||||
|
||||
* `update_value` is the new value to be stored under `key`
|
||||
|
||||
* `new_data` is `data` after updating the value of `key` with `update_value`.
|
||||
|
||||
If the passed function returns `:pop`, the return value of this callback
|
||||
must be `{value, new_data}` where `value` is the value under `key`
|
||||
@@ -235,10 +223,8 @@ defmodule Access do
|
||||
|
||||
defmacrop raise_undefined_behaviour(exception, module, top) do
|
||||
quote do
|
||||
stacktrace = System.stacktrace()
|
||||
|
||||
exception =
|
||||
case stacktrace do
|
||||
case __STACKTRACE__ do
|
||||
[unquote(top) | _] ->
|
||||
reason = "#{inspect(unquote(module))} does not implement the Access behaviour"
|
||||
%{unquote(exception) | reason: reason}
|
||||
@@ -247,7 +233,7 @@ defmodule Access do
|
||||
unquote(exception)
|
||||
end
|
||||
|
||||
reraise exception, stacktrace
|
||||
reraise exception, __STACKTRACE__
|
||||
end
|
||||
end
|
||||
|
||||
@@ -257,6 +243,15 @@ defmodule Access do
|
||||
|
||||
Returns `{:ok, value}` where `value` is the value under `key` if there is such
|
||||
a key, or `:error` if `key` is not found.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Access.fetch(%{name: "meg", age: 26}, :name)
|
||||
{:ok, "meg"}
|
||||
|
||||
iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(container, term) :: {:ok, term} | :error
|
||||
@spec fetch(nil_container, any) :: :error
|
||||
@@ -298,11 +293,26 @@ defmodule Access do
|
||||
|
||||
Returns the value under `key` if there is such a key, or `default` if `key` is
|
||||
not found.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Access.get(%{name: "john"}, :name, "default name")
|
||||
"john"
|
||||
iex> Access.get(%{name: "john"}, :age, 25)
|
||||
25
|
||||
|
||||
iex> Access.get([ordered: true], :timeout)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec get(container, term, term) :: term
|
||||
@spec get(nil_container, any, default) :: default when default: var
|
||||
def get(container, key, default \\ nil)
|
||||
|
||||
# Reimplementing the same logic as Access.fetch/2 here is done for performance, since
|
||||
# this is called a lot and calling fetch/2 means introducing some overhead (like
|
||||
# building the "{:ok, _}" tuple and deconstructing it back right away).
|
||||
|
||||
def get(%module{} = container, key, default) do
|
||||
try do
|
||||
module.fetch(container, key)
|
||||
@@ -436,8 +446,8 @@ defmodule Access do
|
||||
The returned function uses the default value if the key does not exist.
|
||||
This can be used to specify defaults and safely traverse missing keys:
|
||||
|
||||
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name)])
|
||||
nil
|
||||
iex> get_in(%{}, [Access.key(:user, %{name: "meg"}), Access.key(:name)])
|
||||
"meg"
|
||||
|
||||
Such is also useful when using update functions, allowing us to introduce
|
||||
values as we traverse the data structure for updates:
|
||||
@@ -450,8 +460,8 @@ defmodule Access do
|
||||
iex> map = %{user: %{name: "john"}}
|
||||
iex> get_in(map, [Access.key(:unknown, %{}), Access.key(:name, "john")])
|
||||
"john"
|
||||
iex> get_and_update_in(map, [Access.key(:user), Access.key(:name)], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(map, [Access.key(:user), Access.key(:name)], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", %{user: %{name: "JOHN"}}}
|
||||
iex> pop_in(map, [Access.key(:user), Access.key(:name)])
|
||||
@@ -488,15 +498,15 @@ defmodule Access do
|
||||
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
|
||||
`Kernel.get_and_update_in/3`, and friends.
|
||||
|
||||
The returned function raises if the key does not exist.
|
||||
Similar to `key/2`, but the returned function raises if the key does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> map = %{user: %{name: "john"}}
|
||||
iex> get_in(map, [Access.key!(:user), Access.key!(:name)])
|
||||
"john"
|
||||
iex> get_and_update_in(map, [Access.key!(:user), Access.key!(:name)], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(map, [Access.key!(:user), Access.key!(:name)], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", %{user: %{name: "JOHN"}}}
|
||||
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
|
||||
@@ -537,13 +547,16 @@ defmodule Access do
|
||||
|
||||
The returned function raises if `index` is out of bounds.
|
||||
|
||||
Note that popping elements out of tuples is not possible and raises an
|
||||
error.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> map = %{user: {"john", 27}}
|
||||
iex> get_in(map, [:user, Access.elem(0)])
|
||||
"john"
|
||||
iex> get_and_update_in(map, [:user, Access.elem(0)], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(map, [:user, Access.elem(0)], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", %{user: {"JOHN", 27}}}
|
||||
iex> pop_in(map, [:user, Access.elem(0)])
|
||||
@@ -556,7 +569,7 @@ defmodule Access do
|
||||
|
||||
"""
|
||||
@spec elem(non_neg_integer) :: access_fun(data :: tuple, get_value :: term)
|
||||
def elem(index) when is_integer(index) do
|
||||
def elem(index) when is_integer(index) and index >= 0 do
|
||||
pos = index + 1
|
||||
|
||||
fn
|
||||
@@ -587,8 +600,8 @@ defmodule Access do
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> get_in(list, [Access.all(), :name])
|
||||
["john", "mary"]
|
||||
iex> get_and_update_in(list, [Access.all(), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(list, [Access.all(), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{["john", "mary"], [%{name: "JOHN"}, %{name: "MARY"}]}
|
||||
iex> pop_in(list, [Access.all(), :name])
|
||||
@@ -598,8 +611,8 @@ defmodule Access do
|
||||
numbers and multiplying odd numbers by 2:
|
||||
|
||||
iex> require Integer
|
||||
iex> get_and_update_in([1, 2, 3, 4, 5], [Access.all], fn
|
||||
...> num -> if Integer.is_even(num), do: :pop, else: {num, num * 2}
|
||||
iex> get_and_update_in([1, 2, 3, 4, 5], [Access.all], fn num ->
|
||||
...> if Integer.is_even(num), do: :pop, else: {num, num * 2}
|
||||
...> end)
|
||||
{[1, 2, 3, 4, 5], [2, 6, 10]}
|
||||
|
||||
@@ -648,8 +661,8 @@ defmodule Access do
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> get_in(list, [Access.at(1), :name])
|
||||
"mary"
|
||||
iex> get_and_update_in(list, [Access.at(0), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(list, [Access.at(0), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||
|
||||
@@ -667,8 +680,8 @@ defmodule Access do
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> get_in(list, [Access.at(10), :name])
|
||||
nil
|
||||
iex> get_and_update_in(list, [Access.at(10), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(list, [Access.at(10), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{nil, [%{name: "john"}, %{name: "mary"}]}
|
||||
|
||||
@@ -723,11 +736,11 @@ defmodule Access do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> get_in(list, [Access.filter(&(&1.salary > 20)), :name])
|
||||
["francine"]
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary <= 20)), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary <= 20)), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{["john"], [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
@@ -745,8 +758,8 @@ defmodule Access do
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> get_in(list, [Access.filter(&(&1.salary >= 50)), :name])
|
||||
[]
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary >= 50)), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary >= 50)), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{[], [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
@@ -759,7 +772,9 @@ defmodule Access do
|
||||
|
||||
iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
|
||||
** (RuntimeError) Access.filter/1 expected a list, got: %{}
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec filter((term -> boolean)) :: access_fun(data :: list, get_value :: list)
|
||||
def filter(func) when is_function(func) do
|
||||
fn op, data, next -> filter(op, data, func, next) end
|
||||
|
||||
+13
-6
@@ -77,7 +77,7 @@ defmodule Agent do
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -139,7 +139,12 @@ defmodule Agent do
|
||||
@typedoc "The agent state"
|
||||
@type state :: term
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start an agent under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def child_spec(arg) do
|
||||
%{
|
||||
id: Agent,
|
||||
@@ -149,17 +154,19 @@ defmodule Agent do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
@opts unquote(opts)
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
@doc false
|
||||
See `Supervisor`.
|
||||
"""
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]}
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
Supervisor.child_spec(default, unquote(Macro.escape(opts)))
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@@ -23,18 +23,10 @@ defmodule Agent.Server do
|
||||
{:reply, :ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_call(msg, from, state) do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
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, fun) do
|
||||
{:ok, run(fun, [state])}
|
||||
end
|
||||
@@ -45,8 +37,8 @@ defmodule Agent.Server do
|
||||
end
|
||||
|
||||
defp get_initial_call(fun) when is_function(fun, 0) do
|
||||
{:module, module} = :erlang.fun_info(fun, :module)
|
||||
{:name, name} = :erlang.fun_info(fun, :name)
|
||||
{:module, module} = Function.info(fun, :module)
|
||||
{:name, name} = Function.info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
|
||||
+251
-103
@@ -2,61 +2,87 @@ defmodule Application do
|
||||
@moduledoc """
|
||||
A module for working with applications and defining application callbacks.
|
||||
|
||||
In Elixir (actually, in Erlang/OTP), an application is a component
|
||||
implementing some specific functionality, that can be started and stopped
|
||||
as a unit, and which can be re-used in other systems.
|
||||
Applications are the idiomatic way to package software in Erlang/OTP. To get
|
||||
the idea, they are similar to the "library" concept common in other
|
||||
programming languages, but with some additional characteristics.
|
||||
|
||||
Applications are defined with an application file named `APP.app` where
|
||||
`APP` is the application name, usually in `underscore_case`. The application
|
||||
file must reside in the same `ebin` directory as the compiled modules of the
|
||||
application. In Elixir, the Mix build tool is responsible for compiling your
|
||||
source code and generating your application `.app` file. You can learn more
|
||||
about the generation of `.app` files by typing `mix help compile.app`.
|
||||
An application is a component implementing some specific functionality, with a
|
||||
standardized directory structure, configuration, and lifecycle. Applications
|
||||
are *loaded*, *started*, and *stopped*.
|
||||
|
||||
Once your application is compiled, running your system is a matter of starting
|
||||
your current application and its dependencies. Differently from other languages,
|
||||
Elixir does not have a `main` procedure that is responsible for starting your
|
||||
system. Instead, you start one or more applications, each with their own
|
||||
initialization and termination logic.
|
||||
## The application resource file
|
||||
|
||||
Applications also provide an "application environment", which provides one
|
||||
mechanism for configuring long running applications. We will learn more about
|
||||
the tooling, start and shutdown and the application environment in the next
|
||||
sections.
|
||||
Applications are specified in their [*resource
|
||||
file*](http://erlang.org/doc/man/app.html), which is a file called `APP.app`,
|
||||
where `APP` is the application name. For example, the application resource
|
||||
file of the OTP application `ex_unit` is called `ex_unit.app`.
|
||||
|
||||
## Start and shutdown
|
||||
You'll find the resource file of an application in its `ebin` directory, it is
|
||||
generated automatically by Mix. Some of its keys are taken from the keyword
|
||||
lists returned by the `project/0` and `application/0` functions defined in
|
||||
`mix.exs`, and others are generated by Mix itself.
|
||||
|
||||
Starting an application is done via the "application module callback", which
|
||||
is a module that defines the `start/2` function. The `start/2` function should
|
||||
then start a supervisor, which is often called as the top-level supervisor, since
|
||||
it sits at the root of a potentially long supervision tree. When the system is
|
||||
shutting down, all applications shut down their top-level supervisor, which
|
||||
terminates children in the opposite order they are started.
|
||||
You can learn more about the generation of application resource files in the
|
||||
documentation of `Mix.Tasks.Compile.App`, available as well by running `mix
|
||||
help compile.app`.
|
||||
|
||||
Shutting down a live system cleanly can be done by calling `System.stop/1`.
|
||||
It will shut down all applications in the opposite order they are started.
|
||||
Each application will then shutdown its top-level supervisor, if one is
|
||||
available, [which then shuts down its children](Supervisor.html#module-start-and-shutdown).
|
||||
## The application environment
|
||||
|
||||
From Erlang/OTP 19.1, a SIGTERM from the operating system will automatically
|
||||
translate to `System.stop/0`. Erlang/OTP 20 gives user more explicit control
|
||||
over OS signals via the `:os.set_signal/2` function.
|
||||
The key `env` of an application resource file has a list of tuples that map
|
||||
atoms to terms, and its contents are known as the application *environment*.
|
||||
Note that this environment is unrelated to the operating system environment.
|
||||
|
||||
### Application module callback
|
||||
By default, the environment of an application is an empty list. In a Mix
|
||||
project you can set that key in `application/0`:
|
||||
|
||||
An application may start and stop a supervision tree when it boots via
|
||||
the application module callback.
|
||||
def application do
|
||||
[env: [redis_host: "localhost"]]
|
||||
end
|
||||
|
||||
The first step is to pass the module callback in the application definition
|
||||
in the `mix.exs` file:
|
||||
and the generated application resource file is going to have it included.
|
||||
|
||||
The environment is available after loading the application, which is a process
|
||||
explained later:
|
||||
|
||||
Application.load(:APP_NAME)
|
||||
#=> :ok
|
||||
|
||||
Application.get_env(:APP_NAME, :redis_host)
|
||||
#=> "localhost"
|
||||
|
||||
In Mix projects, the environment of the application and its dependencies can
|
||||
be overridden via the `config/config.exs` file. If you start the application
|
||||
with Mix, that configuration is available at compile time, and at runtime too,
|
||||
but take into account it is not included in the generated application resource
|
||||
file, and it is not available if you start the application without Mix.
|
||||
|
||||
For example, someone using your application can override its `:redis_host`
|
||||
environment variable as follows:
|
||||
|
||||
config :APP_NAME, redis_host: "redis.local"
|
||||
|
||||
The function `put_env/3` allows dynamic configuration of the application
|
||||
environment, but as a rule of thumb each application is responsible for its
|
||||
own environment. Please do not use the functions in this module for directly
|
||||
accessing or modifying the environment of other applications.
|
||||
|
||||
The application environment can be overriden via the `-config` option of
|
||||
`erl`, as well as command-line flags, as we are going to see below.
|
||||
|
||||
## The application callback module
|
||||
|
||||
The `mod` key of an application resource file configures an application
|
||||
callback module and start argument:
|
||||
|
||||
def application do
|
||||
[mod: {MyApp, []}]
|
||||
end
|
||||
|
||||
Our application now requires the `MyApp` module to provide an application
|
||||
callback. This can be done by invoking `use Application` in that module and
|
||||
defining a `start/2` callback, for example:
|
||||
This key is optional, only needed for applications that start a supervision tree.
|
||||
|
||||
The `MyApp` module given to `:mod` needs to implement the `Application` behaviour.
|
||||
This can be done by putting `use Application` in that module and implementing the
|
||||
`c:start/2` callback, for example:
|
||||
|
||||
defmodule MyApp do
|
||||
use Application
|
||||
@@ -67,28 +93,113 @@ defmodule Application do
|
||||
end
|
||||
end
|
||||
|
||||
`start/2` typically returns `{:ok, pid}` or `{:ok, pid, state}` where
|
||||
`pid` identifies the supervision tree and `state` is the application state.
|
||||
`args` is the second element of the tuple given to the `:mod` option.
|
||||
The `c:start/2` callback has to spawn and link a supervisor and return `{:ok,
|
||||
pid}` or `{:ok, pid, state}`, where `pid` is the PID of the supervisor, and
|
||||
`state` is an optional application state. `args` is the second element of the
|
||||
tuple given to the `:mod` option.
|
||||
|
||||
The `type` argument passed to `start/2` is usually `:normal` unless in a
|
||||
The `type` argument passed to `c:start/2` is usually `:normal` unless in a
|
||||
distributed setup where application takeovers and failovers are configured.
|
||||
Distributed applications is beyond the scope of this documentation. For those
|
||||
interested on the topic, please access the OTP documentation:
|
||||
Distributed applications are beyond the scope of this documentation.
|
||||
|
||||
* [`:application` module](http://www.erlang.org/doc/man/application.html)
|
||||
* [Applications – OTP Design Principles](http://www.erlang.org/doc/design_principles/applications.html)
|
||||
When an application is shutting down, its `c:stop/1` callback is called after
|
||||
the supervision tree has been stopped by the runtime. This callback allows the
|
||||
application to do any final cleanup. The argument is the state returned by
|
||||
`c:start/2`, if it did, or `[]` otherwise. The return value of `c:stop/1` is
|
||||
ignored.
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
application state and can return any value. Note that shutting down the
|
||||
supervisor is automatically handled by the VM.
|
||||
By using `Application`, modules get a default implementation of `c:stop/1`
|
||||
that ignores its argument and returns `:ok`, but it can be overridden.
|
||||
|
||||
An application without a supervision tree doesn't define an application
|
||||
module callback in the application definition in `mix.exs` file. Even though
|
||||
there is no module with application callbacks such as `start/2` and
|
||||
`stop/1`, the application can be started and stopped the same way as an
|
||||
application with a supervision tree.
|
||||
Application callback modules may also implement the optional callback
|
||||
`c:prep_stop/1`. If present, `c:prep_stop/1` is invoked before the supervision
|
||||
tree is terminated. Its argument is the state returned by `c:start/2`, if it did,
|
||||
or `[]` otherwise, and its return value is passed to `c:stop/1`.
|
||||
|
||||
## The application lifecycle
|
||||
|
||||
### Loading applications
|
||||
|
||||
Applications are *loaded*, which means that the runtime finds and processes
|
||||
their resource files:
|
||||
|
||||
Application.load(:ex_unit)
|
||||
#=> :ok
|
||||
|
||||
If an application has included applications, they are also loaded. And the
|
||||
procedure recurses if they in turn have included applications. Included
|
||||
applications are unrelated to applications in Mix umbrella projects, they are
|
||||
an Erlang/OTP concept that has to do with coordinated starts.
|
||||
|
||||
When an application is loaded, the environment specified in its resource file
|
||||
is merged with any overrides from config files passed to `erl` via the
|
||||
`-config` option. It is worth highlighting that releases pass `sys.config`
|
||||
this way. The resulting environment can still be overridden again via specific
|
||||
`-Application` flags passed to `erl`.
|
||||
|
||||
Loading an application *does not* load its modules.
|
||||
|
||||
In practice, you rarely load applications by hand because that is part of the
|
||||
start process, explained next.
|
||||
|
||||
### Starting applications
|
||||
|
||||
Applications are also *started*:
|
||||
|
||||
Application.start(:ex_unit)
|
||||
#=> :ok
|
||||
|
||||
Once your application is compiled, running your system is a matter of starting
|
||||
your current application and its dependencies. Differently from other languages,
|
||||
Elixir does not have a `main` procedure that is responsible for starting your
|
||||
system. Instead, you start one or more applications, each with their own
|
||||
initialization and termination logic.
|
||||
|
||||
When an application is started, the runtime loads it if it hasn't been loaded
|
||||
yet (in the technical sense described above). Then, it checks if the
|
||||
dependencies listed in the `applications` key of the resource file are already
|
||||
started. Having at least one dependency not started is an error condition, but
|
||||
when you start an application with `mix run`, Mix takes care of starting all
|
||||
the dependencies for you, so in practice you don't need to worry about it
|
||||
unless you are starting applications manually with the API provided by this
|
||||
module.
|
||||
|
||||
If the application does not have a callback module configured, starting is
|
||||
done at this point. Otherwise, its `c:start/2` callback if invoked. The PID of
|
||||
the top-level supervisor returned by this function is stored by the runtime
|
||||
for later use, and the returned application state is saved too, if any.
|
||||
|
||||
### Stopping applications
|
||||
|
||||
Started applications are, finally, *stopped*:
|
||||
|
||||
Application.stop(:ex_unit)
|
||||
#=> :ok
|
||||
|
||||
Stopping an application without a callback module is defined, but except for
|
||||
some system tracing, it is in practice a no-op.
|
||||
|
||||
Stopping an application with a callback module has three steps:
|
||||
|
||||
1. If present, invoke the optional callback `c:prep_stop/1`.
|
||||
2. Terminate the top-level supervisor.
|
||||
3. Invoke the required callback `c:stop/1`.
|
||||
|
||||
The arguments passed to the callbacks are related to the state optionally
|
||||
returned by `c:start/2`, and are documented in the section about the callback
|
||||
module above.
|
||||
|
||||
It is important to highlight that step 2 is a blocking one. Termination of a
|
||||
supervisor triggers a recursive chain of children terminations, therefore
|
||||
orderly shutting down all descendant processes. The `c:stop/1` callback is
|
||||
invoked only after termination of the whole supervision tree.
|
||||
|
||||
Shutting down a live system cleanly can be done by calling `System.stop/1`. It
|
||||
will shut down every application in the opposite order they had been started.
|
||||
|
||||
From Erlang/OTP 19.1, a SIGTERM from the operating system will automatically
|
||||
translate to `System.stop/0`. Erlang/OTP 20 gives user more explicit control
|
||||
over OS signals via the `:os.set_signal/2` function.
|
||||
|
||||
## Tooling
|
||||
|
||||
@@ -109,41 +220,14 @@ defmodule Application do
|
||||
when tools must be shared between developers and not as deployment options.
|
||||
See `mix help archive.build` and `mix help escript.build` for more detail.
|
||||
|
||||
## Application environment
|
||||
## Further information
|
||||
|
||||
Once an application is started, OTP provides an application environment
|
||||
that can be used to configure the application.
|
||||
|
||||
Assuming you are inside a Mix project, you can edit the `application/0`
|
||||
function in the `mix.exs` file to the following:
|
||||
|
||||
def application do
|
||||
[env: [hello: :world]]
|
||||
end
|
||||
|
||||
In the application function, we can define the default environment values
|
||||
for our application. By starting your application with `iex -S mix`, you
|
||||
can access the default value:
|
||||
|
||||
Application.get_env(:APP_NAME, :hello)
|
||||
#=> :world
|
||||
|
||||
Applications and dependencies in Mix projects are typically configured
|
||||
via the `config/config.exs` file. For example, someone using your
|
||||
application can configure the `:hello` key as follows:
|
||||
|
||||
config :APP_NAME, hello: :brand_new_world
|
||||
|
||||
Keep in mind configuration files are only useful to configure static
|
||||
values. For example, if you need to configure your applications based
|
||||
on the system environment, the file system or on database entries,
|
||||
then those configurations are better placed at runtime. For example,
|
||||
one may configure applications dynamically via `put_env/3`.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the functions in this module for directly accessing or modifying
|
||||
the environment of other applications (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
For further details on applications please check the documentation of the
|
||||
[`application`](http://www.erlang.org/doc/man/application.html) Erlang module,
|
||||
and the
|
||||
[Applications](http://www.erlang.org/doc/design_principles/applications.html)
|
||||
section of the [OTP Design Principles User's
|
||||
Guide](http://erlang.org/doc/design_principles/users_guide.html).
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -186,15 +270,25 @@ defmodule Application do
|
||||
| {:error, reason :: term}
|
||||
|
||||
@doc """
|
||||
Called when an application is stopped.
|
||||
Called before stopping the application.
|
||||
|
||||
This function is called when an application has stopped, i.e., when its
|
||||
This function is called before the top-level supervisor is terminated. It
|
||||
receives the state returned by `c:start/2`, if it did, or `[]` otherwise.
|
||||
The return value is later passed to `c:stop/1`.
|
||||
"""
|
||||
@callback prep_stop(state) :: state
|
||||
|
||||
@doc """
|
||||
Called after an application has been stopped.
|
||||
|
||||
This function is called after an application has been stopped, i.e., after its
|
||||
supervision tree has been stopped. It should do the opposite of what the
|
||||
`start/2` callback did, and should perform any necessary cleanup. The return
|
||||
`c:start/2` callback did, and should perform any necessary cleanup. The return
|
||||
value of this callback is ignored.
|
||||
|
||||
`state` is the return value of the `start/2` callback or the return value of
|
||||
the `prep_stop/1` function if the application module defines such a function.
|
||||
`state` is the state returned by `c:start/2`, if it did, or `[]` otherwise.
|
||||
If the optional callback `c:prep_stop/1` is present, `state` is its return
|
||||
value instead.
|
||||
|
||||
`use Application` defines a default implementation of this function which does
|
||||
nothing and just returns `:ok`.
|
||||
@@ -212,7 +306,7 @@ defmodule Application do
|
||||
@callback start_phase(phase :: term, start_type, phase_args :: term) ::
|
||||
:ok | {:error, reason :: term}
|
||||
|
||||
@optional_callbacks start_phase: 3
|
||||
@optional_callbacks start_phase: 3, prep_stop: 1
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -232,7 +326,8 @@ defmodule Application do
|
||||
@type key :: atom
|
||||
@type value :: term
|
||||
@type state :: term
|
||||
@type start_type :: :permanent | :transient | :temporary
|
||||
@type start_type :: :normal | {:takeover, node} | {:failover, node}
|
||||
@type restart_type :: :permanent | :transient | :temporary
|
||||
|
||||
@application_keys [
|
||||
:description,
|
||||
@@ -309,6 +404,41 @@ defmodule Application do
|
||||
|
||||
If the configuration parameter does not exist, the function returns the
|
||||
`default` value.
|
||||
|
||||
## Examples
|
||||
|
||||
`get_env/3` is commonly used to read the configuration of your OTP applications.
|
||||
Since Mix configurations are commonly used to configure applications, we will use
|
||||
this as a point of illustration.
|
||||
|
||||
Consider a new application `:my_app`. `:my_app` contains a database engine which
|
||||
supports a pool of databases. The database engine needs to know the configuration for
|
||||
each of those databases, and that configuration is supplied by key-value pairs in
|
||||
environment of `:my_app`.
|
||||
|
||||
config :my_app, Databases.RepoOne,
|
||||
# A database configuration
|
||||
ip: "localhost"
|
||||
port: 5433
|
||||
|
||||
config :my_app, Databases.RepoTwo,
|
||||
# Another database configuration (for the same OTP app)
|
||||
ip: "localhost"
|
||||
port: 20717
|
||||
|
||||
config :my_app, my_app_databases: [Databases.RepoOne, Databases.RepoTwo]
|
||||
|
||||
Our database engine used by `:my_app` needs to know what databases exist, and
|
||||
what the database configurations are. The database engine can make a call to
|
||||
`get_env(:my_app, :my_app_databases)` to retrieve the list of databases (specified
|
||||
by module names). Our database engine can then traverse each repository in the
|
||||
list and then call `get_env(:my_app, Databases.RepoOne)` and so forth to retrieve
|
||||
the configuration of each one.
|
||||
|
||||
**Important:** if you are writing a library to be used by other developers,
|
||||
it is generally recommended to avoid the application environment, as the
|
||||
application environment is effectively a global storage. For more information,
|
||||
read our [library guidelines](/library-guidelines.html).
|
||||
"""
|
||||
@spec get_env(app, key, value) :: value
|
||||
def get_env(app, key, default \\ nil) do
|
||||
@@ -402,7 +532,7 @@ defmodule Application do
|
||||
:ok = Application.ensure_started(:my_test_dep)
|
||||
|
||||
"""
|
||||
@spec ensure_started(app, start_type) :: :ok | {:error, term}
|
||||
@spec ensure_started(app, restart_type) :: :ok | {:error, term}
|
||||
def ensure_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_started(app, type)
|
||||
end
|
||||
@@ -414,7 +544,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, {app, term}}
|
||||
@spec ensure_all_started(app, restart_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
|
||||
@@ -453,7 +583,7 @@ defmodule Application do
|
||||
Note also that the `:transient` type is of little practical use, since when a
|
||||
supervision tree terminates, the reason is set to `:shutdown`, not `:normal`.
|
||||
"""
|
||||
@spec start(app, start_type) :: :ok | {:error, term}
|
||||
@spec start(app, restart_type) :: :ok | {:error, term}
|
||||
def start(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.start(app, type)
|
||||
end
|
||||
@@ -527,8 +657,26 @@ defmodule Application do
|
||||
|
||||
@doc """
|
||||
Returns the given path inside `app_dir/1`.
|
||||
|
||||
If `path` is a string, then it will be used as the path inside `app_dir/1`. If
|
||||
`path` is a list of strings, it will be joined (see `Path.join/1`) and the result
|
||||
will be used as the path inside `app_dir/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.mkdir_p!("foo/ebin")
|
||||
Code.prepend_path("foo/ebin")
|
||||
|
||||
Application.app_dir(:foo, "my_path")
|
||||
#=> "foo/my_path"
|
||||
|
||||
Application.app_dir(:foo, ["my", "nested", "path"])
|
||||
#=> "foo/my/nested/path"
|
||||
|
||||
"""
|
||||
@spec app_dir(app, String.t() | [String.t()]) :: String.t()
|
||||
def app_dir(app, path)
|
||||
|
||||
def app_dir(app, path) when is_binary(path) do
|
||||
Path.join(app_dir(app), path)
|
||||
end
|
||||
@@ -540,7 +688,7 @@ defmodule Application do
|
||||
@doc """
|
||||
Returns a list with information about the applications which are currently running.
|
||||
"""
|
||||
@spec started_applications(timeout) :: [tuple]
|
||||
@spec started_applications(timeout) :: [{app, description :: charlist(), vsn :: charlist()}]
|
||||
def started_applications(timeout \\ 5000) do
|
||||
:application.which_applications(timeout)
|
||||
end
|
||||
@@ -548,7 +696,7 @@ defmodule Application do
|
||||
@doc """
|
||||
Returns a list with information about the applications which have been loaded.
|
||||
"""
|
||||
@spec loaded_applications :: [tuple]
|
||||
@spec loaded_applications :: [{app, description :: charlist(), vsn :: charlist()}]
|
||||
def loaded_applications do
|
||||
:application.loaded_applications()
|
||||
end
|
||||
|
||||
@@ -38,8 +38,8 @@ defmodule Atom do
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@doc false
|
||||
@deprecated "Use Atom.to_charlist/1 instead"
|
||||
@spec to_char_list(atom) :: charlist
|
||||
def to_char_list(atom), do: Atom.to_charlist(atom)
|
||||
end
|
||||
|
||||
+13
-10
@@ -184,16 +184,19 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp decode_char_clauses(alphabet, :mixed) when length(alphabet) == 32 do
|
||||
alphabet
|
||||
|> Stream.with_index()
|
||||
|> Enum.flat_map(fn {encoding, value} = pair ->
|
||||
if encoding in ?A..?Z do
|
||||
[pair, {encoding - ?A + ?a, value}]
|
||||
else
|
||||
[pair]
|
||||
end
|
||||
end)
|
||||
|> decode_clauses()
|
||||
clauses =
|
||||
alphabet
|
||||
|> Stream.with_index()
|
||||
|> Enum.flat_map(fn {encoding, value} = pair ->
|
||||
if encoding in ?A..?Z do
|
||||
[pair, {encoding - ?A + ?a, value}]
|
||||
else
|
||||
[pair]
|
||||
end
|
||||
end)
|
||||
|> decode_clauses()
|
||||
|
||||
clauses ++ bad_digit_clause()
|
||||
end
|
||||
|
||||
defp decode_mixed_clauses(first, second) do
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
Mechanism for handling behaviours.
|
||||
|
||||
Instead of `defcallback/1` and `defmacrocallback/1`, the `@callback` and
|
||||
`@macrocallback` module attributes can be used (respectively). See the
|
||||
documentation for `Module` for more information on these attributes.
|
||||
This module is deprecated. Instead of `defcallback/1` and
|
||||
`defmacrocallback/1`, the `@callback` and `@macrocallback`
|
||||
module attributes can be used (respectively). See the
|
||||
documentation for `Module` for more information on these
|
||||
attributes.
|
||||
|
||||
Instead of `MyModule.__behaviour__(:callbacks)`,
|
||||
`MyModule.behaviour_info(:callbacks)` can be used.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use @callback and @macrocallback attributes instead"
|
||||
|
||||
@doc """
|
||||
Defines a function callback according to the given type specification.
|
||||
"""
|
||||
@@ -97,14 +101,20 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
def __behaviour__(:docs) do
|
||||
for {tuple, line, kind, docs} <- Code.get_docs(__MODULE__, :callback_docs) do
|
||||
{:docs_v1, _, :elixir, _, _, _, docs} = Code.fetch_docs(__MODULE__)
|
||||
|
||||
for {{kind, name, arity}, line, _, doc, _} <- docs, kind in [:callback, :macrocallback] do
|
||||
case kind do
|
||||
:callback -> {tuple, line, :def, docs}
|
||||
:macrocallback -> {tuple, line, :defmacro, docs}
|
||||
:callback -> {{name, arity}, line, :def, __behaviour__doc_value(doc)}
|
||||
:macrocallback -> {{name, arity}, line, :defmacro, __behaviour__doc_value(doc)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp __behaviour__doc_value(:none), do: nil
|
||||
defp __behaviour__doc_value(:hidden), do: false
|
||||
defp __behaviour__doc_value(%{"en" => doc}), do: doc
|
||||
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@ defmodule Bitwise do
|
||||
operators. For example:
|
||||
|
||||
iex> use Bitwise
|
||||
iex> bnot 1 # named
|
||||
iex> bnot(1) # named
|
||||
-2
|
||||
iex> 1 &&& 1 # operator
|
||||
iex> 1 &&& 1 # operator
|
||||
1
|
||||
|
||||
If you prefer to use only operators or skip them, you can
|
||||
@@ -29,7 +29,10 @@ defmodule Bitwise do
|
||||
All bitwise macros can be used in guards:
|
||||
|
||||
iex> use Bitwise
|
||||
iex> odd? = fn int when band(int, 1) == 1 -> true; _ -> false end
|
||||
iex> odd? = fn
|
||||
...> int when band(int, 1) == 1 -> true
|
||||
...> _ -> false
|
||||
...> end
|
||||
iex> odd?.(1)
|
||||
true
|
||||
|
||||
|
||||
@@ -118,7 +118,12 @@ defmodule Calendar do
|
||||
@callback days_in_month(year, month) :: day
|
||||
|
||||
@doc """
|
||||
Returns true if the given year is a leap year.
|
||||
Returns how many months there are in the given year.
|
||||
"""
|
||||
@callback months_in_year(year) :: month
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given year is a leap year.
|
||||
|
||||
A leap year is a year of a longer length than normal. The exact meaning
|
||||
is up to the calendar. A calendar must return `false` if it does not support
|
||||
@@ -165,24 +170,24 @@ defmodule Calendar do
|
||||
@callback time_to_string(hour, minute, second, microsecond) :: String.t()
|
||||
|
||||
@doc """
|
||||
Converts the given datetime (with time zone) into the `t:iso_days` format.
|
||||
Converts the given datetime (with time zone) into the `t:iso_days/0` format.
|
||||
"""
|
||||
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) ::
|
||||
iso_days
|
||||
|
||||
@doc """
|
||||
Converts `t:iso_days` to the Calendar's datetime format.
|
||||
Converts `t:iso_days/0` to the Calendar's datetime format.
|
||||
"""
|
||||
@callback naive_datetime_from_iso_days(iso_days) ::
|
||||
{year, month, day, hour, minute, second, microsecond}
|
||||
|
||||
@doc """
|
||||
Converts the given time to the `t:day_fraction` format.
|
||||
Converts the given time to the `t:day_fraction/0` format.
|
||||
"""
|
||||
@callback time_to_day_fraction(hour, minute, second, microsecond) :: day_fraction
|
||||
|
||||
@doc """
|
||||
Converts `t:day_fraction` to the Calendar's time format.
|
||||
Converts `t:day_fraction/0` to the Calendar's time format.
|
||||
"""
|
||||
@callback time_from_day_fraction(day_fraction) :: {hour, minute, second, microsecond}
|
||||
|
||||
@@ -229,6 +234,7 @@ defmodule Calendar do
|
||||
between them. If they are compatible, this means that we can also convert
|
||||
dates as well as naive datetimes between them.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec compatible_calendars?(Calendar.calendar(), Calendar.calendar()) :: boolean
|
||||
def compatible_calendars?(calendar, calendar), do: true
|
||||
|
||||
@@ -241,6 +247,7 @@ defmodule Calendar do
|
||||
Returns a microsecond tuple truncated to a given precision (`:microsecond`,
|
||||
`:millisecond` or `:second`).
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec truncate(Calendar.microsecond(), :microsecond | :millisecond | :second) ::
|
||||
Calendar.microsecond()
|
||||
def truncate(microsecond_tuple, :microsecond), do: microsecond_tuple
|
||||
|
||||
@@ -3,8 +3,8 @@ defmodule Date do
|
||||
A Date struct and functions.
|
||||
|
||||
The Date struct contains the fields year, month, day and calendar.
|
||||
New dates can be built with the `new/3` function or using the `~D`
|
||||
sigil:
|
||||
New dates can be built with the `new/3` function or using the
|
||||
[`~D`](`Kernel.sigil_D/2`) sigil:
|
||||
|
||||
iex> ~D[2000-01-01]
|
||||
~D[2000-01-01]
|
||||
@@ -29,7 +29,7 @@ defmodule Date do
|
||||
|
||||
## Comparing dates
|
||||
|
||||
Comparisons in Elixir using `==`, `>`, `<` and similar are structural
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `Date` struct fields. For proper comparison between
|
||||
dates, use the `compare/2` function.
|
||||
|
||||
@@ -85,8 +85,9 @@ defmodule Date do
|
||||
true
|
||||
iex> Enum.reduce(range, 0, fn _date, acc -> acc - 1 end)
|
||||
-366
|
||||
"""
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec range(Date.t(), Date.t()) :: Date.Range.t()
|
||||
def range(%Date{calendar: calendar} = first, %Date{calendar: calendar} = last) do
|
||||
{first_days, _} = to_iso_days(first)
|
||||
@@ -114,6 +115,7 @@ defmodule Date do
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec utc_today(Calendar.calendar()) :: t
|
||||
def utc_today(calendar \\ Calendar.ISO)
|
||||
|
||||
@@ -129,7 +131,7 @@ defmodule Date do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the year in the given `date` is a leap year.
|
||||
Returns `true` if the year in the given `date` is a leap year.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -145,6 +147,7 @@ defmodule Date do
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec leap_year?(Calendar.date()) :: boolean()
|
||||
def leap_year?(date)
|
||||
|
||||
@@ -165,6 +168,7 @@ defmodule Date do
|
||||
29
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec days_in_month(Calendar.date()) :: Calendar.day()
|
||||
def days_in_month(date)
|
||||
|
||||
@@ -172,6 +176,23 @@ defmodule Date do
|
||||
calendar.days_in_month(year, month)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of months in the given `date` year.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Date.months_in_year(~D[1900-01-13])
|
||||
12
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec months_in_year(Calendar.date()) :: Calendar.month()
|
||||
def months_in_year(date)
|
||||
|
||||
def months_in_year(%{calendar: calendar, year: year}) do
|
||||
calendar.months_in_year(year)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Builds a new ISO date.
|
||||
|
||||
@@ -212,6 +233,8 @@ defmodule Date do
|
||||
"2000-02-28"
|
||||
iex> Date.to_string(~N[2000-02-28 01:23:45])
|
||||
"2000-02-28"
|
||||
iex> Date.to_string(~D[-0100-12-15])
|
||||
"-0100-12-15"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.date()) :: String.t()
|
||||
@@ -240,18 +263,29 @@ defmodule Date do
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes>>, calendar) do
|
||||
with {year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day) do
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO), do: convert(date, calendar)
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
with {:ok, %{year: year} = date} <- raw_from_iso8601(rest, calendar) do
|
||||
{:ok, %{date | year: -year}}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<_::binary>>, _calendar) do
|
||||
{:error, :invalid_format}
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with unquote(match_date) <- string,
|
||||
true <- unquote(guard_date) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO) do
|
||||
convert(date, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -266,6 +300,7 @@ defmodule Date do
|
||||
~D[2015-01-23]
|
||||
iex> Date.from_iso8601!("2015:01:23")
|
||||
** (ArgumentError) cannot parse "2015:01:23" as date, reason: :invalid_format
|
||||
|
||||
"""
|
||||
@spec from_iso8601!(String.t(), Calendar.calendar()) :: t
|
||||
def from_iso8601!(string, calendar \\ Calendar.ISO) do
|
||||
@@ -302,11 +337,19 @@ defmodule Date do
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.date(), :extended | :basic) :: String.t()
|
||||
def to_iso8601(date, format \\ :extended) when format in [:basic, :extended] do
|
||||
%{year: year, month: month, day: day} = convert!(date, Calendar.ISO)
|
||||
def to_iso8601(date, format \\ :extended)
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = date, format) when format in [:basic, :extended] do
|
||||
%{year: year, month: month, day: day} = date
|
||||
Calendar.ISO.date_to_iso8601(year, month, day, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = date, format) when format in [:basic, :extended] do
|
||||
date
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given `date` to an Erlang date tuple.
|
||||
|
||||
@@ -397,6 +440,7 @@ defmodule Date do
|
||||
:eq
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec compare(Calendar.date(), Calendar.date()) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar} = date1, %{calendar: calendar} = date2) do
|
||||
%{year: year1, month: month1, day: day1} = date1
|
||||
@@ -444,6 +488,7 @@ defmodule Date do
|
||||
{:ok, %Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert(Calendar.date(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
def convert(%{calendar: calendar, year: year, month: month, day: day}, calendar) do
|
||||
@@ -477,6 +522,7 @@ defmodule Date do
|
||||
%Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert!(Calendar.date(), Calendar.calendar()) :: t
|
||||
def convert!(date, calendar) do
|
||||
case convert(date, calendar) do
|
||||
@@ -502,11 +548,13 @@ defmodule Date do
|
||||
~D[2000-01-01]
|
||||
iex> Date.add(~D[2000-01-01], 2)
|
||||
~D[2000-01-03]
|
||||
|
||||
iex> Date.add(~N[2000-01-01 09:00:00], 2)
|
||||
~D[2000-01-03]
|
||||
iex> Date.add(~D[-0010-01-01], -2)
|
||||
~D[-0011-12-30]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec add(Calendar.date(), integer()) :: t
|
||||
def add(%{calendar: calendar} = date, days) do
|
||||
{iso_days, fraction} = to_iso_days(date)
|
||||
@@ -526,11 +574,13 @@ defmodule Date do
|
||||
2
|
||||
iex> Date.diff(~D[2000-01-01], ~D[2000-01-03])
|
||||
-2
|
||||
|
||||
iex> Date.diff(~D[0000-01-02], ~D[-0001-12-30])
|
||||
3
|
||||
iex> Date.diff(~D[2000-01-01], ~N[2000-01-03 09:00:00])
|
||||
-2
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec diff(Calendar.date(), Calendar.date()) :: integer
|
||||
def diff(%{calendar: Calendar.ISO} = date1, %{calendar: Calendar.ISO} = date2) do
|
||||
%{year: year1, month: month1, day: day1} = date1
|
||||
@@ -584,8 +634,11 @@ defmodule Date do
|
||||
2
|
||||
iex> Date.day_of_week(~N[2016-11-01 01:23:45])
|
||||
2
|
||||
iex> Date.day_of_week(~D[-0015-10-30])
|
||||
3
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec day_of_week(Calendar.date()) :: non_neg_integer()
|
||||
def day_of_week(date)
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ defmodule Date.Range do
|
||||
@type t :: %__MODULE__{
|
||||
first: Date.t(),
|
||||
last: Date.t(),
|
||||
first_in_iso_days: Calendar.iso_days(),
|
||||
last_in_iso_days: Calendar.iso_days()
|
||||
first_in_iso_days: iso_days(),
|
||||
last_in_iso_days: iso_days()
|
||||
}
|
||||
|
||||
@typep iso_days() :: Calendar.iso_days()
|
||||
|
||||
defstruct [:first, :last, :first_in_iso_days, :last_in_iso_days]
|
||||
|
||||
defimpl Enumerable do
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule DateTime do
|
||||
well as the zone abbreviation field used exclusively
|
||||
for formatting purposes.
|
||||
|
||||
Remember, comparisons in Elixir using `==`, `>`, `<` and friends
|
||||
Remember, comparisons in Elixir using `==/2`, `>/2`, `</2` and friends
|
||||
are structural and based on the DateTime struct fields. For proper
|
||||
comparison between datetimes, use the `compare/2` function.
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule DateTime do
|
||||
Such functions expect `t:Calendar.datetime/0` in their typespecs
|
||||
(instead of `t:t/0`).
|
||||
|
||||
Developers should avoid creating the DateTime struct directly
|
||||
Developers should avoid creating the `DateTime` struct directly
|
||||
and instead rely on the functions provided by this module as
|
||||
well as the ones in 3rd party calendar libraries.
|
||||
|
||||
@@ -25,12 +25,12 @@ defmodule DateTime do
|
||||
|
||||
You will notice this module only contains conversion
|
||||
functions as well as functions that work on UTC. This
|
||||
is because a proper DateTime implementation requires a
|
||||
TimeZone database which currently is not provided as part
|
||||
is because a proper `DateTime` implementation requires a
|
||||
time zone database which currently is not provided as part
|
||||
of Elixir.
|
||||
|
||||
Such may be addressed in upcoming versions, meanwhile,
|
||||
use 3rd party packages to provide DateTime building and
|
||||
use 3rd party packages to provide `DateTime` building and
|
||||
similar functionality with time zone backing.
|
||||
"""
|
||||
|
||||
@@ -96,17 +96,17 @@ defmodule DateTime do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1464096368)
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1_464_096_368)
|
||||
iex> datetime
|
||||
#DateTime<2016-05-24 13:26:08Z>
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1432560368868569, :microsecond)
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1_432_560_368_868_569, :microsecond)
|
||||
iex> datetime
|
||||
#DateTime<2015-05-25 13:26:08.868569Z>
|
||||
|
||||
The unit can also be an integer as in `t:System.time_unit/0`:
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_unix(143256036886856, 1024)
|
||||
iex> {:ok, datetime} = DateTime.from_unix(143_256_036_886_856, 1024)
|
||||
iex> datetime
|
||||
#DateTime<6403-03-17 07:05:22.320Z>
|
||||
|
||||
@@ -155,10 +155,10 @@ defmodule DateTime do
|
||||
iex> DateTime.from_unix!(0)
|
||||
#DateTime<1970-01-01 00:00:00Z>
|
||||
|
||||
iex> DateTime.from_unix!(1464096368)
|
||||
iex> DateTime.from_unix!(1_464_096_368)
|
||||
#DateTime<2016-05-24 13:26:08Z>
|
||||
|
||||
iex> DateTime.from_unix!(1432560368868569, :microsecond)
|
||||
iex> DateTime.from_unix!(1_432_560_368_868_569, :microsecond)
|
||||
#DateTime<2015-05-25 13:26:08.868569Z>
|
||||
|
||||
"""
|
||||
@@ -186,6 +186,7 @@ defmodule DateTime do
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec from_naive(NaiveDateTime.t(), Calendar.time_zone()) :: {:ok, t}
|
||||
def from_naive(naive_datetime, time_zone)
|
||||
|
||||
@@ -231,6 +232,7 @@ defmodule DateTime do
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec from_naive!(NaiveDateTime.t(), Calendar.time_zone()) :: t
|
||||
def from_naive!(naive_datetime, time_zone) do
|
||||
case from_naive(naive_datetime, time_zone) do
|
||||
@@ -254,7 +256,7 @@ defmodule DateTime do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> 1464096368 |> DateTime.from_unix!() |> DateTime.to_unix()
|
||||
iex> 1_464_096_368 |> DateTime.from_unix!() |> DateTime.to_unix()
|
||||
1464096368
|
||||
|
||||
iex> dt = %DateTime{calendar: Calendar.ISO, day: 20, hour: 18, microsecond: {273806, 6},
|
||||
@@ -414,12 +416,8 @@ defmodule DateTime do
|
||||
@spec to_iso8601(Calendar.datetime(), :extended | :basic) :: String.t()
|
||||
def to_iso8601(datetime, format \\ :extended)
|
||||
|
||||
def to_iso8601(_, format) when format not in [:extended, :basic] do
|
||||
raise ArgumentError,
|
||||
"DateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect(format)}"
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format) do
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format)
|
||||
when format in [:extended, :basic] do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
@@ -450,7 +448,7 @@ defmodule DateTime do
|
||||
)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = datetime, format) do
|
||||
def to_iso8601(%{calendar: _} = datetime, format) when format in [:extended, :basic] do
|
||||
datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
@@ -487,6 +485,14 @@ defmodule DateTime do
|
||||
iex> datetime
|
||||
#DateTime<2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> {:ok, datetime, 0} = DateTime.from_iso8601("-2015-01-23T23:50:07Z")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 23:50:07Z>
|
||||
|
||||
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("-2015-01-23T23:50:07,123+02:30")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
|
||||
@@ -504,50 +510,79 @@ defmodule DateTime do
|
||||
{:error, :invalid_format}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) ::
|
||||
{:ok, t, Calendar.utc_offset()} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
|
||||
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
|
||||
true <- sep in [?\s, ?T],
|
||||
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
|
||||
{year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
{minute, ""} <- Integer.parse(min),
|
||||
{second, ""} <- Integer.parse(sec),
|
||||
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{:ok, date} <- Date.new(year, month, day),
|
||||
{:ok, time} <- Time.new(hour, minute, second, microsecond),
|
||||
{:ok, offset} <- parse_offset(rest) do
|
||||
%{year: year, month: month, day: day} = date
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
|
||||
{_, precision} = microsecond
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
datetime =
|
||||
Calendar.ISO.naive_datetime_to_iso_days(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond
|
||||
)
|
||||
|> apply_tz_offset(offset)
|
||||
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
|
||||
|
||||
{:ok, %{datetime | microsecond: microsecond}, offset}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, true)
|
||||
end
|
||||
|
||||
defp parse_offset(rest) do
|
||||
case Calendar.ISO.parse_offset(rest) do
|
||||
{offset, ""} when is_integer(offset) -> {:ok, offset}
|
||||
{nil, ""} -> {:error, :missing_offset}
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, false)
|
||||
end
|
||||
|
||||
@sep [?\s, ?T]
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar, is_negative_datetime) do
|
||||
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
|
||||
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, minute, second} = unquote(read_time)
|
||||
year = if is_negative_datetime, do: -year, else: year
|
||||
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
{:error, :invalid_date}
|
||||
|
||||
not calendar.valid_time?(hour, minute, second, microsecond) ->
|
||||
{:error, :invalid_time}
|
||||
|
||||
offset == 0 ->
|
||||
datetime = %DateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
std_offset: 0,
|
||||
utc_offset: 0,
|
||||
zone_abbr: "UTC",
|
||||
time_zone: "Etc/UTC"
|
||||
}
|
||||
|
||||
{:ok, datetime, 0}
|
||||
|
||||
is_nil(offset) ->
|
||||
{:error, :missing_offset}
|
||||
|
||||
true ->
|
||||
{_, precision} = microsecond
|
||||
|
||||
datetime =
|
||||
Calendar.ISO.naive_datetime_to_iso_days(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond
|
||||
)
|
||||
|> apply_tz_offset(offset)
|
||||
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
|
||||
|
||||
{:ok, %{datetime | microsecond: microsecond}, offset}
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
@@ -575,6 +610,12 @@ defmodule DateTime do
|
||||
iex> DateTime.to_string(dt)
|
||||
"2000-02-29 23:00:07-04:00 AMT America/Manaus"
|
||||
|
||||
iex> dt = %DateTime{year: -100, month: 12, day: 19, zone_abbr: "CET",
|
||||
...> hour: 3, minute: 20, second: 31, microsecond: {0, 0},
|
||||
...> utc_offset: 3600, std_offset: 0, time_zone: "Europe/Stockholm"}
|
||||
iex> DateTime.to_string(dt)
|
||||
"-0100-12-19 03:20:31+01:00 CET Europe/Stockholm"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.datetime()) :: String.t()
|
||||
def to_string(%{calendar: calendar} = datetime) do
|
||||
@@ -629,6 +670,7 @@ defmodule DateTime do
|
||||
:gt
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec compare(Calendar.datetime(), Calendar.datetime()) :: :lt | :eq | :gt
|
||||
def compare(
|
||||
%{calendar: _, utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
@@ -660,6 +702,8 @@ defmodule DateTime do
|
||||
|
||||
The answer can be returned in any `unit` available from `t:System.time_unit/0`.
|
||||
|
||||
Leap seconds are not taken into account.
|
||||
|
||||
This function returns the difference in seconds where seconds are measured
|
||||
according to `Calendar.ISO`.
|
||||
|
||||
@@ -677,6 +721,7 @@ defmodule DateTime do
|
||||
-18000
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec diff(Calendar.datetime(), Calendar.datetime()) :: integer()
|
||||
def diff(
|
||||
%{utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
@@ -716,6 +761,7 @@ defmodule DateTime do
|
||||
#DateTime<2017-11-07 11:45:18+01:00 CET Europe/Paris>
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%DateTime{microsecond: microsecond} = datetime, precision) do
|
||||
%{datetime | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
@@ -744,6 +790,7 @@ defmodule DateTime do
|
||||
zone_abbr: "AMT"}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert(Calendar.datetime(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
|
||||
@@ -818,6 +865,7 @@ defmodule DateTime do
|
||||
zone_abbr: "AMT"}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert!(Calendar.datetime(), Calendar.calendar()) :: t | no_return
|
||||
def convert!(datetime, calendar) do
|
||||
case convert(datetime, calendar) do
|
||||
|
||||
+231
-34
@@ -16,11 +16,11 @@ defmodule Calendar.ISO do
|
||||
@behaviour Calendar
|
||||
|
||||
@unix_epoch 62_167_219_200
|
||||
@unix_start 1_000_000 * -@unix_epoch
|
||||
@unix_end 315_569_519_999_999_999 - @unix_epoch * 1_000_000
|
||||
@unix_range_microseconds @unix_start..@unix_end
|
||||
unix_start = (315_537_897_600 + @unix_epoch) * -1_000_000
|
||||
unix_end = 315_569_519_999_999_999 - @unix_epoch * 1_000_000
|
||||
@unix_range_microseconds unix_start..unix_end
|
||||
|
||||
@type year :: 0..9999
|
||||
@type year :: -9999..9999
|
||||
@type month :: 1..12
|
||||
@type day :: 1..31
|
||||
|
||||
@@ -34,8 +34,43 @@ defmodule Calendar.ISO do
|
||||
@days_per_nonleap_year 365
|
||||
@days_per_leap_year 366
|
||||
|
||||
@months_in_year 12
|
||||
|
||||
@doc false
|
||||
def __match_date__ do
|
||||
quote do
|
||||
[
|
||||
<<y1, y2, y3, y4, ?-, m1, m2, ?-, d1, d2>>,
|
||||
y1 >= ?0 and y1 <= ?9 and y2 >= ?0 and y2 <= ?9 and y3 >= ?0 and y3 <= ?9 and y4 >= ?0 and
|
||||
y4 <= ?9 and m1 >= ?0 and m1 <= ?9 and m2 >= ?0 and m2 <= ?9 and d1 >= ?0 and d1 <= ?9 and
|
||||
d2 >= ?0 and d2 <= ?9,
|
||||
{
|
||||
(y1 - ?0) * 1000 + (y2 - ?0) * 100 + (y3 - ?0) * 10 + (y4 - ?0),
|
||||
(m1 - ?0) * 10 + (m2 - ?0),
|
||||
(d1 - ?0) * 10 + (d2 - ?0)
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __match_time__ do
|
||||
quote do
|
||||
[
|
||||
<<h1, h2, ?:, i1, i2, ?:, s1, s2>>,
|
||||
h1 >= ?0 and h1 <= ?9 and h2 >= ?0 and h2 <= ?9 and i1 >= ?0 and i1 <= ?9 and i2 >= ?0 and
|
||||
i2 <= ?9 and s1 >= ?0 and s1 <= ?9 and s2 >= ?0 and s2 <= ?9,
|
||||
{
|
||||
(h1 - ?0) * 10 + (h2 - ?0),
|
||||
(i1 - ?0) * 10 + (i2 - ?0),
|
||||
(s1 - ?0) * 10 + (s2 - ?0)
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the `t:Calendar.iso_days` format of the specified date.
|
||||
Returns the `t:Calendar.iso_days/0` format of the specified date.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -45,8 +80,11 @@ defmodule Calendar.ISO do
|
||||
{730485, {43200000000, 86400000000}}
|
||||
iex> Calendar.ISO.naive_datetime_to_iso_days(2000, 1, 1, 13, 0, 0, {0, 6})
|
||||
{730485, {46800000000, 86400000000}}
|
||||
iex> Calendar.ISO.naive_datetime_to_iso_days(-1, 1, 1, 0, 0, 0, {0, 6})
|
||||
{-365, {0, 86400000000}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec naive_datetime_to_iso_days(
|
||||
Calendar.year(),
|
||||
@@ -62,28 +100,30 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the `t:Calendar.iso_days` format to the datetime format specified by this calendar.
|
||||
Converts the `t:Calendar.iso_days/0` format to the datetime format specified by this calendar.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({0, {0, 86400}})
|
||||
{0, 1, 1, 0, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730485, {0, 86400}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {0, 86400}})
|
||||
{2000, 1, 1, 0, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730485, {43200, 86400}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {43200, 86400}})
|
||||
{2000, 1, 1, 12, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({-365, {0, 86400000000}})
|
||||
{-1, 1, 1, 0, 0, 0, {0, 6}}
|
||||
|
||||
"""
|
||||
@spec naive_datetime_from_iso_days(Calendar.iso_days()) ::
|
||||
{
|
||||
Calendar.year(),
|
||||
Calendar.month(),
|
||||
Calendar.day(),
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
}
|
||||
@doc since: "1.5.0"
|
||||
@spec naive_datetime_from_iso_days(Calendar.iso_days()) :: {
|
||||
Calendar.year(),
|
||||
Calendar.month(),
|
||||
Calendar.day(),
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
}
|
||||
@impl true
|
||||
def naive_datetime_from_iso_days({days, day_fraction}) do
|
||||
{year, month, day} = date_from_iso_days(days)
|
||||
@@ -102,6 +142,7 @@ defmodule Calendar.ISO do
|
||||
{45296000123, 86400000000}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec time_to_day_fraction(
|
||||
Calendar.hour(),
|
||||
@@ -123,12 +164,13 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.time_from_day_fraction({1,2})
|
||||
iex> Calendar.ISO.time_from_day_fraction({1, 2})
|
||||
{12, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.time_from_day_fraction({13,24})
|
||||
iex> Calendar.ISO.time_from_day_fraction({13, 24})
|
||||
{13, 0, 0, {0, 6}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec time_from_day_fraction(Calendar.day_fraction()) ::
|
||||
{Calendar.hour(), Calendar.minute(), Calendar.second(), Calendar.microsecond()}
|
||||
@@ -147,6 +189,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
# Converts year, month, day to count of days since 0000-01-01.
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def date_to_iso_days(0, 1, 1) do
|
||||
0
|
||||
end
|
||||
@@ -155,7 +198,7 @@ defmodule Calendar.ISO do
|
||||
719_528
|
||||
end
|
||||
|
||||
def date_to_iso_days(year, month, day) when year in 0..9999 do
|
||||
def date_to_iso_days(year, month, day) when year in -9999..9999 do
|
||||
true = day <= days_in_month(year, month)
|
||||
|
||||
days_in_previous_years(year) + days_before_month(month) + leap_day_offset(year, month) + day -
|
||||
@@ -164,6 +207,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def date_from_iso_days(days) when days in 0..3_652_424 do
|
||||
{year, day_of_year} = days_to_year(days)
|
||||
extra_day = if leap_year?(year), do: 1, else: 0
|
||||
@@ -171,10 +215,24 @@ defmodule Calendar.ISO do
|
||||
{year, month, day_in_month + 1}
|
||||
end
|
||||
|
||||
def date_from_iso_days(days) when days in -3_652_059..-1 do
|
||||
{year, day_of_year} = days_to_year(-days)
|
||||
previous_extra_day = if leap_year?(year), do: 1, else: 0
|
||||
extra_day = if leap_year?(year + 1), do: 1, else: 0
|
||||
day_of_year = @days_per_nonleap_year + extra_day - day_of_year
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{-year - 1, month, day_in_month + previous_extra_day}
|
||||
end
|
||||
|
||||
defp div_mod(int1, int2) do
|
||||
div = div(int1, int2)
|
||||
mod = int1 - div * int2
|
||||
{div, mod}
|
||||
rem = int1 - div * int2
|
||||
|
||||
if rem >= 0 do
|
||||
{div, rem}
|
||||
else
|
||||
{div - 1, rem + int2}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -194,6 +252,8 @@ defmodule Calendar.ISO do
|
||||
29
|
||||
iex> Calendar.ISO.days_in_month(2004, 4)
|
||||
30
|
||||
iex> Calendar.ISO.days_in_month(-1, 5)
|
||||
31
|
||||
|
||||
"""
|
||||
@spec days_in_month(year, month) :: 28..31
|
||||
@@ -207,6 +267,22 @@ defmodule Calendar.ISO do
|
||||
def days_in_month(_, month) when month in [4, 6, 9, 11], do: 30
|
||||
def days_in_month(_, month) when month in 1..12, do: 31
|
||||
|
||||
@doc """
|
||||
Returns how many months there are in the given year.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Calendar.ISO.months_in_year(2004)
|
||||
12
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@impl true
|
||||
@spec months_in_year(year) :: 12
|
||||
def months_in_year(_year) do
|
||||
@months_in_year
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns if the given year is a leap year.
|
||||
|
||||
@@ -220,12 +296,14 @@ defmodule Calendar.ISO do
|
||||
true
|
||||
iex> Calendar.ISO.leap_year?(1900)
|
||||
false
|
||||
iex> Calendar.ISO.leap_year?(-4)
|
||||
true
|
||||
|
||||
"""
|
||||
@spec leap_year?(year) :: boolean()
|
||||
@impl true
|
||||
def leap_year?(year) when is_integer(year) and year >= 0 do
|
||||
rem(year, 4) === 0 and (rem(year, 100) > 0 or rem(year, 400) === 0)
|
||||
def leap_year?(year) when is_integer(year) do
|
||||
rem(year, 4) === 0 and (rem(year, 100) !== 0 or rem(year, 400) === 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -237,18 +315,21 @@ defmodule Calendar.ISO do
|
||||
|
||||
iex> Calendar.ISO.day_of_week(2016, 10, 31)
|
||||
1
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 01)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 1)
|
||||
2
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 02)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 2)
|
||||
3
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 03)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 3)
|
||||
4
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 04)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 4)
|
||||
5
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 05)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 5)
|
||||
6
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 06)
|
||||
iex> Calendar.ISO.day_of_week(2016, 11, 6)
|
||||
7
|
||||
iex> Calendar.ISO.day_of_week(-99, 1, 31)
|
||||
4
|
||||
|
||||
"""
|
||||
@spec day_of_week(year, month, day) :: 1..7
|
||||
@impl true
|
||||
@@ -259,7 +340,23 @@ defmodule Calendar.ISO do
|
||||
|
||||
@doc """
|
||||
Converts the given time into a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6})
|
||||
"02:02:02.000002"
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 2})
|
||||
"02:02:02.00"
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 0})
|
||||
"02:02:02"
|
||||
|
||||
"""
|
||||
@spec time_to_string(
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
) :: String.t()
|
||||
@impl true
|
||||
def time_to_string(hour, minute, second, microsecond) do
|
||||
time_to_string(hour, minute, second, microsecond, :extended)
|
||||
@@ -284,7 +381,18 @@ defmodule Calendar.ISO do
|
||||
|
||||
@doc """
|
||||
Converts the given date into a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
"2015-02-28"
|
||||
iex> Calendar.ISO.date_to_string(2017, 8, 1)
|
||||
"2017-08-01"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31)
|
||||
"-0099-01-31"
|
||||
|
||||
"""
|
||||
@spec date_to_string(year, month, day) :: String.t()
|
||||
@impl true
|
||||
def date_to_string(year, month, day) do
|
||||
date_to_string(year, month, day, :extended)
|
||||
@@ -300,16 +408,58 @@ defmodule Calendar.ISO do
|
||||
|
||||
@doc """
|
||||
Converts the datetime (without time zone) into a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
"2015-02-28 01:02:03.000004"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@spec naive_datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
) :: String.t()
|
||||
def naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) do
|
||||
date_to_string(year, month, day) <> " " <> time_to_string(hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convers the datetime (with time zone) into a string.
|
||||
Converts the datetime (with time zone) into a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
|
||||
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
|
||||
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
|
||||
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@spec datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond(),
|
||||
Calendar.time_zone(),
|
||||
Calendar.zone_abbr(),
|
||||
Calendar.utc_offset(),
|
||||
Calendar.std_offset()
|
||||
) :: String.t()
|
||||
def datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
@@ -330,18 +480,58 @@ defmodule Calendar.ISO do
|
||||
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if the date given is valid according to the proleptic Gregorian calendar.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 28)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 30)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 31)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 32)
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec valid_date?(year, month, day) :: boolean
|
||||
def valid_date?(year, month, day) do
|
||||
month in 1..12 and year in 0..9999 and day in 1..days_in_month(year, month)
|
||||
month in 1..12 and year in -9999..9999 and day in 1..days_in_month(year, month)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if the date given is valid according to the proleptic Gregorian calendar.
|
||||
Note that leap seconds are considered valid, but the use of 24:00:00 as the
|
||||
zero hour of the day is considered invalid.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec valid_time?(Calendar.hour(), Calendar.minute(), Calendar.secon(), Calendar.microsecond()) ::
|
||||
boolean
|
||||
def valid_time?(hour, minute, second, {microsecond, precision}) do
|
||||
hour in 0..23 and minute in 0..59 and second in 0..60 and microsecond in 0..999_999 and
|
||||
precision in 0..6
|
||||
end
|
||||
|
||||
@doc """
|
||||
See `c:Calendar.day_rollover_relative_to_midlight_utc/0` for documentation.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@impl true
|
||||
@spec day_rollover_relative_to_midnight_utc() :: {0, 1}
|
||||
def day_rollover_relative_to_midnight_utc() do
|
||||
{0, 1}
|
||||
end
|
||||
@@ -371,11 +561,15 @@ defmodule Calendar.ISO do
|
||||
defp sign(total) when total < 0, do: "-"
|
||||
defp sign(_), do: "+"
|
||||
|
||||
defp zero_pad(val, count) do
|
||||
defp zero_pad(val, count) when val >= 0 do
|
||||
num = Integer.to_string(val)
|
||||
:binary.copy("0", count - byte_size(num)) <> num
|
||||
end
|
||||
|
||||
defp zero_pad(val, count) do
|
||||
"-" <> zero_pad(-val, count)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
@@ -405,6 +599,7 @@ defmodule Calendar.ISO do
|
||||
do: precision_for_unit(div(number, 10), precision + 1)
|
||||
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def date_to_iso8601(year, month, day, format \\ :extended) do
|
||||
date_to_string(year, month, day, format)
|
||||
end
|
||||
@@ -509,6 +704,7 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def iso_days_to_unit({days, {parts, ppd}}, unit) do
|
||||
day_microseconds = days * @parts_per_day
|
||||
microseconds = div(parts * @parts_per_day, ppd)
|
||||
@@ -516,6 +712,7 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def add_day_fraction_to_iso_days({days, {parts, ppd}}, add, ppd) do
|
||||
normalize_iso_days(days, parts + add, ppd)
|
||||
end
|
||||
|
||||
@@ -4,7 +4,8 @@ defmodule NaiveDateTime do
|
||||
|
||||
The NaiveDateTime struct contains the fields year, month, day, hour,
|
||||
minute, second, microsecond and calendar. New naive datetimes can be
|
||||
built with the `new/2` and `new/7` functions or using the `~N` sigil:
|
||||
built with the `new/2` and `new/8` functions or using the
|
||||
[`~N`](`Kernel.sigil_N/2`) sigil:
|
||||
|
||||
iex> ~N[2000-01-01 23:00:07]
|
||||
~N[2000-01-01 23:00:07]
|
||||
@@ -27,18 +28,18 @@ defmodule NaiveDateTime do
|
||||
`NaiveDateTime` is not validated against a time zone, such errors
|
||||
would go unnoticed.
|
||||
|
||||
The functions on this module work with the `NaiveDateTime` struct as well
|
||||
The functions of this module work with the `NaiveDateTime` struct as well
|
||||
as any struct that contains the same fields as the `NaiveDateTime` struct,
|
||||
such as `DateTime`. Such functions expect
|
||||
`t:Calendar.naive_datetime/0` in their typespecs (instead of `t:t/0`).
|
||||
|
||||
Developers should avoid creating the NaiveDateTime structs directly
|
||||
and instead rely on the functions provided by this module as well
|
||||
and instead, rely on the functions provided by this module as well
|
||||
as the ones in 3rd party calendar libraries.
|
||||
|
||||
## Comparing naive date times
|
||||
|
||||
Comparisons in Elixir using `==`, `>`, `<` and similar are structural
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `NaiveDateTime` struct fields. For proper comparison
|
||||
between naive datetimes, use the `compare/2` function.
|
||||
|
||||
@@ -52,7 +53,7 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.diff(~N[2010-04-17 14:00:00], ~N[1970-01-01 00:00:00])
|
||||
1271512800
|
||||
|
||||
iex> NaiveDateTime.add(~N[1970-01-01 00:00:00], 1271512800)
|
||||
iex> NaiveDateTime.add(~N[1970-01-01 00:00:00], 1_271_512_800)
|
||||
~N[2010-04-17 14:00:00]
|
||||
|
||||
Those functions are optimized to deal with common epochs, such
|
||||
@@ -95,6 +96,7 @@ defmodule NaiveDateTime do
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO)
|
||||
|
||||
@@ -155,6 +157,9 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.new(2000, 1, 1, 23, 59, 59, 1_000_000)
|
||||
{:error, :invalid_time}
|
||||
|
||||
iex> NaiveDateTime.new(2000, 1, 1, 23, 59, 59, {0, 1}, Calendar.ISO)
|
||||
{:ok, ~N[2000-01-01 23:59:59.0]}
|
||||
|
||||
"""
|
||||
@spec new(
|
||||
Calendar.year(),
|
||||
@@ -166,10 +171,35 @@ defmodule NaiveDateTime do
|
||||
Calendar.microsecond(),
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
def new(year, month, day, hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO) do
|
||||
with {:ok, date} <- Date.new(year, month, day, calendar),
|
||||
{:ok, time} <- Time.new(hour, minute, second, microsecond, calendar),
|
||||
do: new(date, time)
|
||||
def new(year, month, day, hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
def new(year, month, day, hour, minute, second, microsecond, calendar)
|
||||
when is_integer(microsecond) do
|
||||
new(year, month, day, hour, minute, second, {microsecond, 6}, calendar)
|
||||
end
|
||||
|
||||
def new(year, month, day, hour, minute, second, microsecond, calendar) do
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
{:error, :invalid_date}
|
||||
|
||||
not calendar.valid_time?(hour, minute, second, microsecond) ->
|
||||
{:error, :invalid_time}
|
||||
|
||||
true ->
|
||||
naive_datetime = %NaiveDateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
|
||||
{:ok, naive_datetime}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -206,7 +236,7 @@ defmodule NaiveDateTime do
|
||||
Adds a specified amount of time to a `NaiveDateTime`.
|
||||
|
||||
Accepts an `integer` in any `unit` available from `t:System.time_unit/0`.
|
||||
Negative values will be move backwards in time.
|
||||
Negative values will move backwards in time.
|
||||
|
||||
This operation is only possible if both calendars are convertible to `Calendar.ISO`.
|
||||
|
||||
@@ -230,14 +260,15 @@ defmodule NaiveDateTime do
|
||||
|
||||
# changes below the precision will not be visible
|
||||
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
|
||||
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
|
||||
{21000, 0}
|
||||
|
||||
# from Gregorian seconds
|
||||
iex> NaiveDateTime.add(~N[0000-01-01 00:00:00], 63579428950)
|
||||
iex> NaiveDateTime.add(~N[0000-01-01 00:00:00], 63_579_428_950)
|
||||
~N[2014-10-02 00:29:10]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec add(t, integer, System.time_unit()) :: t
|
||||
def add(%NaiveDateTime{} = naive_datetime, integer, unit \\ :second)
|
||||
when is_integer(integer) do
|
||||
@@ -268,29 +299,36 @@ defmodule NaiveDateTime do
|
||||
21
|
||||
iex> NaiveDateTime.diff(~N[2014-10-02 00:29:10], ~N[2014-10-02 00:29:12])
|
||||
-2
|
||||
iex> NaiveDateTime.diff(~N[-0001-10-02 00:29:10], ~N[-0001-10-02 00:29:12])
|
||||
-2
|
||||
|
||||
# to Gregorian seconds
|
||||
iex> NaiveDateTime.diff(~N[2014-10-02 00:29:10], ~N[0000-01-01 00:00:00])
|
||||
63579428950
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec diff(t, t, System.time_unit()) :: integer
|
||||
def diff(%NaiveDateTime{} = ndatetime1, %NaiveDateTime{} = ndatetime2, unit \\ :second) do
|
||||
if not Calendar.compatible_calendars?(ndatetime1.calendar, ndatetime2.calendar) do
|
||||
def diff(
|
||||
%NaiveDateTime{} = naive_datetime1,
|
||||
%NaiveDateTime{} = naive_datetime2,
|
||||
unit \\ :second
|
||||
) do
|
||||
if not Calendar.compatible_calendars?(naive_datetime1.calendar, naive_datetime2.calendar) do
|
||||
raise ArgumentError,
|
||||
"cannot calculate the difference between #{inspect(ndatetime1)} and " <>
|
||||
"#{inspect(ndatetime2)} because their calendars are not compatible " <>
|
||||
"cannot calculate the difference between #{inspect(naive_datetime1)} and " <>
|
||||
"#{inspect(naive_datetime2)} because their calendars are not compatible " <>
|
||||
"and thus the result would be ambiguous"
|
||||
end
|
||||
|
||||
units1 = ndatetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units2 = ndatetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units1 = naive_datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units2 = naive_datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units1 - units2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given naive datetime with the microsecond field truncated to the
|
||||
given precision (`:microsecond`, `millisecond` or `:second`).
|
||||
given precision (`:microsecond`, `:millisecond` or `:second`).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -304,9 +342,10 @@ defmodule NaiveDateTime do
|
||||
~N[2017-11-06 00:23:51]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%NaiveDateTime{microsecond: microsecond} = ndatetime, precision) do
|
||||
%{ndatetime | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
def truncate(%NaiveDateTime{microsecond: microsecond} = naive_datetime, precision) do
|
||||
%{naive_datetime | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -366,6 +405,8 @@ defmodule NaiveDateTime do
|
||||
"2000-02-28 23:00:13"
|
||||
iex> NaiveDateTime.to_string(~N[2000-02-28 23:00:13.001])
|
||||
"2000-02-28 23:00:13.001"
|
||||
iex> NaiveDateTime.to_string(~N[-0100-12-15 03:20:31])
|
||||
"-0100-12-15 03:20:31"
|
||||
|
||||
This function can also be used to convert a DateTime to a string without
|
||||
the time zone information:
|
||||
@@ -396,7 +437,7 @@ defmodule NaiveDateTime do
|
||||
Parses the extended "Date and time of day" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Timezone offset may be included in the string but they will be
|
||||
Time zone offset may be included in the string but they will be
|
||||
simply discarded as such information is not included in naive date
|
||||
times.
|
||||
|
||||
@@ -452,20 +493,33 @@ defmodule NaiveDateTime do
|
||||
|
||||
"""
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
|
||||
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
|
||||
true <- sep in [?\s, ?T],
|
||||
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
|
||||
{year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
{min, ""} <- Integer.parse(min),
|
||||
{sec, ""} <- Integer.parse(sec),
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
with {:ok, %{year: year} = naive_datetime} <- raw_from_iso8601(rest, calendar) do
|
||||
{:ok, %{naive_datetime | year: -year}}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
@sep [?\s, ?T]
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
|
||||
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO),
|
||||
do: convert(utc_date, calendar)
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, min, sec} = unquote(read_time)
|
||||
|
||||
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO) do
|
||||
convert(utc_date, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
@@ -563,12 +617,6 @@ defmodule NaiveDateTime do
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
def to_iso8601(_date, format) do
|
||||
raise ArgumentError,
|
||||
"NaiveDateTime.to_iso8601/2 expects format to be :extended or :basic, " <>
|
||||
"got: #{inspect(format)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `NaiveDateTime` struct to an Erlang datetime tuple.
|
||||
|
||||
@@ -614,7 +662,7 @@ defmodule NaiveDateTime do
|
||||
{:ok, ~N[2000-01-01 13:30:15.005]}
|
||||
iex> NaiveDateTime.from_erl({{2000, 13, 1}, {13, 30, 15}})
|
||||
{:error, :invalid_date}
|
||||
iex> NaiveDateTime.from_erl({{2000, 13, 1},{13, 30, 15}})
|
||||
iex> NaiveDateTime.from_erl({{2000, 13, 1}, {13, 30, 15}})
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@@ -684,6 +732,7 @@ defmodule NaiveDateTime do
|
||||
:lt
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec compare(Calendar.naive_datetime(), Calendar.naive_datetime()) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar1} = naive_datetime1, %{calendar: calendar2} = naive_datetime2) do
|
||||
if Calendar.compatible_calendars?(calendar1, calendar2) do
|
||||
@@ -720,6 +769,7 @@ defmodule NaiveDateTime do
|
||||
hour: 13, minute: 30, second: 15, microsecond: {0, 0}}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert(Calendar.naive_datetime(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
|
||||
@@ -781,6 +831,7 @@ defmodule NaiveDateTime do
|
||||
hour: 13, minute: 30, second: 15, microsecond: {0, 0}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert!(Calendar.naive_datetime(), Calendar.calendar()) :: t
|
||||
def convert!(naive_datetime, calendar) do
|
||||
case convert(naive_datetime, calendar) do
|
||||
|
||||
@@ -3,8 +3,8 @@ defmodule Time do
|
||||
A Time struct and functions.
|
||||
|
||||
The Time struct contains the fields hour, minute, second and microseconds.
|
||||
New times can be built with the `new/4` function or using the `~T`
|
||||
sigil:
|
||||
New times can be built with the `new/4` function or using the
|
||||
[`~T`](`Kernel.sigil_T/2`) sigil:
|
||||
|
||||
iex> ~T[23:00:07.001]
|
||||
~T[23:00:07.001]
|
||||
@@ -29,7 +29,7 @@ defmodule Time do
|
||||
|
||||
## Comparing times
|
||||
|
||||
Comparisons in Elixir using `==`, `>`, `<` and similar are structural
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `Time` struct fields. For proper comparison between
|
||||
times, use the `compare/2` function.
|
||||
"""
|
||||
@@ -55,6 +55,7 @@ defmodule Time do
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO) do
|
||||
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(:os.system_time(), :native)
|
||||
@@ -176,7 +177,7 @@ defmodule Time do
|
||||
Parses the extended "Local time" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Timezone offset may be included in the string but they will be
|
||||
Time zone offset may be included in the string but they will be
|
||||
simply discarded as such information is not included in times.
|
||||
|
||||
As specified in the standard, the separator "T" may be omitted if
|
||||
@@ -216,27 +217,31 @@ defmodule Time do
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<?T, h, rest::binary>>, calendar) when h in ?0..?9 do
|
||||
from_iso8601(<<h, rest::binary>>, calendar)
|
||||
def from_iso8601(<<?T, rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
def from_iso8601(<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>, calendar) do
|
||||
with {hour, ""} <- Integer.parse(hour),
|
||||
{min, ""} <- Integer.parse(min),
|
||||
{sec, ""} <- Integer.parse(sec),
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with <<unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_time),
|
||||
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO),
|
||||
do: convert(utc_time, calendar)
|
||||
{hour, min, sec} = unquote(read_time)
|
||||
|
||||
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO) do
|
||||
convert(utc_time, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<_::binary>>, _calendar) do
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the extended "Local time" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
@@ -251,6 +256,7 @@ defmodule Time do
|
||||
~T[23:50:07.123]
|
||||
iex> Time.from_iso8601!("2015:01:23 23-50-07")
|
||||
** (ArgumentError) cannot parse "2015:01:23 23-50-07" as time, reason: :invalid_format
|
||||
|
||||
"""
|
||||
@spec from_iso8601!(String.t(), Calendar.calendar()) :: t
|
||||
def from_iso8601!(string, calendar \\ Calendar.ISO) do
|
||||
@@ -287,17 +293,25 @@ defmodule Time do
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.time(), :extended | :basic) :: String.t()
|
||||
def to_iso8601(time, format \\ :extended) when format in [:extended, :basic] do
|
||||
def to_iso8601(time, format \\ :extended)
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = time, format) when format in [:extended, :basic] do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = convert!(time, Calendar.ISO)
|
||||
} = time
|
||||
|
||||
Calendar.ISO.time_to_iso8601(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = time, format) when format in [:extended, :basic] do
|
||||
time
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts given `time` to an Erlang time tuple.
|
||||
|
||||
@@ -379,7 +393,7 @@ defmodule Time do
|
||||
~T[17:30:00.000000]
|
||||
iex> Time.add(~T[11:00:00.005], 2400)
|
||||
~T[11:40:00.005000]
|
||||
iex> Time.add(~T[00:00:00], 86399999, :millisecond)
|
||||
iex> Time.add(~T[00:00:00], 86_399_999, :millisecond)
|
||||
~T[23:59:59.999000]
|
||||
iex> Time.add(~T[17:10:05], 86400)
|
||||
~T[17:10:05.000000]
|
||||
@@ -387,6 +401,7 @@ defmodule Time do
|
||||
~T[22:59:00.000000]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec add(Calendar.time(), integer, System.time_unit()) :: t
|
||||
def add(%{calendar: calendar} = time, number, unit \\ :second) when is_integer(number) do
|
||||
number = System.convert_time_unit(number, unit, :microsecond)
|
||||
@@ -433,6 +448,7 @@ defmodule Time do
|
||||
:gt
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec compare(Calendar.time(), Calendar.time()) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar} = time1, %{calendar: calendar} = time2) do
|
||||
%{hour: hour1, minute: minute1, second: second1, microsecond: {microsecond1, _}} = time1
|
||||
@@ -472,6 +488,7 @@ defmodule Time do
|
||||
{:ok, %Time{calendar: Calendar.Holocene, hour: 13, minute: 30, second: 15, microsecond: {0, 0}}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert(Calendar.time(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
@@ -527,6 +544,7 @@ defmodule Time do
|
||||
%Time{calendar: Calendar.Holocene, hour: 13, minute: 30, second: 15, microsecond: {0, 0}}
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec convert!(Calendar.time(), Calendar.calendar()) :: t
|
||||
def convert!(time, calendar) do
|
||||
case convert(time, calendar) do
|
||||
@@ -541,7 +559,7 @@ defmodule Time do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the difference between two times, considering only the hour, minute
|
||||
Returns the difference between two times, considering only the hour, minute,
|
||||
second and microsecond.
|
||||
|
||||
As with the `compare/2` function both `Time` structs and other structures
|
||||
@@ -568,7 +586,7 @@ defmodule Time do
|
||||
|
||||
# Two `NaiveDateTime` structs could have big differences in the date
|
||||
# but only the time part is considered.
|
||||
iex> Time.diff(~N[2017-01-01 00:29:12], (~N[1900-02-03 00:29:10]))
|
||||
iex> Time.diff(~N[2017-01-01 00:29:12], ~N[1900-02-03 00:29:10])
|
||||
2
|
||||
|
||||
iex> Time.diff(~T[00:29:12], ~T[00:29:10], :microsecond)
|
||||
@@ -577,6 +595,7 @@ defmodule Time do
|
||||
-2_000_000
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec diff(Calendar.time(), Calendar.time(), System.time_unit()) :: integer
|
||||
def diff(time1, time2, unit \\ :second) do
|
||||
fraction1 = to_day_fraction(time1)
|
||||
@@ -602,6 +621,7 @@ defmodule Time do
|
||||
~T[01:01:01]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%Time{microsecond: microsecond} = time, precision) do
|
||||
%{time | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
|
||||
+293
-169
@@ -5,25 +5,55 @@ defmodule Code do
|
||||
This module complements Erlang's [`:code` module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behaviour which is specific to Elixir. Almost all of the functions in this module
|
||||
have global side effects on the behaviour of Elixir.
|
||||
|
||||
## Working with files
|
||||
|
||||
This module contains three functions for compiling and evaluating files.
|
||||
Here is a summary of them and their behaviour:
|
||||
|
||||
* `require_file/2` - compiles a file and tracks its name. It does not
|
||||
compile the file again if it has been previously required.
|
||||
|
||||
* `compile_file/2` - compiles a file without tracking its name. Compiles the
|
||||
file multiple times when invoked multiple times.
|
||||
|
||||
* `eval_file/2` - evaluates the file contents without tracking its name. It
|
||||
returns the result of the last expression in the file, instead of the modules
|
||||
defined in it.
|
||||
|
||||
In a nutshell, the first must be used when you want to keep track of the files
|
||||
handled by the system, to avoid the same file from being compiled multiple
|
||||
times. This is common in scripts.
|
||||
|
||||
`compile_file/2` must be used when you are interested in the modules defined in a
|
||||
file, without tracking. `eval_file/2` should be used when you are intested on
|
||||
the result of evaluating the file rather than the modules it defines.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Lists all loaded files.
|
||||
Lists all required files.
|
||||
|
||||
## Examples
|
||||
|
||||
Code.require_file("../eex/test/eex_test.exs")
|
||||
List.first(Code.loaded_files()) =~ "eex_test.exs"
|
||||
List.first(Code.required_files()) =~ "eex_test.exs"
|
||||
#=> true
|
||||
|
||||
"""
|
||||
@spec loaded_files() :: [binary]
|
||||
@doc since: "1.7.0"
|
||||
@spec required_files() :: [binary]
|
||||
def required_files do
|
||||
:elixir_code_server.call(:required)
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on 1.9
|
||||
@doc false
|
||||
def loaded_files do
|
||||
:elixir_code_server.call(:loaded)
|
||||
required_files()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes files from the loaded files list.
|
||||
Removes files from the required files list.
|
||||
|
||||
The modules defined in the file are not removed;
|
||||
calling this function only removes them from the list,
|
||||
@@ -31,17 +61,27 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
# Load EEx test code, unload file, check for functions still available
|
||||
Code.load_file("../eex/test/eex_test.exs")
|
||||
# Require EEx test code
|
||||
Code.require_file("../eex/test/eex_test.exs")
|
||||
|
||||
Code.unload_files(Code.loaded_files())
|
||||
# Now unrequire all files
|
||||
Code.unrequire_files(Code.required_files())
|
||||
|
||||
# Notice modules are still available
|
||||
function_exported?(EExTest.Compiled, :before_compile, 0)
|
||||
#=> true
|
||||
|
||||
"""
|
||||
@spec unload_files([binary]) :: :ok
|
||||
@doc since: "1.7.0"
|
||||
@spec unrequire_files([binary]) :: :ok
|
||||
def unrequire_files(files) do
|
||||
:elixir_code_server.cast({:unrequire_files, files})
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on 1.9
|
||||
@doc false
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast({:unload_files, files})
|
||||
unrequire_files(files)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -208,7 +248,10 @@ defmodule Code do
|
||||
* `:line` - the line the string starts, used for error reporting
|
||||
|
||||
* `:line_length` - the line length to aim for when formatting
|
||||
the document. Defaults to 98.
|
||||
the document. Defaults to 98. Note this value is used as
|
||||
reference but it is not enforced by the formatter as sometimes
|
||||
user intervention is required. See "Running the formatter"
|
||||
section
|
||||
|
||||
* `:locals_without_parens` - a keyword list of name and arity
|
||||
pairs that should be kept without parens whenever possible.
|
||||
@@ -243,6 +286,104 @@ defmodule Code do
|
||||
based on the name, this behaviour should be configurable, such as the
|
||||
`:locals_without_parens` option.
|
||||
|
||||
## Running the formatter
|
||||
|
||||
The formatter attempts to fit the most it can on a single line and
|
||||
introduces line breaks wherever possible when it cannot.
|
||||
|
||||
In some cases, this may lead to undesired formatting. Therefore, **some
|
||||
code generated by the formatter may not be aesthetically pleasing and
|
||||
may require explicit intervention from the developer**. That's why we
|
||||
do not recommend to run the formatter blindly in an existing codebase.
|
||||
Instead you should format and sanity check each formatted file.
|
||||
|
||||
Let's see some examples. The code below:
|
||||
|
||||
"this is a very long string ... #{inspect(some_value)}"
|
||||
|
||||
may be formatted as:
|
||||
|
||||
"this is a very long string ... #{
|
||||
inspect(some_value)
|
||||
}"
|
||||
|
||||
This happens because the only place the formatter can introduce a
|
||||
new line without changing the code semantics is in the interpolation.
|
||||
In those scenarios, we recommend developers to directly adjust the
|
||||
code. Here we can use the binary concatenation operator `<>/2`:
|
||||
|
||||
"this is a very long string " <>
|
||||
"... #{inspect(some_value)}"
|
||||
|
||||
The string concatenation makes the code fit on a single line and also
|
||||
gives more options to the formatter.
|
||||
|
||||
A similar example is when the formatter breaks a function definition
|
||||
over multiple clauses:
|
||||
|
||||
def my_function(
|
||||
%User{name: name, age: age, ...},
|
||||
arg1,
|
||||
arg2
|
||||
) do
|
||||
...
|
||||
end
|
||||
|
||||
While the code above is completely valid, you may prefer to match on
|
||||
the struct variables inside the function body in order to keep the
|
||||
definition on a single line:
|
||||
|
||||
def my_function(%User{} = user, arg1, arg2) do
|
||||
%{name: name, age: age, ...} = user
|
||||
...
|
||||
end
|
||||
|
||||
In some situations, you can use the fact the formatter does not generate
|
||||
elegant code as a hint for refactoring. Take this code:
|
||||
|
||||
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
|
||||
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
|
||||
required_permissions == Enum.to_list(MapSet.intersection(MapSet.new(required_permissions), MapSet.new(available_permissions)))
|
||||
end
|
||||
|
||||
The code above has very long lines and running the formatter is not going
|
||||
to address this issue. In fact, the formatter may make it more obvious that
|
||||
you have complex expressions:
|
||||
|
||||
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
|
||||
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
|
||||
required_permissions ==
|
||||
Enum.to_list(
|
||||
MapSet.intersection(
|
||||
MapSet.new(required_permissions),
|
||||
MapSet.new(available_permissions)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
Take such cases as a suggestion that your code should be refactored:
|
||||
|
||||
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
|
||||
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
|
||||
matching_permissions?(required_permissions, available_permissions)
|
||||
end
|
||||
|
||||
defp matching_permissions?(required_permissions, available_permissions) do
|
||||
intersection =
|
||||
required_permissions
|
||||
|> MapSet.new()
|
||||
|> MapSet.intersection(MapSet.new(available_permissions))
|
||||
|> Enum.to_list()
|
||||
|
||||
required_permissions == intersection
|
||||
end
|
||||
|
||||
To sum it up: since the formatter cannot change the semantics of your
|
||||
code, sometimes it is necessary to tweak or refactor the code to get
|
||||
optimal formatting. To help better understand how to control the formatter,
|
||||
we describe in the next sections the cases where the formatter keeps the
|
||||
user encoding and how to control multiline expressions.
|
||||
|
||||
## Keeping user's formatting
|
||||
|
||||
The formatter respects the input format in some cases. Those are
|
||||
@@ -275,53 +416,6 @@ defmodule Code do
|
||||
rules in the future. The goal of documenting them is to provide better
|
||||
understanding on what to expect from the formatter.
|
||||
|
||||
## Adjusting formatted output
|
||||
|
||||
The formatter attempts to the fit the most it can on a single line.
|
||||
When the code does not fit a single line, the formatter introduces
|
||||
line breaks in the code.
|
||||
|
||||
In some rare situations, this may lead to undesired formatting.
|
||||
For example, the code below:
|
||||
|
||||
"this is a very long string ... #{inspect(some_value)}"
|
||||
|
||||
may be formatted as:
|
||||
|
||||
"this is a very long string ... #{
|
||||
inspect(some_value)
|
||||
}"
|
||||
|
||||
This happens because the only place the formatter can introduce a
|
||||
new line without changing the code semantics is in the interpolation.
|
||||
In those scenarios, we recommend developers to directly adjust the
|
||||
code. Here we can use the binary concatenation operator `<>`:
|
||||
|
||||
"this is a very long string " <>
|
||||
"... #{inspect(some_value)}"
|
||||
|
||||
The string concatenation makes the code fit on a single line and also
|
||||
gives more options to the formatter.
|
||||
|
||||
A similar example is when the formatter breaks a fuction definition
|
||||
over multiple clauses:
|
||||
|
||||
def my_function(
|
||||
%User{name: name, age: age, ...},
|
||||
arg1,
|
||||
arg2
|
||||
) do
|
||||
|
||||
While the code above is completely valid, you may prefer to match on
|
||||
the struct variables inside the function body in order to keep the
|
||||
definition on a single line:
|
||||
|
||||
def my_function(%User{} = user, arg1, arg2) do
|
||||
%{name: name, age: age, ...} = user
|
||||
|
||||
Since the formatter cannot change the semantics of your code,
|
||||
sometimes it is necessary to tweak the code to get optimal formatting.
|
||||
|
||||
### Multi-line lists, maps, tuples, etc
|
||||
|
||||
You can force lists, tuples, bitstrings, maps, structs and function
|
||||
@@ -343,14 +437,14 @@ defmodule Code do
|
||||
|
||||
[foo, bar]
|
||||
|
||||
You can also force keywords to be rendered on multiple lines by
|
||||
having each entry on its own line:
|
||||
You can also force function calls and keywords to be rendered on multiple
|
||||
lines by having each entry on its own line:
|
||||
|
||||
defstruct name: nil,
|
||||
age: 0
|
||||
|
||||
The code above will be kept with one keyword entry per line by the
|
||||
formatter. To avoid that, just keep everything on a single line.
|
||||
formatter. To avoid that, just squash everything into a single line.
|
||||
|
||||
### Parens and no parens in function calls
|
||||
|
||||
@@ -359,7 +453,8 @@ defmodule Code do
|
||||
|
||||
1. calls that have do/end blocks
|
||||
2. local calls without parens where the name and arity of the local
|
||||
call is also listed under `:locals_without_parens`
|
||||
call is also listed under `:locals_without_parens` (except for
|
||||
calls with arity 0, where the compiler always require parens)
|
||||
|
||||
The choice of parens and no parens also affects indentation. When a
|
||||
function call with parens doesn't fit on the same line, the formatter
|
||||
@@ -379,16 +474,15 @@ defmodule Code do
|
||||
arg2,
|
||||
arg3
|
||||
|
||||
If the last argument is a data structure of variable length, such as
|
||||
maps and lists, and the beginning of the data structure fits on the
|
||||
same line as the function call, then no indentation happens, this
|
||||
allows code like this:
|
||||
If the last argument is a data structure, such as maps and lists, and
|
||||
the beginning of the data structure fits on the same line as the function
|
||||
call, then no indentation happens, this allows code like this:
|
||||
|
||||
Enum.reduce(some_collection, initial_value, fn element, acc ->
|
||||
# code
|
||||
end)
|
||||
|
||||
some_funtion_without_parens %{
|
||||
some_function_without_parens %{
|
||||
foo: :bar,
|
||||
baz: :bat
|
||||
}
|
||||
@@ -402,7 +496,7 @@ defmodule Code do
|
||||
The formatter also extracts all trailing comments to their previous line.
|
||||
For example, the code below
|
||||
|
||||
hello # world
|
||||
hello #world
|
||||
|
||||
will be rewritten to
|
||||
|
||||
@@ -437,6 +531,7 @@ defmodule Code do
|
||||
user formatting). In such cases, the code formatter will always format to
|
||||
the latter.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec format_string!(binary, keyword) :: iodata
|
||||
def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do
|
||||
line_length = Keyword.get(opts, :line_length, 98)
|
||||
@@ -450,6 +545,7 @@ defmodule Code do
|
||||
See `format_string!/2` for more information on code formatting and
|
||||
available options.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec format_file!(binary, keyword) :: iodata
|
||||
def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
string = File.read!(file)
|
||||
@@ -554,6 +650,10 @@ defmodule Code do
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
Defaults to `false`.
|
||||
|
||||
* `:warn_on_unnecessary_quotes` - when `false`, does not warn
|
||||
when atoms, keywords or calls have unnecessary quotes on
|
||||
them. Defaults to `true`.
|
||||
|
||||
## `Macro.to_string/2`
|
||||
|
||||
The opposite of converting a string to its quoted form is
|
||||
@@ -566,8 +666,12 @@ defmodule Code do
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
|
||||
with {:ok, tokens} <- :elixir.string_to_tokens(to_charlist(string), line, file, opts) do
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
case :elixir.string_to_tokens(to_charlist(string), line, file, opts) do
|
||||
{:ok, tokens} ->
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
|
||||
{:error, _error_msg} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -593,8 +697,8 @@ defmodule Code do
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file/2` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file/2` simply evaluates the file contents and returns the
|
||||
While `require_file/2` and `compile_file/2` returns the loaded modules and their
|
||||
bytecode, `eval_file/2` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings (exactly the same return value as `eval_string/3`).
|
||||
"""
|
||||
@spec eval_file(binary, nil | binary) :: {term, binding :: list}
|
||||
@@ -603,32 +707,13 @@ defmodule Code do
|
||||
eval_string(File.read!(file), [], file: file, line: 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ModuleName, bytecode}`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file/2` is invoked by different processes concurrently,
|
||||
the target file will be loaded concurrently many times. Check `require_file/2`
|
||||
if you don't want a file to be loaded concurrently.
|
||||
|
||||
## Examples
|
||||
|
||||
modules = Code.load_file("eex_test.exs", "../eex/test")
|
||||
List.first(modules)
|
||||
#=> {EExTest.Compiled, <<70, 79, 82, 49, ...>>}
|
||||
|
||||
"""
|
||||
@spec load_file(binary, nil | binary) :: [{module, binary}]
|
||||
# TODO: Deprecate me on 1.9
|
||||
@doc false
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call({:acquire, file})
|
||||
loaded = :elixir_compiler.file(file)
|
||||
:elixir_code_server.cast({:loaded, file})
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
end
|
||||
|
||||
@@ -636,47 +721,51 @@ defmodule Code do
|
||||
Requires the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
The return value is the same as that of `load_file/2`. If the file was already
|
||||
required or loaded, `require_file/2` doesn't do anything and returns `nil`.
|
||||
If the file was already required, `require_file/2` doesn't do anything and
|
||||
returns `nil`.
|
||||
|
||||
Notice that if `require_file/2` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file/2` acquires a lock and the remaining
|
||||
ones will block until the file is available. This means that if `require_file/2` is called
|
||||
more than one times with a given file, that file will be loaded only once. The first process to
|
||||
call `require_file/2` will get the list of loaded modules, others will get `nil`.
|
||||
ones will block until the file is available. This means that if `require_file/2`
|
||||
is called more than once with a given file, that file will be compiled only once.
|
||||
The first process to call `require_file/2` will get the list of loaded modules,
|
||||
others will get `nil`.
|
||||
|
||||
Check `load_file/2` if you want to load a file multiple times. See also `unload_files/1`.
|
||||
See `compile_file/2` if you would like to compile a file without tracking its
|
||||
filenames. Finally, if you would like to get the result of evaluating file rather
|
||||
than the modules defined in it, see `eval_file/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
If the code is already loaded, it returns `nil`:
|
||||
|
||||
Code.require_file("eex_test.exs", "../eex/test")
|
||||
#=> nil
|
||||
|
||||
If the code is not loaded yet, it returns the same as `load_file/2`:
|
||||
If the file has not been required, it returns the list of modules:
|
||||
|
||||
modules = Code.require_file("eex_test.exs", "../eex/test")
|
||||
List.first(modules)
|
||||
#=> {EExTest.Compiled, <<70, 79, 82, 49, ...>>}
|
||||
|
||||
If the code has been required, it returns `nil`:
|
||||
|
||||
Code.require_file("eex_test.exs", "../eex/test")
|
||||
#=> nil
|
||||
|
||||
"""
|
||||
@spec require_file(binary, nil | binary) :: [{module, binary}] | nil
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
# TODO: Simply block until :required or :proceed once load_file is removed in 2.0
|
||||
case :elixir_code_server.call({:acquire, file}) do
|
||||
:loaded ->
|
||||
:required ->
|
||||
nil
|
||||
|
||||
{:queued, ref} ->
|
||||
receive do
|
||||
{:elixir_code_server, ^ref, :loaded} -> nil
|
||||
{:elixir_code_server, ^ref, :required} -> nil
|
||||
end
|
||||
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file(file)
|
||||
:elixir_code_server.cast({:loaded, file})
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
end
|
||||
end
|
||||
@@ -705,7 +794,7 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.available_compiler_options
|
||||
iex> Code.available_compiler_options()
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
|
||||
"""
|
||||
@@ -714,6 +803,27 @@ defmodule Code do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Purge compiler modules.
|
||||
|
||||
The compiler utilizes temporary modules to compile code. For example,
|
||||
`elixir_compiler_1`, `elixir_compiler_2`, etc. In case the compiled code
|
||||
stores references to anonymous functions or similar, the Elixir compiler
|
||||
may be unable to reclaim those modules, keeping an unecessary amount of
|
||||
code in memory and eventually leading to modules such as `elixir_compiler_12345`.
|
||||
|
||||
This function purges all modules currently kept by the compiler, allowing
|
||||
old compiler module names to be resued. If there are any processes running
|
||||
any code from such modules, they will be terminated too.
|
||||
|
||||
It returns `{:ok, number_of_modules_purged}`.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec purge_compiler_modules() :: {:ok, non_neg_integer()}
|
||||
def purge_compiler_modules() do
|
||||
:elixir_code_server.call(:purge_compiler_modules)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options.
|
||||
|
||||
@@ -775,7 +885,11 @@ defmodule Code do
|
||||
given as second argument which will be used for reporting warnings
|
||||
and errors.
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.compile/2`.
|
||||
**Warning**: `string` can be any Elixir code and code can be executed with
|
||||
the same privileges as the Erlang VM: this means that such code could
|
||||
compromise the machine (for example by executing system commands).
|
||||
Don't use `compile_string/2` with untrusted input (such as strings coming
|
||||
from the network).
|
||||
"""
|
||||
@spec compile_string(List.Chars.t(), binary) :: [{module, binary}]
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
@@ -795,6 +909,25 @@ defmodule Code do
|
||||
:elixir_compiler.quoted(quoted, file)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
Returns a list of tuples where the first element is the module name and
|
||||
the second one is its bytecode (as a binary). Opposite to `require_file/2`,
|
||||
it does not track the filename of the compiled file.
|
||||
|
||||
If you would like to get the result of evaluating file rather than the
|
||||
modules defined in it, see `eval_file/2`.
|
||||
|
||||
For compiling many files concurrently, see `Kernel.ParallelCompiler.compile/2`.
|
||||
"""
|
||||
@spec compile_file(binary, nil | binary) :: [{module, binary}]
|
||||
def compile_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
:elixir_compiler.file(find_file(file, relative_to))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded.
|
||||
|
||||
@@ -921,88 +1054,79 @@ defmodule Code do
|
||||
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:
|
||||
|
||||
* `:moduledoc` - tuple `{line, doc}` where `line` is the line on
|
||||
which the module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute,
|
||||
`false` if `@moduledoc false` was used, or `nil` if no `@moduledoc`
|
||||
was used.
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, arguments, doc}`. `doc` can be either a
|
||||
string, `false` if `@doc false` was used, or `nil` if no doc was used.
|
||||
|
||||
* `:callback_docs` - list of all docstrings attached to
|
||||
`@callbacks` using the `@doc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, doc}`. `doc` can be either a string or
|
||||
`nil` if no `@doc` was set.
|
||||
|
||||
* `:type_docs` - list of all docstrings attached to `@type` callbacks
|
||||
using the `@typedoc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, doc}`. `doc` can be either a string or
|
||||
`nil` if no `@typedoc` was used.
|
||||
|
||||
* `:all` - a keyword list with `:docs`, `:moduledoc`, `:callback_docs`,
|
||||
and `:type_docs`.
|
||||
|
||||
If the module cannot be found, it returns `nil`.
|
||||
It returns the term stored in the documentation chunk in the format defined by
|
||||
[EEP 48](http://erlang.org/eep/eeps/eep-0048.html) or `{:error, reason}` if
|
||||
the chunk is not available.
|
||||
|
||||
## Examples
|
||||
|
||||
# Module documentation of an existing module
|
||||
iex> {_line, text} = Code.get_docs(Atom, :moduledoc)
|
||||
iex> text |> String.split("\n") |> Enum.at(0)
|
||||
iex> {:docs_v1, _, :elixir, _, %{"en" => module_doc}, _, _} = Code.fetch_docs(Atom)
|
||||
iex> module_doc |> String.split("\n") |> Enum.at(0)
|
||||
"Convenience functions for working with atoms."
|
||||
|
||||
# A module that doesn't exist
|
||||
iex> Code.get_docs(ModuleNotGood, :all)
|
||||
nil
|
||||
iex> Code.fetch_docs(ModuleNotGood)
|
||||
{:error, :module_not_found}
|
||||
|
||||
"""
|
||||
@doc_kinds [:docs, :moduledoc, :callback_docs, :type_docs, :all]
|
||||
|
||||
@spec get_docs(module, :moduledoc) :: {line :: pos_integer, doc :: false | binary} | nil
|
||||
@spec get_docs(module, :docs) :: [{function, line, kind, list, doc}] | nil
|
||||
when function: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :callback_docs) :: [{callback, line, kind, doc}] | nil
|
||||
when callback: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :type_docs) :: [{type, line, kind, doc}] | nil
|
||||
when type: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :all) :: keyword | nil
|
||||
def get_docs(module, kind)
|
||||
|
||||
def get_docs(module, kind) when is_atom(module) and kind in @doc_kinds do
|
||||
@doc since: "1.7.0"
|
||||
@spec fetch_docs(module | String.t()) ::
|
||||
{:docs_v1, anno, beam_language, format, module_doc :: doc, metadata,
|
||||
docs :: [{{kind, name, arity}, anno, signature, doc, metadata}]}
|
||||
| {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary}}
|
||||
| future_formats
|
||||
when anno: :erl_anno.anno(),
|
||||
beam_language: atom,
|
||||
format: binary,
|
||||
doc: %{binary => binary} | :none | :hidden,
|
||||
kind: atom,
|
||||
name: atom,
|
||||
signature: [binary],
|
||||
metadata: map,
|
||||
future_formats: term
|
||||
def fetch_docs(module) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{_module, bin, _beam_path} -> do_get_docs(bin, kind)
|
||||
:error -> nil
|
||||
{_module, bin, _beam_path} -> do_fetch_docs(bin)
|
||||
:error -> {:error, :module_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def get_docs(binpath, kind) when is_binary(binpath) and kind in @doc_kinds do
|
||||
do_get_docs(String.to_charlist(binpath), kind)
|
||||
def fetch_docs(binpath) when is_binary(binpath) do
|
||||
do_fetch_docs(String.to_charlist(binpath))
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
@docs_chunk 'Docs'
|
||||
|
||||
defp do_get_docs(bin_or_path, kind) do
|
||||
defp do_fetch_docs(bin_or_path) do
|
||||
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
|
||||
{:ok, {_module, [{@docs_chunk, bin}]}} ->
|
||||
lookup_docs(:erlang.binary_to_term(bin), kind)
|
||||
try do
|
||||
:erlang.binary_to_term(bin)
|
||||
rescue
|
||||
_ -> {:error, {:invalid_chunk, bin}}
|
||||
end
|
||||
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} ->
|
||||
nil
|
||||
{:error, :chunk_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind), do: do_lookup_docs(docs, kind)
|
||||
@doc ~S"""
|
||||
Deprecated function to retrieve old documentation format.
|
||||
|
||||
# unsupported chunk version
|
||||
defp lookup_docs(_, _), do: nil
|
||||
|
||||
defp do_lookup_docs(docs, :all), do: docs
|
||||
defp do_lookup_docs(docs, kind), do: Keyword.get(docs, kind)
|
||||
Elixir v1.7 adopts [EEP 48](http://erlang.org/eep/eeps/eep-0048.html)
|
||||
which is a new documentation format meant to be shared across all
|
||||
BEAM languages. The old format, used by `Code.get_docs/2`, is no
|
||||
longer available, and therefore this function always returns `nil`.
|
||||
Use `Code.fetch_docs/1` instead.
|
||||
"""
|
||||
@doc deprecated:
|
||||
"Code.get_docs/2 always returns nil as its outdated documentation is no longer stored on BEAM files. Use Code.fetch_docs/1 instead"
|
||||
@spec get_docs(module, :moduledoc | :docs | :callback_docs | :type_docs | :all) :: nil
|
||||
def get_docs(_module, _kind) do
|
||||
nil
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
|
||||
+459
-243
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ defmodule Code.Identifier do
|
||||
@spec unary_op(atom) :: {:non_associative, precedence :: pos_integer} | :error
|
||||
def unary_op(op) do
|
||||
cond do
|
||||
op in [:&] -> {:non_associative, 100}
|
||||
op in [:&] -> {:non_associative, 90}
|
||||
op in [:!, :^, :not, :+, :-, :~~~] -> {:non_associative, 300}
|
||||
op in [:@] -> {:non_associative, 320}
|
||||
true -> :error
|
||||
@@ -36,7 +36,7 @@ defmodule Code.Identifier do
|
||||
op in [:when] -> {:right, 50}
|
||||
op in [:::] -> {:right, 60}
|
||||
op in [:|] -> {:right, 70}
|
||||
op in [:=] -> {:right, 90}
|
||||
op in [:=] -> {:right, 100}
|
||||
op in [:||, :|||, :or] -> {:left, 130}
|
||||
op in [:&&, :&&&, :and] -> {:left, 140}
|
||||
op in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
@@ -55,22 +55,22 @@ defmodule Code.Identifier do
|
||||
@doc """
|
||||
Classifies the given atom into one of the following categories:
|
||||
|
||||
* :alias - a valid Elixir alias, like Foo, Foo.Bar and so on
|
||||
* `:alias` - a valid Elixir alias, like `Foo`, `Foo.Bar` and so on
|
||||
|
||||
* :callable_local - an atom that can be used as a local call;
|
||||
this category includes identifiers like :foo
|
||||
* `:callable_local` - an atom that can be used as a local call;
|
||||
this category includes identifiers like `:foo`
|
||||
|
||||
* :callable_operators - all callable operators, such as `:<>`. Note
|
||||
* `:callable_operators` - all callable operators, such as `:<>`. Note
|
||||
operators such as `:..` are not callable because of ambiguity
|
||||
|
||||
* :not_callable - an atom that cannot be used as a function call after the
|
||||
. operator (for example, :<<>> is not callable because Foo.<<>> is a
|
||||
syntax error); this category includes atoms like :Foo, since they are
|
||||
* `:not_callable` - an atom that cannot be used as a function call after the
|
||||
`.` operator (for example, `:<<>>` is not callable because `Foo.<<>>` is a
|
||||
syntax error); this category includes atoms like `:Foo`, since they are
|
||||
valid identifiers but they need quotes to be used in function calls
|
||||
(Foo."Bar")
|
||||
(`Foo."Bar"`)
|
||||
|
||||
* :other - any other atom (these are usually escaped when inspected, like
|
||||
:"foo and bar")
|
||||
* `:other` - any other atom (these are usually escaped when inspected, like
|
||||
`:"foo and bar"`)
|
||||
|
||||
"""
|
||||
def classify(atom) when is_atom(atom) do
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
defmodule Code.Typespec do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir quoted expression.
|
||||
"""
|
||||
@spec spec_to_quoted(atom, tuple) :: {atom, keyword, [Macro.t()]}
|
||||
def spec_to_quoted(name, spec)
|
||||
|
||||
def spec_to_quoted(name, {:type, line, :fun, [{:type, _, :product, args}, result]})
|
||||
when is_atom(name) do
|
||||
meta = [line: line]
|
||||
body = {name, meta, Enum.map(args, &typespec_to_quoted/1)}
|
||||
|
||||
vars =
|
||||
for type_expr <- args ++ [result],
|
||||
var <- collect_vars(type_expr),
|
||||
uniq: true,
|
||||
do: {var, {:var, meta, nil}}
|
||||
|
||||
spec = {:::, meta, [body, typespec_to_quoted(result)]}
|
||||
|
||||
if vars == [] do
|
||||
spec
|
||||
else
|
||||
{:when, meta, [spec, vars]}
|
||||
end
|
||||
end
|
||||
|
||||
def spec_to_quoted(name, {:type, line, :fun, []}) when is_atom(name) do
|
||||
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
|
||||
end
|
||||
|
||||
def spec_to_quoted(name, {:type, line, :bounded_fun, [type, constrs]}) when is_atom(name) do
|
||||
{:type, _, :fun, [{:type, _, :product, args}, result]} = type
|
||||
|
||||
guards =
|
||||
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constrs do
|
||||
{erl_to_ex_var(var), typespec_to_quoted(type)}
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
ignore_vars = Keyword.keys(guards)
|
||||
|
||||
vars =
|
||||
for type_expr <- args ++ [result],
|
||||
var <- collect_vars(type_expr),
|
||||
var not in ignore_vars,
|
||||
uniq: true,
|
||||
do: {var, {:var, meta, nil}}
|
||||
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
|
||||
when_args = [
|
||||
{:::, meta, [{name, [line: line], args}, typespec_to_quoted(result)]},
|
||||
guards ++ vars
|
||||
]
|
||||
|
||||
{:when, meta, when_args}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_quoted(type)
|
||||
|
||||
def type_to_quoted({{:record, record}, fields, args}) when is_atom(record) do
|
||||
fields = for field <- fields, do: typespec_to_quoted(field)
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
type = {:{}, [], [record | fields]}
|
||||
quote(do: unquote(record)(unquote_splicing(args)) :: unquote(type))
|
||||
end
|
||||
|
||||
def type_to_quoted({name, type, args}) when is_atom(name) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
quote(do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_quoted(type)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the module's BEAM code.
|
||||
|
||||
The result is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module must have a corresponding BEAM file which can be
|
||||
located by the runtime system. The types will be in the Erlang
|
||||
Abstract Format.
|
||||
"""
|
||||
@spec fetch_types(module | binary) :: {:ok, [tuple]} | :error
|
||||
def fetch_types(module) when is_atom(module) or is_binary(module) do
|
||||
case typespecs_abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
types =
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code,
|
||||
kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> {:opaque, type}
|
||||
{name, length(args)} in exported_types -> {:type, type}
|
||||
true -> {:typep, type}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, types}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the module's BEAM code.
|
||||
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module must have a corresponding BEAM file which can be
|
||||
located by the runtime system. The types will be in the Erlang
|
||||
Abstract Format.
|
||||
"""
|
||||
@spec fetch_specs(module) :: {:ok, [tuple]} | :error
|
||||
def fetch_specs(module) when is_atom(module) or is_binary(module) do
|
||||
case typespecs_abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
{:ok, for({:attribute, _, :spec, value} <- abstract_code, do: value)}
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the module's BEAM code.
|
||||
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module must have a corresponding BEAM file
|
||||
which can be located by the runtime system. The types will be
|
||||
in the Erlang Abstract Format.
|
||||
"""
|
||||
@spec fetch_callbacks(module) :: {:ok, [tuple]} | :error
|
||||
def fetch_callbacks(module) when is_atom(module) or is_binary(module) do
|
||||
case typespecs_abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
{:ok, for({:attribute, _, :callback, value} <- abstract_code, do: value)}
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Do not rely on abstract_code when OTP 20+ support is dropped (v1.8).
|
||||
# We should then be able to simplify this code and use `with`.
|
||||
defp typespecs_abstract_code(module) do
|
||||
case get_module_and_beam(module) do
|
||||
{module, binary} ->
|
||||
case :beam_lib.chunks(binary, [:debug_info]) do
|
||||
{:ok, {_, [debug_info: {:debug_info_v1, backend, data}]}} ->
|
||||
case data do
|
||||
{:elixir_v1, %{}, specs} ->
|
||||
# Fast path to avoid translation to Erlang from Elixir.
|
||||
{:ok, specs}
|
||||
|
||||
_ ->
|
||||
case backend.debug_info(:erlang_v1, module, data, []) do
|
||||
{:ok, abstract_code} -> {:ok, abstract_code}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
case :beam_lib.chunks(binary, [:abstract_code]) do
|
||||
{:ok, {_, [{:abstract_code, {_raw_abstract_v1, abstract_code}}]}} ->
|
||||
{:ok, abstract_code}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp get_module_and_beam(module) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{^module, beam, _filename} -> {module, beam}
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp get_module_and_beam(beam) when is_binary(beam) do
|
||||
case :beam_lib.info(beam) do
|
||||
[_ | _] = info -> {info[:module], beam}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp collect_vars({:ann_type, _line, args}) when is_list(args) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp collect_vars({:type, _line, _kind, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({:remote_type, _line, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({:typed_record_field, _line, type}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
defp collect_vars(_) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:user_type, line, name, args}) do
|
||||
typespec_to_quoted({:type, line, name, args})
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :tuple, args}) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
{:{}, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, _line, :list, [{:type, _, :union, unions} = arg]}) do
|
||||
case unpack_typespec_kw(unions, []) do
|
||||
{:ok, ast} -> ast
|
||||
:error -> [typespec_to_quoted(arg)]
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :list, []}) do
|
||||
{:list, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, _line, :list, [arg]}) do
|
||||
[typespec_to_quoted(arg)]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :nonempty_list, []}) do
|
||||
[{:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :nonempty_list, [arg]}) do
|
||||
[typespec_to_quoted(arg), {:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :map, :any}) do
|
||||
{:map, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :map, fields}) do
|
||||
fields =
|
||||
Enum.map(fields, fn
|
||||
{:type, _, :map_field_assoc, :any} ->
|
||||
{{:optional, [], [{:any, [], []}]}, {:any, [], []}}
|
||||
|
||||
{:type, _, :map_field_exact, [{:atom, _, k}, v]} ->
|
||||
{k, typespec_to_quoted(v)}
|
||||
|
||||
{:type, _, :map_field_exact, [k, v]} ->
|
||||
{{:required, [], [typespec_to_quoted(k)]}, typespec_to_quoted(v)}
|
||||
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{{:optional, [], [typespec_to_quoted(k)]}, typespec_to_quoted(v)}
|
||||
end)
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
map = {:%{}, [line: line], fields}
|
||||
|
||||
if struct do
|
||||
{:%, [line: line], [struct, map]}
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_quoted(arg)
|
||||
|
||||
case {typespec_to_quoted(arg1), typespec_to_quoted(arg2)} do
|
||||
{arg1, 0} ->
|
||||
quote(line: line, do: <<_::unquote(arg1)>>)
|
||||
|
||||
{0, arg2} ->
|
||||
quote(line: line, do: <<_::_*unquote(arg2)>>)
|
||||
|
||||
{arg1, arg2} ->
|
||||
quote(line: line, do: <<_::unquote(arg1), _::_*unquote(arg2)>>)
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :union, args}) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
Enum.reduce(Enum.reverse(args), fn arg, expr -> {:|, [line: line], [arg, expr]} end)
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :fun, [{:type, _, :product, args}, result]}) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
[{:->, [line: line], [args, typespec_to_quoted(result)]}]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :fun, [args, result]}) do
|
||||
[{:->, [line: line], [[typespec_to_quoted(args)], typespec_to_quoted(result)]}]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :fun, []}) do
|
||||
typespec_to_quoted({:type, line, :fun, [{:type, line, :any}, {:type, line, :any, []}]})
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :range, [left, right]}) do
|
||||
{:.., [line: line], [typespec_to_quoted(left), typespec_to_quoted(right)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, _line, nil, []}) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, name, args}) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
{name, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:var, line, var}) do
|
||||
{erl_to_ex_var(var), line, nil}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, line, op, arg}) do
|
||||
{op, [line: line], [typespec_to_quoted(arg)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:remote_type, line, [mod, name, args]}) do
|
||||
remote_type(line, mod, name, args)
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:ann_type, line, [var, type]}) do
|
||||
{:::, [line: line], [typespec_to_quoted(var), typespec_to_quoted(type)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted(
|
||||
{:typed_record_field, {:record_field, line, {:atom, line1, name}}, type}
|
||||
) do
|
||||
typespec_to_quoted({:ann_type, line, [{:var, line1, name}, type]})
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, _, :any}) do
|
||||
quote(do: ...)
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:paren_type, _, [type]}) do
|
||||
typespec_to_quoted(type)
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({type, _line, atom}) when is_atom(type) do
|
||||
atom
|
||||
end
|
||||
|
||||
defp typespec_to_quoted(other), do: other
|
||||
|
||||
## Helpers
|
||||
|
||||
defp remote_type(line, {:atom, _, :elixir}, {:atom, _, :charlist}, []) do
|
||||
typespec_to_quoted({:type, line, :charlist, []})
|
||||
end
|
||||
|
||||
defp remote_type(line, {:atom, _, :elixir}, {:atom, _, :nonempty_charlist}, []) do
|
||||
typespec_to_quoted({:type, line, :nonempty_charlist, []})
|
||||
end
|
||||
|
||||
defp remote_type(line, {:atom, _, :elixir}, {:atom, _, :struct}, []) do
|
||||
typespec_to_quoted({:type, line, :struct, []})
|
||||
end
|
||||
|
||||
defp remote_type(line, {:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]) do
|
||||
typespec_to_quoted({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
defp remote_type(line, {:atom, _, :elixir}, {:atom, _, :keyword}, args) do
|
||||
typespec_to_quoted({:type, line, :keyword, args})
|
||||
end
|
||||
|
||||
defp remote_type(line, mod, name, args) do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
dot = {:., [line: line], [typespec_to_quoted(mod), typespec_to_quoted(name)]}
|
||||
{dot, [line: line], args}
|
||||
end
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c::utf8, rest::binary>> ->
|
||||
String.to_atom("_#{String.downcase(<<c::utf8>>)}#{rest}")
|
||||
|
||||
<<c::utf8, rest::binary>> ->
|
||||
String.to_atom("#{String.downcase(<<c::utf8>>)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([{:type, _, :tuple, [{:atom, _, atom}, type]} | t], acc) do
|
||||
unpack_typespec_kw(t, [{atom, typespec_to_quoted(type)} | acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([], acc) do
|
||||
{:ok, Enum.reverse(acc)}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
:error
|
||||
end
|
||||
end
|
||||
@@ -90,11 +90,40 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def into(original) do
|
||||
def into(original) when is_binary(original) do
|
||||
fun = fn
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc | x]
|
||||
acc, :done -> IO.iodata_to_binary(acc)
|
||||
_, :halt -> :ok
|
||||
acc, {:cont, x} when is_binary(x) and is_list(acc) ->
|
||||
[acc | x]
|
||||
|
||||
acc, {:cont, x} when is_bitstring(x) and is_bitstring(acc) ->
|
||||
<<acc::bitstring, x::bitstring>>
|
||||
|
||||
acc, {:cont, x} when is_bitstring(x) ->
|
||||
<<IO.iodata_to_binary(acc)::bitstring, x::bitstring>>
|
||||
|
||||
acc, :done when is_bitstring(acc) ->
|
||||
acc
|
||||
|
||||
acc, :done ->
|
||||
IO.iodata_to_binary(acc)
|
||||
|
||||
_, :halt ->
|
||||
:ok
|
||||
end
|
||||
|
||||
{[original], fun}
|
||||
end
|
||||
|
||||
def into(original) when is_bitstring(original) do
|
||||
fun = fn
|
||||
acc, {:cont, x} when is_bitstring(x) ->
|
||||
<<acc::bitstring, x::bitstring>>
|
||||
|
||||
acc, :done ->
|
||||
acc
|
||||
|
||||
_, :halt ->
|
||||
:ok
|
||||
end
|
||||
|
||||
{original, fun}
|
||||
|
||||
+54
-3
@@ -1,6 +1,6 @@
|
||||
defmodule Dict do
|
||||
@moduledoc ~S"""
|
||||
WARNING: this module is deprecated.
|
||||
Generic API for dictionaries.
|
||||
|
||||
If you need a general dictionary, use the `Map` module.
|
||||
If you need to manipulate keyword lists, use `Keyword`.
|
||||
@@ -9,18 +9,26 @@ defmodule Dict do
|
||||
`new` function in the respective modules.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use Map or Keyword modules instead"
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type t :: list | map
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
message =
|
||||
"Use the Map module for working with maps or the Keyword module for working with keyword lists"
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
|
||||
@deprecated message
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
message = "Use maps and the Map module instead"
|
||||
|
||||
@deprecated message
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
@@ -28,6 +36,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def get_lazy(dict, key, fun) when is_function(fun, 0) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
@@ -35,12 +44,14 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def get_and_update(dict, key, fun) do
|
||||
current_value = get(dict, key)
|
||||
{get, new_value} = fun.(current_value)
|
||||
{get, put(dict, key, new_value)}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
@@ -48,10 +59,12 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def has_key?(dict, key) do
|
||||
match?({:ok, _}, fetch(dict, key))
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def put_new(dict, key, value) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
@@ -59,6 +72,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def put_new_lazy(dict, key, fun) when is_function(fun, 0) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
@@ -66,10 +80,12 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce(keys, dict, &delete(&2, &1))
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def take(dict, keys) do
|
||||
Enum.reduce(keys, new(), fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
@@ -79,24 +95,28 @@ defmodule Dict do
|
||||
end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn kv, acc -> {:cont, [kv | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn {k, _}, acc -> {:cont, [k | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn {_, v}, acc -> {:cont, [v | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def equal?(dict1, dict2) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
@@ -116,6 +136,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def merge(dict1, dict2, fun \\ fn _k, _v1, v2 -> v2 end) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
@@ -132,6 +153,7 @@ defmodule Dict do
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
@@ -142,6 +164,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
@@ -152,6 +175,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
@@ -162,6 +186,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def pop_lazy(dict, key, fun) when is_function(fun, 0) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
@@ -172,6 +197,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def split(dict, keys) do
|
||||
Enum.reduce(keys, {new(), dict}, fn key, {inc, exc} = acc ->
|
||||
case fetch(exc, key) do
|
||||
@@ -220,71 +246,85 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec keys(t) :: [key]
|
||||
def keys(dict) do
|
||||
target(dict).keys(dict)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec values(t) :: [value]
|
||||
def values(dict) do
|
||||
target(dict).values(dict)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(dict) do
|
||||
target(dict).size(dict)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(dict, key) do
|
||||
target(dict).has_key?(dict, key)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default \\ nil) do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec get_lazy(t, key, (() -> value)) :: value
|
||||
def get_lazy(dict, key, fun) do
|
||||
target(dict).get_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
|
||||
def get_and_update(dict, key, fun) do
|
||||
target(dict).get_and_update(dict, key, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(dict, key) do
|
||||
target(dict).fetch(dict, key)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(dict, key) do
|
||||
target(dict).fetch!(dict, key)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec put(t, key, value) :: t
|
||||
def put(dict, key, val) do
|
||||
target(dict).put(dict, key, val)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(dict, key, val) do
|
||||
target(dict).put_new(dict, key, val)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec put_new_lazy(t, key, (() -> value)) :: t
|
||||
def put_new_lazy(dict, key, fun) do
|
||||
target(dict).put_new_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec delete(t, key) :: t
|
||||
def delete(dict, key) do
|
||||
target(dict).delete(dict, key)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
@@ -297,6 +337,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict1, dict2, fun) do
|
||||
target1 = target(dict1)
|
||||
@@ -316,46 +357,55 @@ defmodule Dict do
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default \\ nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
|
||||
def pop_lazy(dict, key, fun) do
|
||||
target(dict).pop_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec update!(t, key, (value -> value)) :: t
|
||||
def update!(dict, key, fun) do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update(dict, key, initial, fun) do
|
||||
target(dict).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec drop(t, [key]) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec take(t, [key]) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
target(dict).empty(dict)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
@@ -379,6 +429,7 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
@spec to_list(t) :: list
|
||||
def to_list(dict) do
|
||||
target(dict).to_list(dict)
|
||||
|
||||
@@ -11,25 +11,31 @@ defmodule DynamicSupervisor do
|
||||
|
||||
## Examples
|
||||
|
||||
A dynamic supervisor is started with no children, only with the
|
||||
supervision strategy (the only strategy currently supported is
|
||||
`:one_for_one`):
|
||||
A dynamic supervisor is started with no children, often under a
|
||||
supervisor with the supervision strategy (the only strategy currently
|
||||
supported is `:one_for_one`) and a name:
|
||||
|
||||
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
|
||||
children = [
|
||||
{DynamicSupervisor, strategy: :one_for_one, name: MyApp.DynamicSupervisor}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
The options given in the child specification are documented in `start_link/1`.
|
||||
|
||||
Once the dynamic supervisor is running, we can start children
|
||||
with `start_child/2`, which receives a child specification:
|
||||
|
||||
{:ok, agent1} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
|
||||
{:ok, agent1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
|
||||
Agent.update(agent1, &Map.put(&1, :key, "value"))
|
||||
Agent.get(agent1, & &1)
|
||||
#=> %{key: "value"}
|
||||
|
||||
{:ok, agent2} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
|
||||
{:ok, agent2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
|
||||
Agent.get(agent2, & &1)
|
||||
#=> %{}
|
||||
|
||||
DynamicSupervisor.count_children(sup)
|
||||
DynamicSupervisor.count_children(MyApp.DynamicSupervisor)
|
||||
#=> %{active: 2, specs: 2, supervisors: 0, workers: 2}
|
||||
|
||||
## Module-based supervisors
|
||||
@@ -45,6 +51,7 @@ defmodule DynamicSupervisor do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
@@ -57,6 +64,67 @@ defmodule DynamicSupervisor do
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about these rules in the documentation for `GenServer`.
|
||||
|
||||
## Migrating from Supervisor's :simple_one_for_one
|
||||
|
||||
In case you were using the deprecated `:simple_one_for_one` strategy from
|
||||
the `Supervisor` module, you can migrate to the `DynamicSupervisor` in
|
||||
few steps.
|
||||
|
||||
Imagine the given "old" code:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_child(foo, bar, baz) do
|
||||
# This will start child by calling MyWorker.start_link(initial_arg, foo, bar, baz)
|
||||
Supervisor.start_child(__MODULE__, [foo, bar, baz])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
children = [
|
||||
# Or the deprecated: worker(MyWorker, [initial_arg])
|
||||
%{id: MyWorker, start: {MyWorker, :start_link, [initial_arg]})
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :simple_one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
It can be upgraded to the DynamicSupervisor like this:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_child(foo, bar, baz) do
|
||||
# If MyWorker is not using the new child specs, we need to pass a map:
|
||||
# spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
|
||||
spec = {MyWorker, foo: foo, bar: bar, baz: baz}
|
||||
DynamicSupervisor.start_child(__MODULE__, spec)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
DynamicSupervisor.init(
|
||||
strategy: :one_for_one,
|
||||
extra_arguments: [initial_arg]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
The difference is that the `DynamicSupervisor` expects the child specification
|
||||
at the moment `start_child/2` is called, and no longer on the init callback.
|
||||
If there are any initial arguments given on initialization, such as `[initial_arg]`,
|
||||
it can be given in the `:extra_arguments` flag on `DynamicSupervisor.init/1`.
|
||||
"""
|
||||
|
||||
@behaviour GenServer
|
||||
@@ -69,13 +137,14 @@ defmodule DynamicSupervisor do
|
||||
"""
|
||||
@callback init(args :: term) :: {:ok, sup_flags()} | :ignore
|
||||
|
||||
@opaque sup_flags() :: %{
|
||||
strategy: strategy(),
|
||||
intensity: non_neg_integer(),
|
||||
period: pos_integer(),
|
||||
max_children: non_neg_integer() | :infinity,
|
||||
extra_arguments: [term()]
|
||||
}
|
||||
@typedoc "The supervisor flags returned on init"
|
||||
@type sup_flags() :: %{
|
||||
strategy: strategy(),
|
||||
intensity: non_neg_integer(),
|
||||
period: pos_integer(),
|
||||
max_children: non_neg_integer() | :infinity,
|
||||
extra_arguments: [term()]
|
||||
}
|
||||
|
||||
@typedoc "Option values used by the `start*` functions"
|
||||
@type option :: {:name, Supervisor.name()} | init_option()
|
||||
@@ -94,6 +163,13 @@ defmodule DynamicSupervisor do
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :one_for_one
|
||||
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child ::
|
||||
{:ok, pid}
|
||||
| {:ok, pid, info :: term}
|
||||
| :ignore
|
||||
| {:error, {:already_started, pid} | :max_children | term}
|
||||
|
||||
defstruct [
|
||||
:args,
|
||||
:extra_arguments,
|
||||
@@ -104,17 +180,40 @@ defmodule DynamicSupervisor do
|
||||
:max_restarts,
|
||||
:max_seconds,
|
||||
children: %{},
|
||||
dynamic: 0,
|
||||
restarts: []
|
||||
]
|
||||
|
||||
@doc """
|
||||
Returns a specification to start a dynamic supervisor under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
@doc since: "1.6.1"
|
||||
def child_spec(opts) when is_list(opts) do
|
||||
id =
|
||||
case Keyword.get(opts, :name, DynamicSupervisor) do
|
||||
name when is_atom(name) -> name
|
||||
{:global, name} -> name
|
||||
{:via, _module, name} -> name
|
||||
end
|
||||
|
||||
%{
|
||||
id: id,
|
||||
start: {DynamicSupervisor, :start_link, [opts]},
|
||||
type: :supervisor
|
||||
}
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour DynamicSupervisor
|
||||
@opts unquote(opts)
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
@@ -122,13 +221,10 @@ defmodule DynamicSupervisor do
|
||||
type: :supervisor
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
Supervisor.child_spec(default, unquote(Macro.escape(opts)))
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@doc false
|
||||
def init(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -153,6 +249,7 @@ defmodule DynamicSupervisor do
|
||||
process and exits not only on crashes but also if the parent process exits
|
||||
with `:normal` reason.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec start_link(options) :: Supervisor.on_start()
|
||||
def start_link(options) when is_list(options) do
|
||||
keys = [:extra_arguments, :max_children, :max_seconds, :max_restarts, :strategy]
|
||||
@@ -178,6 +275,7 @@ defmodule DynamicSupervisor do
|
||||
name, the supported values are described in the "Name registration"
|
||||
section in the `GenServer` module docs.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec start_link(module, term, GenServer.options()) :: Supervisor.on_start()
|
||||
def start_link(mod, args, opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, {mod, args, opts[:name]}, opts)
|
||||
@@ -186,8 +284,9 @@ defmodule DynamicSupervisor do
|
||||
@doc """
|
||||
Dynamically adds a child specification to `supervisor` and starts that child.
|
||||
|
||||
`child_spec` should be a valid child specification. The child process will
|
||||
be started as defined in the child specification.
|
||||
`child_spec` should be a valid child specification as detailed in the
|
||||
"child_spec/1" section of the documentation for `Supervisor`. The child
|
||||
process will be started as defined in the child specification.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or `{:ok, child,
|
||||
info}`, then child specification and PID are added to the supervisor and
|
||||
@@ -205,8 +304,9 @@ defmodule DynamicSupervisor do
|
||||
of `:max_children` set on the supervisor initialization (see `init/1`), then
|
||||
this function returns `{:error, :max_children}`.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec start_child(Supervisor.supervisor(), :supervisor.child_spec() | {module, term} | module) ::
|
||||
Supervisor.on_start_child()
|
||||
on_start_child()
|
||||
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
|
||||
validate_and_start_child(supervisor, child_spec)
|
||||
end
|
||||
@@ -278,11 +378,12 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child identified by child id.
|
||||
Terminates the given child identified by `pid`.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no process with
|
||||
the given PID, this function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec terminate_child(Supervisor.supervisor(), pid) :: :ok | {:error, :not_found}
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
call(supervisor, {:terminate_child, pid})
|
||||
@@ -308,6 +409,7 @@ defmodule DynamicSupervisor do
|
||||
* `modules` - as defined in the child specification
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec which_children(Supervisor.supervisor()) :: [
|
||||
{:undefined, pid | :restarting, :worker | :supervisor, :supervisor.modules()}
|
||||
]
|
||||
@@ -320,7 +422,7 @@ defmodule DynamicSupervisor do
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - always 1 as dynamic supervisors have a single specification
|
||||
* `:specs` - the number of children processes
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by
|
||||
this supervisor
|
||||
@@ -332,6 +434,7 @@ defmodule DynamicSupervisor do
|
||||
is still alive
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec count_children(Supervisor.supervisor()) :: %{
|
||||
specs: non_neg_integer,
|
||||
active: non_neg_integer,
|
||||
@@ -342,6 +445,22 @@ defmodule DynamicSupervisor do
|
||||
call(supervisor, :count_children) |> :maps.from_list()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Synchronously stops the given supervisor with the given `reason`.
|
||||
|
||||
It returns `:ok` if the supervisor terminates with the given
|
||||
reason. If it terminates with another reason, the call exits.
|
||||
|
||||
This function keeps OTP semantics regarding error reporting.
|
||||
If the reason is any other than `:normal`, `:shutdown` or
|
||||
`{:shutdown, _}`, an error report is logged.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec stop(Supervisor.supervisor(), reason :: term, timeout) :: :ok
|
||||
def stop(supervisor, reason \\ :normal, timeout \\ :infinity) do
|
||||
GenServer.stop(supervisor, reason, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a set of options that initializes a dynamic supervisor.
|
||||
|
||||
@@ -374,7 +493,7 @@ defmodule DynamicSupervisor do
|
||||
|
||||
* `:max_children` - the maximum amount of children to be running
|
||||
under this supervisor at the same time. When `:max_children` is
|
||||
exceeded, `start_child/2` returns `{:error, :dynamic}`. Defaults
|
||||
exceeded, `start_child/2` returns `{:error, :max_children}`. Defaults
|
||||
to `:infinity`.
|
||||
|
||||
* `:extra_arguments` - arguments that are prepended to the arguments
|
||||
@@ -382,7 +501,8 @@ defmodule DynamicSupervisor do
|
||||
an empty list.
|
||||
|
||||
"""
|
||||
@spec init([init_option]) :: {:ok, map()}
|
||||
@doc since: "1.6.0"
|
||||
@spec init([init_option]) :: {:ok, sup_flags()}
|
||||
def init(options) when is_list(options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
@@ -413,7 +533,14 @@ defmodule DynamicSupervisor do
|
||||
|
||||
case mod.init(args) do
|
||||
{:ok, flags} when is_map(flags) ->
|
||||
state = %DynamicSupervisor{mod: mod, args: args, name: name || {self(), mod}}
|
||||
name =
|
||||
cond do
|
||||
is_nil(name) -> {self(), mod}
|
||||
is_atom(name) -> {:local, name}
|
||||
is_tuple(name) -> name
|
||||
end
|
||||
|
||||
state = %DynamicSupervisor{mod: mod, args: args, name: name}
|
||||
|
||||
case init(state, flags) do
|
||||
{:ok, state} -> {:ok, state}
|
||||
@@ -440,14 +567,15 @@ defmodule DynamicSupervisor do
|
||||
:ok <- validate_seconds(max_seconds),
|
||||
:ok <- validate_dynamic(max_children),
|
||||
:ok <- validate_extra_arguments(extra_arguments) do
|
||||
{:ok, %{
|
||||
state
|
||||
| extra_arguments: extra_arguments,
|
||||
max_children: max_children,
|
||||
max_restarts: max_restarts,
|
||||
max_seconds: max_seconds,
|
||||
strategy: strategy
|
||||
}}
|
||||
{:ok,
|
||||
%{
|
||||
state
|
||||
| extra_arguments: extra_arguments,
|
||||
max_children: max_children,
|
||||
max_restarts: max_restarts,
|
||||
max_seconds: max_seconds,
|
||||
strategy: strategy
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -528,10 +656,10 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
|
||||
def handle_call({:start_child, child}, _from, state) do
|
||||
%{dynamic: dynamic, max_children: max_children} = state
|
||||
%{children: children, max_children: max_children} = state
|
||||
|
||||
if dynamic < max_children do
|
||||
handle_start_child(child, %{state | dynamic: dynamic + 1})
|
||||
if map_size(children) < max_children do
|
||||
handle_start_child(child, state)
|
||||
else
|
||||
{:reply, {:error, :max_children}, state}
|
||||
end
|
||||
@@ -548,7 +676,7 @@ defmodule DynamicSupervisor do
|
||||
{:reply, reply, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
|
||||
_ ->
|
||||
{:reply, reply, update_in(state.dynamic, &(&1 - 1))}
|
||||
{:reply, reply, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -557,7 +685,7 @@ defmodule DynamicSupervisor do
|
||||
apply(m, f, a)
|
||||
catch
|
||||
kind, reason ->
|
||||
{:error, exit_reason(kind, reason, System.stacktrace())}
|
||||
{:error, exit_reason(kind, reason, __STACKTRACE__)}
|
||||
else
|
||||
{:ok, pid, extra} when is_pid(pid) -> {:ok, pid, extra}
|
||||
{:ok, pid} when is_pid(pid) -> {:ok, pid}
|
||||
@@ -567,14 +695,14 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
end
|
||||
|
||||
defp save_child(pid, {m, f, _}, :temporary, shutdown, type, modules, state) do
|
||||
put_in(state.children[pid], {{m, f, :undefined}, :temporary, shutdown, type, modules})
|
||||
end
|
||||
|
||||
defp save_child(pid, mfa, restart, shutdown, type, modules, state) do
|
||||
mfa = mfa_for_restart(mfa, restart)
|
||||
put_in(state.children[pid], {mfa, restart, shutdown, type, modules})
|
||||
end
|
||||
|
||||
defp mfa_for_restart({m, f, _}, :temporary), do: {m, f, :undefined}
|
||||
defp mfa_for_restart(mfa, _), do: mfa
|
||||
|
||||
defp exit_reason(:exit, reason, _), do: reason
|
||||
defp exit_reason(:error, reason, stack), do: {reason, stack}
|
||||
defp exit_reason(:throw, value, stack), do: {{:nocatch, value}, stack}
|
||||
@@ -791,9 +919,8 @@ defmodule DynamicSupervisor do
|
||||
{:ok, delete_child(pid, state)}
|
||||
end
|
||||
|
||||
defp delete_child(pid, state) do
|
||||
%{children: children, dynamic: dynamic} = state
|
||||
%{state | children: Map.delete(children, pid), dynamic: dynamic - 1}
|
||||
defp delete_child(pid, %{children: children} = state) do
|
||||
%{state | children: Map.delete(children, pid)}
|
||||
end
|
||||
|
||||
defp restart_child(pid, child, state) do
|
||||
@@ -817,8 +944,6 @@ defmodule DynamicSupervisor do
|
||||
defp add_restart(state) do
|
||||
%{max_seconds: max_seconds, max_restarts: max_restarts, restarts: restarts} = state
|
||||
|
||||
# The below is equivalent to 1 second. We avoid
|
||||
# :second because of incompatibilties with OTP < 20
|
||||
now = :erlang.monotonic_time(1)
|
||||
restarts = add_restart([now | restarts], now, max_seconds)
|
||||
state = %{state | restarts: restarts}
|
||||
@@ -836,8 +961,9 @@ defmodule DynamicSupervisor do
|
||||
|
||||
defp restart_child(:one_for_one, current_pid, child, state) do
|
||||
{{m, f, args} = mfa, restart, shutdown, type, modules} = child
|
||||
%{extra_arguments: extra} = state
|
||||
|
||||
case start_child(m, f, args) do
|
||||
case start_child(m, f, extra ++ args) do
|
||||
{:ok, pid, _} ->
|
||||
state = delete_child(current_pid, state)
|
||||
{:ok, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
@@ -856,21 +982,21 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
end
|
||||
|
||||
defp report_error(error, reason, pid, child, %{name: name}) do
|
||||
defp report_error(error, reason, pid, child, %{name: name, extra_arguments: extra}) do
|
||||
:error_logger.error_report(
|
||||
:supervision_report,
|
||||
:supervisor_report,
|
||||
supervisor: name,
|
||||
errorContext: error,
|
||||
reason: reason,
|
||||
offender: extract_child(pid, child)
|
||||
offender: extract_child(pid, child, extra)
|
||||
)
|
||||
end
|
||||
|
||||
defp extract_child(pid, {mfa, restart, shutdown, type, _modules}) do
|
||||
defp extract_child(pid, {{m, f, args}, restart, shutdown, type, _modules}, extra) do
|
||||
[
|
||||
pid: pid,
|
||||
id: :undefined,
|
||||
mfargs: mfa,
|
||||
mfargs: {m, f, extra ++ args},
|
||||
restart_type: restart,
|
||||
shutdown: shutdown,
|
||||
child_type: type
|
||||
|
||||
+149
-118
@@ -131,10 +131,10 @@ defprotocol Enumerable do
|
||||
|
||||
As an example, here is the implementation of `reduce` for lists:
|
||||
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
def reduce([h | t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)
|
||||
|
||||
"""
|
||||
@spec reduce(t, acc, reducer) :: result
|
||||
@@ -156,7 +156,7 @@ defprotocol Enumerable do
|
||||
Checks if an element exists within the enumerable.
|
||||
|
||||
It should return `{:ok, boolean}` if you can check the membership of a
|
||||
given element in the enumerable with `===` without traversing the whole
|
||||
given element in the enumerable with `===/2` without traversing the whole
|
||||
enumerable.
|
||||
|
||||
Otherwise it should return `{:error, __MODULE__}` and a default algorithm
|
||||
@@ -197,30 +197,51 @@ defmodule Enum do
|
||||
import Kernel, except: [max: 2, min: 2]
|
||||
|
||||
@moduledoc """
|
||||
Provides a set of algorithms that enumerate over enumerables according
|
||||
to the `Enumerable` protocol.
|
||||
Provides a set of algorithms to work with enumerables.
|
||||
|
||||
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
In Elixir, an enumerable is any data type that implements the
|
||||
`Enumerable` protocol. `List`s (`[1, 2, 3]`), `Map`s (`%{foo: 1, bar: 2}`)
|
||||
and `Range`s (`1..3`) are common data types used as enumerables:
|
||||
|
||||
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
|
||||
[2, 4, 6]
|
||||
|
||||
Some particular types, like maps, yield a specific format on enumeration.
|
||||
For example, the argument is always a `{key, value}` tuple for maps:
|
||||
iex> Enum.sum([1, 2, 3])
|
||||
6
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> Enum.map(1..3, fn x -> x * 2 end)
|
||||
[2, 4, 6]
|
||||
|
||||
iex> Enum.sum(1..3)
|
||||
6
|
||||
|
||||
iex> map = %{"a" => 1, "b" => 2}
|
||||
iex> Enum.map(map, fn {k, v} -> {k, v * 2} end)
|
||||
[a: 2, b: 4]
|
||||
[{"a", 2}, {"b", 4}]
|
||||
|
||||
Note that the functions in the `Enum` module are eager: they always
|
||||
start the enumeration of the given enumerable. The `Stream` module
|
||||
allows lazy enumeration of enumerables and provides infinite streams.
|
||||
However, many other enumerables exist in the language, such as `MapSet`s
|
||||
and the data type returned by `File.stream!/3` which allows a file to be
|
||||
traversed as if it was an enumerable.
|
||||
|
||||
Since the majority of the functions in `Enum` enumerate the whole
|
||||
enumerable and return a list as result, infinite streams need to
|
||||
be carefully used with such functions, as they can potentially run
|
||||
forever. For example:
|
||||
The functions in this module work in linear time. This means that,
|
||||
the larger the enumerable, the longer it will take to perform the desired
|
||||
operation. This is expected on operations such as `Enum.map/2`. After all,
|
||||
if we want to traverse every element on a list, the longer the list, the
|
||||
more elements we need to traverse, and the longer it will take.
|
||||
|
||||
Enum.each Stream.cycle([1, 2, 3]), &IO.puts(&1)
|
||||
This linear behaviour should also be expected on operations like `count/1`,
|
||||
`member?/2`, `at/2` and similar. While Elixir does allow data types to
|
||||
provide performant variants for such operations, you should not expect it
|
||||
to always be available, since the `Enum` module is meant to work with a
|
||||
large variety of data types and not all data types can provide optimized
|
||||
behaviour.
|
||||
|
||||
Finally, note the functions in the `Enum` module are eager: they will
|
||||
traverse the enumerable as soon as they are invoked. This is particularly
|
||||
dangerous when working with infinite enumerables. In such cases, you should
|
||||
use the `Stream` module, which allows you to lazily express computations,
|
||||
without traversing collections, and work with possibly infinite collections.
|
||||
See the `Stream` module for examples and documentation.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -231,7 +252,6 @@ defmodule Enum do
|
||||
@type index :: integer
|
||||
@type default :: any
|
||||
|
||||
# Require Stream.Reducers and its callbacks
|
||||
require Stream.Reducers, as: R
|
||||
|
||||
defmacrop skip(acc) do
|
||||
@@ -253,16 +273,16 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given `fun` evaluates to true on all of the items in the enumerable.
|
||||
Returns `true` if the given `fun` evaluates to true on all of the items in the enumerable.
|
||||
|
||||
It stops the iteration at the first invocation that returns `false` or `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.all?([2, 4, 6], fn x -> rem(x, 2) == 0 end)
|
||||
true
|
||||
|
||||
iex> Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.all?([2, 3, 4], fn x -> rem(x, 2) == 0 end)
|
||||
false
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
@@ -291,16 +311,16 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given `fun` evaluates to true on any of the items in the enumerable.
|
||||
Returns `true` if the given `fun` evaluates to true on any of the items in the enumerable.
|
||||
|
||||
It stops the iteration at the first invocation that returns a truthy value (not `false` or `nil`).
|
||||
It stops the iteration at the first invocation that returns a truthy value (neither `false` nor `nil`).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.any?([2, 4, 6], fn x -> rem(x, 2) == 1 end)
|
||||
false
|
||||
|
||||
iex> Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.any?([2, 3, 4], fn x -> rem(x, 2) == 1 end)
|
||||
true
|
||||
|
||||
If no function is given, it defaults to checking if at least one item
|
||||
@@ -337,10 +357,6 @@ defmodule Enum do
|
||||
enumerated once and the `index` is counted from the end (e.g.
|
||||
`-1` finds the last element).
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `index`, it will need to traverse `index`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
@@ -364,19 +380,29 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecate on v1.7
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Enum.chunk_every/2 instead"
|
||||
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
|
||||
|
||||
# Deprecate on v1.7
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
def chunk(enumerable, count, step, leftover \\ nil) do
|
||||
@deprecated "Use Enum.chunk_every/3 instead"
|
||||
def chunk(enum, n, step) do
|
||||
chunk_every(enum, n, step, nil)
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Enum.chunk_every/4 instead"
|
||||
def chunk(enumerable, count, step, leftover) do
|
||||
chunk_every(enumerable, count, step, leftover || :discard)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk_every(enumerable, count, count)`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_every(t, pos_integer) :: [list]
|
||||
def chunk_every(enumerable, count), do: chunk_every(enumerable, count, count, [])
|
||||
|
||||
@@ -416,6 +442,7 @@ defmodule Enum do
|
||||
[[1, 2], [4, 5]]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_every(t, pos_integer, pos_integer, t | :discard) :: [list]
|
||||
def chunk_every(enumerable, count, step, leftover \\ [])
|
||||
when is_integer(count) and count > 0 and is_integer(step) and step > 0 do
|
||||
@@ -437,11 +464,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> chunk_fun = fn i, acc ->
|
||||
...> if rem(i, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([i | acc]), []}
|
||||
iex> chunk_fun = fn item, acc ->
|
||||
...> if rem(item, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([item | acc]), []}
|
||||
...> else
|
||||
...> {:cont, [i | acc]}
|
||||
...> {:cont, [item | acc]}
|
||||
...> end
|
||||
...> end
|
||||
iex> after_fun = fn
|
||||
@@ -452,6 +479,7 @@ defmodule Enum do
|
||||
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_while(
|
||||
t,
|
||||
acc,
|
||||
@@ -566,7 +594,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.count([1, 2, 3, 4, 5], fn x -> rem(x, 2) == 0 end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -581,7 +609,7 @@ defmodule Enum do
|
||||
Enumerates the `enumerable`, returning a list where all consecutive
|
||||
duplicated elements are collapsed to a single element.
|
||||
|
||||
Elements are compared using `===`.
|
||||
Elements are compared using `===/2`.
|
||||
|
||||
If you want to remove all duplicated elements, regardless of order,
|
||||
see `uniq/1`.
|
||||
@@ -591,7 +619,7 @@ defmodule Enum do
|
||||
iex> Enum.dedup([1, 2, 3, 3, 2, 1])
|
||||
[1, 2, 3, 2, 1]
|
||||
|
||||
iex> Enum.dedup([1, 1, 2, 2.0, :three, :"three"])
|
||||
iex> Enum.dedup([1, 1, 2, 2.0, :three, :three])
|
||||
[1, 2, 2.0, :three]
|
||||
|
||||
"""
|
||||
@@ -705,7 +733,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.drop_while([1, 2, 3, 2, 1], fn(x) -> x < 3 end)
|
||||
iex> Enum.drop_while([1, 2, 3, 2, 1], fn x -> x < 3 end)
|
||||
[3, 2, 1]
|
||||
|
||||
"""
|
||||
@@ -787,10 +815,6 @@ defmodule Enum do
|
||||
enumerated once and the `index` is counted from the end (e.g.
|
||||
`-1` fetches the last element).
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `index`, it will need to traverse `index`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
@@ -820,9 +844,6 @@ defmodule Enum do
|
||||
Raises `OutOfBoundsError` if the given `index` is outside the range of
|
||||
the enumerable.
|
||||
|
||||
Note this operation takes linear time. In order to access the element
|
||||
at index `index`, it will need to traverse `index` previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch!([2, 4, 6], 0)
|
||||
@@ -848,11 +869,11 @@ defmodule Enum do
|
||||
for which `fun` returns a truthy value.
|
||||
|
||||
See also `reject/2` which discards all elements where the
|
||||
function returns true.
|
||||
function a truthy value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
|
||||
[2]
|
||||
|
||||
Keep in mind that `filter` is not capable of filtering and
|
||||
@@ -881,7 +902,7 @@ defmodule Enum do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Enum.filter/2 + Enum.map/2 or for comprehensions instead"
|
||||
def filter_map(enumerable, filter, mapper) when is_list(enumerable) do
|
||||
for item <- enumerable, filter.(item), do: mapper.(item)
|
||||
end
|
||||
@@ -898,13 +919,13 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 4, 6], fn x -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 4, 6], 0, fn x -> rem(x, 2) == 1 end)
|
||||
0
|
||||
|
||||
iex> Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 3, 4], fn x -> rem(x, 2) == 1 end)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -928,10 +949,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_index([2, 4, 6], fn x -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_index([2, 3, 4], fn x -> rem(x, 2) == 1 end)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -958,10 +979,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_value([2, 4, 6], fn x -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_value([2, 3, 4], fn x -> rem(x, 2) == 1 end)
|
||||
true
|
||||
|
||||
iex> Enum.find_value([1, 2, 3], "no bools!", &is_boolean/1)
|
||||
@@ -992,13 +1013,13 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.flat_map([:a, :b, :c], fn(x) -> [x, x] end)
|
||||
iex> Enum.flat_map([:a, :b, :c], fn x -> [x, x] end)
|
||||
[:a, :a, :b, :b, :c, :c]
|
||||
|
||||
iex> Enum.flat_map([{1, 3}, {4, 6}], fn({x, y}) -> x..y end)
|
||||
iex> Enum.flat_map([{1, 3}, {4, 6}], fn {x, y} -> x..y end)
|
||||
[1, 2, 3, 4, 5, 6]
|
||||
|
||||
iex> Enum.flat_map([:a, :b, :c], fn(x) -> [[x]] end)
|
||||
iex> Enum.flat_map([:a, :b, :c], fn x -> [[x]] end)
|
||||
[[:a], [:b], [:c]]
|
||||
|
||||
"""
|
||||
@@ -1029,12 +1050,12 @@ defmodule Enum do
|
||||
|
||||
iex> enumerable = 1..100
|
||||
iex> n = 3
|
||||
iex> Enum.flat_map_reduce(enumerable, 0, fn i, acc ->
|
||||
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
|
||||
iex> Enum.flat_map_reduce(enumerable, 0, fn x, acc ->
|
||||
...> if acc < n, do: {[x], acc + 1}, else: {:halt, acc}
|
||||
...> end)
|
||||
{[1, 2, 3], 3}
|
||||
|
||||
iex> Enum.flat_map_reduce(1..5, 0, fn(i, acc) -> {[[i]], acc + i} end)
|
||||
iex> Enum.flat_map_reduce(1..5, 0, fn x, acc -> {[[x]], acc + x} end)
|
||||
{[[1], [2], [3], [4], [5]], 15}
|
||||
|
||||
"""
|
||||
@@ -1229,9 +1250,8 @@ defmodule Enum do
|
||||
reduce(enumerable, initial, callback)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
acc -> fun.(acc, :done)
|
||||
end
|
||||
@@ -1280,10 +1300,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
|
||||
[2, 4, 6]
|
||||
|
||||
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
|
||||
iex> Enum.map([a: 1, b: 2], fn {k, v} -> {k, -v} end)
|
||||
[a: -1, b: -2]
|
||||
|
||||
"""
|
||||
@@ -1325,6 +1345,7 @@ defmodule Enum do
|
||||
[1001, 1002, 1003]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec map_every(t, non_neg_integer, (element -> any)) :: list
|
||||
def map_every(enumerable, nth, fun)
|
||||
|
||||
@@ -1388,7 +1409,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> {x * 2, x + acc} end)
|
||||
iex> Enum.map_reduce([1, 2, 3], 0, fn x, acc -> {x * 2, x + acc} end)
|
||||
{[2, 4, 6], 6}
|
||||
|
||||
"""
|
||||
@@ -1441,6 +1462,8 @@ defmodule Enum do
|
||||
iex> Enum.max_by([~D[2017-03-31], ~D[2017-04-01]], &Date.to_erl/1)
|
||||
~D[2017-04-01]
|
||||
|
||||
For selecting a maximum value out of two consider using `Kernel.max/2`.
|
||||
|
||||
"""
|
||||
@spec max(t, (() -> empty_result)) :: element | empty_result | no_return when empty_result: any
|
||||
def max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) do
|
||||
@@ -1459,7 +1482,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.max_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
|
||||
iex> Enum.max_by(["a", "aa", "aaa"], fn x -> String.length(x) end)
|
||||
"aaa"
|
||||
|
||||
iex> Enum.max_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
|
||||
@@ -1488,7 +1511,7 @@ defmodule Enum do
|
||||
@doc """
|
||||
Checks if `element` exists within the enumerable.
|
||||
|
||||
Membership is tested with the match (`===`) operator.
|
||||
Membership is tested with the match (`===/2`) operator.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1559,6 +1582,8 @@ defmodule Enum do
|
||||
iex> Enum.min_by([~D[2017-03-31], ~D[2017-04-01]], &Date.to_erl/1)
|
||||
~D[2017-03-31]
|
||||
|
||||
For selecting a minimal value out of two consider using `Kernel.min/2`.
|
||||
|
||||
"""
|
||||
@spec min(t, (() -> empty_result)) :: element | empty_result | no_return when empty_result: any
|
||||
def min(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) do
|
||||
@@ -1577,7 +1602,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.min_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
|
||||
iex> Enum.min_by(["a", "aa", "aaa"], fn x -> String.length(x) end)
|
||||
"a"
|
||||
|
||||
iex> Enum.min_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
|
||||
@@ -1626,8 +1651,8 @@ defmodule Enum do
|
||||
when empty_result: any
|
||||
def min_max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end)
|
||||
|
||||
def min_max(left..right, _empty_fallback) do
|
||||
{Kernel.min(left, right), Kernel.max(left, right)}
|
||||
def min_max(first..last, _empty_fallback) do
|
||||
{Kernel.min(first, last), Kernel.max(first, last)}
|
||||
end
|
||||
|
||||
def min_max(enumerable, empty_fallback) do
|
||||
@@ -1655,7 +1680,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.min_max_by(["aaa", "bb", "c"], fn(x) -> String.length(x) end)
|
||||
iex> Enum.min_max_by(["aaa", "bb", "c"], fn x -> String.length(x) end)
|
||||
{"c", "aaa"}
|
||||
|
||||
iex> Enum.min_max_by(["aaa", "a", "bb", "c", "ccc"], &String.length/1)
|
||||
@@ -1712,19 +1737,20 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
|
||||
{[4, 2, 0], [5, 3, 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn({_k, v}) -> v < 0 end)
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v < 0 end)
|
||||
{[b: -2, d: -3], [a: 1, c: 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn({_k, v}) -> v > 50 end)
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v > 50 end)
|
||||
{[], [a: 1, b: -2, c: 1, d: -3]}
|
||||
|
||||
iex> Enum.split_with(%{}, fn({_k, v}) -> v > 50 end)
|
||||
iex> Enum.split_with(%{}, fn {_k, v} -> v > 50 end)
|
||||
{[], []}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec split_with(t, (element -> any)) :: {list, list}
|
||||
def split_with(enumerable, fun) do
|
||||
{acc1, acc2} =
|
||||
@@ -1741,7 +1767,7 @@ defmodule Enum do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Enum.split_with/2 instead"
|
||||
def partition(enumerable, fun) do
|
||||
split_with(enumerable, fun)
|
||||
end
|
||||
@@ -1828,7 +1854,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.reduce([1, 2, 3, 4], fn(x, acc) -> x * acc end)
|
||||
iex> Enum.reduce([1, 2, 3, 4], fn x, acc -> x * acc end)
|
||||
24
|
||||
|
||||
"""
|
||||
@@ -1867,7 +1893,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
|
||||
iex> Enum.reduce([1, 2, 3], 0, fn x, acc -> x + acc end)
|
||||
6
|
||||
|
||||
## Reduce as a building block
|
||||
@@ -1908,7 +1934,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def reduce(%_{} = enumerable, acc, fun) do
|
||||
Enumerable.reduce(enumerable, {:cont, acc}, fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
reduce_enumerable(enumerable, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(%{} = enumerable, acc, fun) do
|
||||
@@ -1916,7 +1942,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def reduce(enumerable, acc, fun) do
|
||||
Enumerable.reduce(enumerable, {:cont, acc}, fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
reduce_enumerable(enumerable, acc, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1931,8 +1957,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.reduce_while(1..100, 0, fn i, acc ->
|
||||
...> if i < 3, do: {:cont, acc + i}, else: {:halt, acc}
|
||||
iex> Enum.reduce_while(1..100, 0, fn x, acc ->
|
||||
...> if x < 3, do: {:cont, acc + x}, else: {:halt, acc}
|
||||
...> end)
|
||||
3
|
||||
|
||||
@@ -1943,14 +1969,14 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns elements of `enumerable` for which the function `fun` returns
|
||||
`false` or `nil`.
|
||||
Returns a list of elements in `enumerable` excluding those for which the function `fun` returns
|
||||
a truthy value.
|
||||
|
||||
See also `filter/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.reject([1, 2, 3], fn x -> rem(x, 2) == 0 end)
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@@ -1982,11 +2008,11 @@ defmodule Enum do
|
||||
def reverse(enumerable), do: reduce(enumerable, [], &[&1 | &2])
|
||||
|
||||
@doc """
|
||||
Reverses the elements in `enumerable`, appends the tail, and returns
|
||||
Reverses the elements in `enumerable`, appends the `tail`, and returns
|
||||
it as a list.
|
||||
|
||||
This is an optimization for
|
||||
`Enum.concat(Enum.reverse(enumerable), tail)`.
|
||||
`enumerable |> Enum.reverse() |> Enum.concat(tail)`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2132,6 +2158,7 @@ defmodule Enum do
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec slice(t, Range.t()) :: list
|
||||
def slice(enumerable, first..last) do
|
||||
{count, fun} = slice_count_and_fun(enumerable)
|
||||
@@ -2220,14 +2247,14 @@ defmodule Enum do
|
||||
The sorting algorithm will be stable as long as the given function
|
||||
returns `true` for values considered equal:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2))
|
||||
iex> Enum.sort(["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2)))
|
||||
["of", "some", "kind", "monster"]
|
||||
|
||||
If the function does not return `true` for equal values, the sorting
|
||||
is not stable and the order of equal terms may be shuffled.
|
||||
For example:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2))
|
||||
iex> Enum.sort(["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2)))
|
||||
["of", "kind", "some", "monster"]
|
||||
|
||||
"""
|
||||
@@ -2342,7 +2369,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn x -> x < 3 end)
|
||||
{[1, 2], [3, 4]}
|
||||
|
||||
"""
|
||||
@@ -2548,7 +2575,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.take_while([1, 2, 3], fn(x) -> x < 3 end)
|
||||
iex> Enum.take_while([1, 2, 3], fn x -> x < 3 end)
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@@ -2580,13 +2607,10 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: [element]
|
||||
def to_list(enumerable) when is_list(enumerable) do
|
||||
enumerable
|
||||
end
|
||||
|
||||
def to_list(enumerable) do
|
||||
reverse(enumerable) |> :lists.reverse()
|
||||
end
|
||||
def to_list(enumerable) when is_list(enumerable), do: enumerable
|
||||
def to_list(%_{} = enumerable), do: reverse(enumerable) |> :lists.reverse()
|
||||
def to_list(%{} = enumerable), do: Map.to_list(enumerable)
|
||||
def to_list(enumerable), do: reverse(enumerable) |> :lists.reverse()
|
||||
|
||||
@doc """
|
||||
Enumerates the `enumerable`, removing all duplicated elements.
|
||||
@@ -2604,7 +2628,7 @@ defmodule Enum do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Enum.uniq_by/2 instead"
|
||||
def uniq(enumerable, fun) do
|
||||
uniq_by(enumerable, fun)
|
||||
end
|
||||
@@ -2718,10 +2742,10 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Zips corresponding elements from a list of enumerables
|
||||
Zips corresponding elements from a finite collection of enumerables
|
||||
into one list of tuples.
|
||||
|
||||
The zipping finishes as soon as any enumerable in the given list completes.
|
||||
The zipping finishes as soon as any enumerable in the given collection completes.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2732,11 +2756,13 @@ defmodule Enum do
|
||||
[{1, :a}, {2, :b}, {3, :c}]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec zip([t]) :: t
|
||||
@spec zip(t) :: t
|
||||
|
||||
def zip([]), do: []
|
||||
|
||||
def zip(enumerables) when is_list(enumerables) do
|
||||
def zip(enumerables) do
|
||||
Stream.zip(enumerables).({:cont, []}, &{:cont, [&1 | &2]})
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
@@ -2744,7 +2770,8 @@ defmodule Enum do
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, aggregate: 3, entry_to_string: 1, reduce: 3, reduce_by: 3}
|
||||
@compile {:inline,
|
||||
aggregate: 3, entry_to_string: 1, reduce: 3, reduce_by: 3, reduce_enumerable: 3}
|
||||
|
||||
defp entry_to_string(entry) when is_binary(entry), do: entry
|
||||
defp entry_to_string(entry), do: String.Chars.to_string(entry)
|
||||
@@ -2757,8 +2784,8 @@ defmodule Enum do
|
||||
empty.()
|
||||
end
|
||||
|
||||
defp aggregate(left..right, fun, _empty) do
|
||||
fun.(left, right)
|
||||
defp aggregate(first..last, fun, _empty) do
|
||||
fun.(first, last)
|
||||
end
|
||||
|
||||
defp aggregate(enumerable, fun, empty) do
|
||||
@@ -2802,13 +2829,13 @@ defmodule Enum do
|
||||
lower_limit + :rand.uniform(upper_limit - lower_limit + 1) - 1
|
||||
end
|
||||
|
||||
# TODO: Remove me on Elixir v1.8
|
||||
# TODO: Remove me on Elixir v1.9
|
||||
defp backwards_compatible_slice(args) do
|
||||
try do
|
||||
Enumerable.slice(args)
|
||||
catch
|
||||
:error, :undef ->
|
||||
case System.stacktrace() do
|
||||
case __STACKTRACE__ do
|
||||
[{module, :slice, [^args], _} | _] -> {:error, module}
|
||||
stack -> :erlang.raise(:error, :undef, stack)
|
||||
end
|
||||
@@ -2948,6 +2975,10 @@ defmodule Enum do
|
||||
reduce_range_dec(first - 1, last, fun.(first, acc), fun)
|
||||
end
|
||||
|
||||
defp reduce_enumerable(enumerable, acc, fun) do
|
||||
Enumerable.reduce(enumerable, {:cont, acc}, fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
end
|
||||
|
||||
## reject
|
||||
|
||||
defp reject_list([head | tail], fun) do
|
||||
@@ -3244,16 +3275,16 @@ defimpl Enumerable, for: List do
|
||||
def member?(_list, _value), do: {:error, __MODULE__}
|
||||
def slice(_list), do: {:error, __MODULE__}
|
||||
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
def reduce([h | t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)
|
||||
|
||||
@doc false
|
||||
def slice([], _start, _count), do: []
|
||||
def slice(_list, _start, 0), do: []
|
||||
def slice([head | tail], 0, count), do: [head | slice(tail, 0, count - 1)]
|
||||
def slice([_ | tail], start, count), do: slice(tail, start - 1, count)
|
||||
def slice([_head | tail], start, count), do: slice(tail, start - 1, count)
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Map do
|
||||
@@ -3277,10 +3308,10 @@ defimpl Enumerable, for: Map do
|
||||
reduce_list(:maps.to_list(map), acc, fun)
|
||||
end
|
||||
|
||||
defp reduce_list(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
defp reduce_list(_list, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
defp reduce_list(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce_list(list, &1, fun)}
|
||||
defp reduce_list([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
defp reduce_list([h | t], {:cont, acc}, fun), do: reduce_list(t, fun.(h, acc), fun)
|
||||
defp reduce_list([head | tail], {:cont, acc}, fun), do: reduce_list(tail, fun.(head, acc), fun)
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
|
||||
+250
-119
@@ -2,10 +2,8 @@ defmodule Exception do
|
||||
@moduledoc """
|
||||
Functions to format throw/catch/exit and exceptions.
|
||||
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace/0` will return the stacktrace for the
|
||||
last throw/error/exit that occurred in the current process.
|
||||
Note that stacktraces in Elixir are only available inside
|
||||
catch and rescue by using the `__STACKTRACE__/0` variable.
|
||||
|
||||
Do not rely on the particular format returned by the `format*`
|
||||
functions in this module. They may be changed in future releases
|
||||
@@ -82,31 +80,16 @@ defmodule Exception do
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
The third argument is the stacktrace which is used to enrich
|
||||
a normalized error with more information. It is only used when
|
||||
the kind is an error.
|
||||
"""
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(non_error_kind, payload, stacktrace) :: payload when payload: var
|
||||
|
||||
# Generating a stacktrace is expensive, default to nil
|
||||
# to only fetch it when needed.
|
||||
def normalize(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def normalize(:error, exception, stacktrace) do
|
||||
if exception?(exception) do
|
||||
exception
|
||||
else
|
||||
ErlangError.normalize(exception, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
def normalize(_kind, payload, _stacktrace) do
|
||||
payload
|
||||
end
|
||||
def normalize(kind, payload, stacktrace \\ [])
|
||||
def normalize(:error, %_{__exception__: true} = payload, _stacktrace), do: payload
|
||||
def normalize(:error, payload, stacktrace), do: ErlangError.normalize(payload, stacktrace)
|
||||
def normalize(_kind, payload, _stacktrace), do: payload
|
||||
|
||||
@doc """
|
||||
Normalizes and formats any throw/error/exit.
|
||||
@@ -114,15 +97,12 @@ defmodule Exception do
|
||||
The message is formatted and displayed in the same
|
||||
format as used by Elixir's CLI.
|
||||
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
The third argument is the stacktrace which is used to enrich
|
||||
a normalized error with more information. It is only used when
|
||||
the kind is an error.
|
||||
"""
|
||||
@spec format_banner(kind, any, stacktrace | nil) :: String.t()
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
@spec format_banner(kind, any, stacktrace) :: String.t()
|
||||
def format_banner(kind, exception, stacktrace \\ [])
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
exception = normalize(:error, exception, stacktrace)
|
||||
@@ -147,18 +127,17 @@ defmodule Exception do
|
||||
It relies on `format_banner/3` and `format_stacktrace/1`
|
||||
to generate the final format.
|
||||
|
||||
Note that `{:EXIT, pid}` do not generate a stacktrace though
|
||||
(as they are retrieved as messages without stacktraces).
|
||||
If `kind` is `{:EXIT, pid}`, it does not generate a stacktrace,
|
||||
as such exits are retrieved as messages without stacktraces.
|
||||
"""
|
||||
@spec format(kind, any, stacktrace | nil) :: String.t()
|
||||
def format(kind, payload, stacktrace \\ nil)
|
||||
@spec format(kind, any, stacktrace) :: String.t()
|
||||
def format(kind, payload, stacktrace \\ [])
|
||||
|
||||
def format({:EXIT, _} = kind, any, _) do
|
||||
format_banner(kind, any)
|
||||
end
|
||||
|
||||
def format(kind, payload, stacktrace) do
|
||||
stacktrace = stacktrace || System.stacktrace()
|
||||
message = format_banner(kind, payload, stacktrace)
|
||||
|
||||
case stacktrace do
|
||||
@@ -171,12 +150,13 @@ defmodule Exception do
|
||||
Attaches information to exceptions for extra debugging.
|
||||
|
||||
This operation is potentially expensive, as it reads data
|
||||
from the filesystem, parse beam files, evaluates code and
|
||||
from the filesystem, parses beam files, evaluates code and
|
||||
so on.
|
||||
|
||||
If the exception module implements the optional `c:blame/2`
|
||||
callbak, it will be invoked to perform the computation.
|
||||
callback, it will be invoked to perform the computation.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec blame(:error, any, stacktrace) :: {t, stacktrace}
|
||||
@spec blame(non_error_kind, payload, stacktrace) :: {payload, stacktrace} when payload: var
|
||||
def blame(kind, error, stacktrace)
|
||||
@@ -209,6 +189,7 @@ defmodule Exception do
|
||||
Note this functionality requires Erlang/OTP 20, otherwise `:error`
|
||||
is always returned.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec blame_mfa(module, function, args :: [term]) ::
|
||||
{:ok, :def | :defp | :defmacro | :defmacrop, [{args :: [term], guards :: [term]}]}
|
||||
| :error
|
||||
@@ -229,7 +210,7 @@ defmodule Exception do
|
||||
{_, kind, _, clauses} <- List.keyfind(defs, {function, arity}, 0) do
|
||||
clauses =
|
||||
for {meta, ex_args, guards, _block} <- clauses do
|
||||
scope = :elixir_erl.definition_scope(meta, "nofile")
|
||||
scope = :elixir_erl.scope(meta)
|
||||
|
||||
{erl_args, scope} =
|
||||
:elixir_erl_clauses.match(&:elixir_erl_pass.translate_args/2, ex_args, scope)
|
||||
@@ -301,8 +282,17 @@ defmodule Exception do
|
||||
|
||||
defp rewrite_guard(guard) do
|
||||
Macro.prewalk(guard, fn
|
||||
{:., _, [:erlang, call]} -> rewrite_guard_call(call)
|
||||
other -> other
|
||||
{{:., _, [:erlang, :element]}, _, [{{:., _, [:erlang, :+]}, _, [int, 1]}, arg]} ->
|
||||
{:elem, [], [arg, int]}
|
||||
|
||||
{{:., _, [:erlang, :element]}, _, [int, arg]} when is_integer(int) ->
|
||||
{:elem, [], [arg, int - 1]}
|
||||
|
||||
{:., _, [:erlang, call]} ->
|
||||
rewrite_guard_call(call)
|
||||
|
||||
other ->
|
||||
other
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -379,8 +369,8 @@ defmodule Exception do
|
||||
format_exit_reason(reason)
|
||||
else
|
||||
mfa ->
|
||||
# Assume tuple formattable as an mfa is an mfa, so exit was caused by
|
||||
# failed mfa.
|
||||
# Assume tuple formattable as an mfa is an mfa,
|
||||
# so exit was caused by failed mfa.
|
||||
"exited in: " <>
|
||||
mfa <> joiner <> "** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
|
||||
end
|
||||
@@ -604,13 +594,13 @@ defmodule Exception do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, 1
|
||||
iex> Exception.format_mfa(Foo, :bar, 1)
|
||||
"Foo.bar/1"
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
iex> Exception.format_mfa(Foo, :bar, [])
|
||||
"Foo.bar()"
|
||||
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
iex> Exception.format_mfa(nil, :bar, [])
|
||||
"nil.bar()"
|
||||
|
||||
Anonymous functions are reported as -func/arity-anonfn-count-,
|
||||
@@ -683,15 +673,86 @@ end
|
||||
|
||||
defmodule ArgumentError do
|
||||
defexception message: "argument error"
|
||||
|
||||
@impl true
|
||||
def blame(
|
||||
%{message: "argument error"} = exception,
|
||||
[{:erlang, :apply, [module, function, args], _} | _] = stacktrace
|
||||
) do
|
||||
message =
|
||||
cond do
|
||||
# Note that args may be an empty list even if they were supplied
|
||||
not is_atom(module) and is_atom(function) and args == [] ->
|
||||
"you attempted to apply #{inspect(function)} on #{inspect(module)}. " <>
|
||||
"If you are using apply/3, make sure the module is an atom. " <>
|
||||
"If you are using the dot syntax, such as map.field or module.function, " <>
|
||||
"make sure the left side of the dot is an atom or a map"
|
||||
|
||||
not is_atom(module) ->
|
||||
"you attempted to apply a function on #{inspect(module)}. " <>
|
||||
"Modules (the first argument of apply) must always be an atom"
|
||||
|
||||
not is_atom(function) ->
|
||||
"you attempted to apply #{inspect(function)} on module #{inspect(module)}. " <>
|
||||
"Functions (the second argument of apply) must always be an atom"
|
||||
|
||||
not is_list(args) ->
|
||||
"you attempted to apply #{inspect(function)} on module #{inspect(module)} " <>
|
||||
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a list"
|
||||
end
|
||||
|
||||
{%{exception | message: message}, stacktrace}
|
||||
end
|
||||
|
||||
def blame(exception, stacktrace) do
|
||||
{exception, stacktrace}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ArithmeticError do
|
||||
defexception message: "bad argument in arithmetic expression"
|
||||
|
||||
@unary_ops [:+, :-]
|
||||
@binary_ops [:+, :-, :*, :/]
|
||||
@binary_funs [:div, :rem]
|
||||
@bitwise_binary_funs [:band, :bor, :bxor, :bsl, :bsr]
|
||||
|
||||
@impl true
|
||||
def blame(%{message: message} = exception, [{:erlang, fun, args, _} | _] = stacktrace) do
|
||||
message =
|
||||
message <>
|
||||
case {fun, args} do
|
||||
{op, [a]} when op in @unary_ops ->
|
||||
": #{op}(#{inspect(a)})"
|
||||
|
||||
{op, [a, b]} when op in @binary_ops ->
|
||||
": #{inspect(a)} #{op} #{inspect(b)}"
|
||||
|
||||
{fun, [a, b]} when fun in @binary_funs ->
|
||||
": #{fun}(#{inspect(a)}, #{inspect(b)})"
|
||||
|
||||
{fun, [a, b]} when fun in @bitwise_binary_funs ->
|
||||
": Bitwise.#{fun}(#{inspect(a)}, #{inspect(b)})"
|
||||
|
||||
{:bnot, [a]} ->
|
||||
": Bitwise.bnot(#{inspect(a)})"
|
||||
|
||||
_ ->
|
||||
""
|
||||
end
|
||||
|
||||
{%{exception | message: message}, stacktrace}
|
||||
end
|
||||
|
||||
def blame(exception, stacktrace) do
|
||||
{exception, stacktrace}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SystemLimitError do
|
||||
defexception []
|
||||
|
||||
@impl true
|
||||
def message(_) do
|
||||
"a system limit has been reached"
|
||||
end
|
||||
@@ -700,6 +761,7 @@ end
|
||||
defmodule SyntaxError do
|
||||
defexception [:file, :line, description: "syntax error"]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
@@ -709,6 +771,7 @@ end
|
||||
defmodule TokenMissingError do
|
||||
defexception [:file, :line, description: "expression is incomplete"]
|
||||
|
||||
@impl true
|
||||
def message(%{file: file, line: line, description: description}) do
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <> " " <> description
|
||||
end
|
||||
@@ -717,6 +780,7 @@ end
|
||||
defmodule CompileError do
|
||||
defexception [:file, :line, description: "compile error"]
|
||||
|
||||
@impl true
|
||||
def message(%{file: file, line: line, description: description}) do
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <> " " <> description
|
||||
end
|
||||
@@ -725,6 +789,7 @@ end
|
||||
defmodule BadFunctionError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -733,6 +798,7 @@ end
|
||||
defmodule BadStructError do
|
||||
defexception [:struct, :term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -741,6 +807,7 @@ end
|
||||
defmodule BadMapError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"expected a map, got: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -749,6 +816,7 @@ end
|
||||
defmodule BadBooleanError do
|
||||
defexception [:term, :operator]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"expected a boolean on left-side of \"#{exception.operator}\", got: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -757,6 +825,7 @@ end
|
||||
defmodule MatchError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -765,6 +834,7 @@ end
|
||||
defmodule CaseClauseError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -773,6 +843,7 @@ end
|
||||
defmodule WithClauseError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"no with clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -781,6 +852,7 @@ end
|
||||
defmodule CondClauseError do
|
||||
defexception []
|
||||
|
||||
@impl true
|
||||
def message(_exception) do
|
||||
"no cond clause evaluated to a true value"
|
||||
end
|
||||
@@ -789,6 +861,7 @@ end
|
||||
defmodule TryClauseError do
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
@@ -797,11 +870,12 @@ end
|
||||
defmodule BadArityError do
|
||||
defexception [:function, :args]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
{:arity, arity} = Function.info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
@@ -811,73 +885,80 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [:module, :function, :arity, :reason, :exports]
|
||||
defexception [:module, :function, :arity, :reason, :message]
|
||||
|
||||
def message(%{reason: nil, module: module, function: function, arity: arity} = e) do
|
||||
@impl true
|
||||
def message(%{message: nil} = exception) do
|
||||
%{reason: reason, module: module, function: function, arity: arity} = exception
|
||||
{message, _loaded?} = message(reason, module, function, arity)
|
||||
message
|
||||
end
|
||||
|
||||
def message(%{message: message}) do
|
||||
message
|
||||
end
|
||||
|
||||
defp message(nil, module, function, arity) do
|
||||
cond do
|
||||
is_nil(function) or is_nil(arity) ->
|
||||
"undefined function"
|
||||
{"undefined function", false}
|
||||
|
||||
not is_nil(module) and :code.is_loaded(module) == false ->
|
||||
message(%{e | reason: :"module could not be loaded"})
|
||||
is_nil(module) ->
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
{"function #{formatted_fun} is undefined", false}
|
||||
|
||||
function_exported?(module, :module_info, 0) ->
|
||||
message(:"function not exported", module, function, arity)
|
||||
|
||||
true ->
|
||||
message(%{e | reason: :"function not exported"})
|
||||
message(:"module could not be loaded", module, function, arity)
|
||||
end
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"module could not be loaded",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
defp message(:"module could not be loaded", module, function, arity) do
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
"function #{formatted_fun} is undefined (module #{inspect(module)} is not available)"
|
||||
{"function #{formatted_fun} is undefined (module #{inspect(module)} is not available)", false}
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"function not exported",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
IO.iodata_to_binary(function_not_exported(module, function, arity, nil))
|
||||
defp message(:"function not exported", module, function, arity) do
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
{"function #{formatted_fun} is undefined or private", true}
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"function not available",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
"nil." <> fa = Exception.format_mfa(nil, function, arity)
|
||||
|
||||
"function " <>
|
||||
Exception.format_mfa(module, function, arity) <>
|
||||
" is undefined (function #{fa} is not available)"
|
||||
defp message(reason, module, function, arity) do
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
{"function #{formatted_fun} is undefined (#{reason})", false}
|
||||
end
|
||||
|
||||
def message(%{reason: reason, module: module, function: function, arity: arity}) do
|
||||
"function " <> Exception.format_mfa(module, function, arity) <> " is undefined (#{reason})"
|
||||
@impl true
|
||||
def blame(exception, stacktrace) do
|
||||
%{reason: reason, module: module, function: function, arity: arity} = exception
|
||||
{message, loaded?} = message(reason, module, function, arity)
|
||||
message = message <> hint(module, function, arity, loaded?)
|
||||
{%{exception | message: message}, stacktrace}
|
||||
end
|
||||
|
||||
defp hint(nil, _function, 0, _loaded?) do
|
||||
". If you are using the dot syntax, such as map.field or module.function, " <>
|
||||
"make sure the left side of the dot is an atom or a map"
|
||||
end
|
||||
|
||||
defp hint(module, function, arity, true) do
|
||||
hint_for_loaded_module(module, function, arity, nil)
|
||||
end
|
||||
|
||||
defp hint(_module, _function, _arity, _loaded?) do
|
||||
""
|
||||
end
|
||||
|
||||
@doc false
|
||||
def function_not_exported(module, function, arity, exports) do
|
||||
suffix =
|
||||
if macro_exported?(module, function, arity) do
|
||||
". However there is a macro with the same name and arity. " <>
|
||||
"Be sure to require #{inspect(module)} if you intend to invoke this macro"
|
||||
else
|
||||
did_you_mean(module, function, exports)
|
||||
end
|
||||
|
||||
[
|
||||
"function ",
|
||||
Exception.format_mfa(module, function, arity),
|
||||
" is undefined or private",
|
||||
suffix
|
||||
]
|
||||
def hint_for_loaded_module(module, function, arity, exports) do
|
||||
if macro_exported?(module, function, arity) do
|
||||
". However there is a macro with the same name and arity. " <>
|
||||
"Be sure to require #{inspect(module)} if you intend to invoke this macro"
|
||||
else
|
||||
IO.iodata_to_binary(did_you_mean(module, function, exports))
|
||||
end
|
||||
end
|
||||
|
||||
@function_threshold 0.77
|
||||
@@ -929,6 +1010,7 @@ end
|
||||
defmodule FunctionClauseError do
|
||||
defexception [:module, :function, :arity, :kind, :args, :clauses]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
case exception do
|
||||
%{function: nil} ->
|
||||
@@ -941,6 +1023,7 @@ defmodule FunctionClauseError do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def blame(%{module: module, function: function, arity: arity} = exception, stacktrace) do
|
||||
case stacktrace do
|
||||
[{^module, ^function, args, meta} | rest] when length(args) == arity ->
|
||||
@@ -1020,6 +1103,7 @@ end
|
||||
defmodule Protocol.UndefinedError do
|
||||
defexception [:protocol, :value, description: ""]
|
||||
|
||||
@impl true
|
||||
def message(%{protocol: protocol, value: value, description: description}) do
|
||||
"protocol #{inspect(protocol)} not implemented for #{inspect(value)}" <>
|
||||
maybe_description(description) <> maybe_available(protocol)
|
||||
@@ -1043,17 +1127,72 @@ defmodule Protocol.UndefinedError do
|
||||
end
|
||||
|
||||
defmodule KeyError do
|
||||
defexception [:key, :term]
|
||||
defexception [:key, :term, :message]
|
||||
|
||||
def message(exception) do
|
||||
msg = "key #{inspect(exception.key)} not found"
|
||||
@impl true
|
||||
def message(exception = %{message: nil}), do: message(exception.key, exception.term)
|
||||
def message(%{message: message}), do: message
|
||||
|
||||
if exception.term != nil do
|
||||
msg <> " in: #{inspect(exception.term)}"
|
||||
def message(key, term) do
|
||||
message = "key #{inspect(key)} not found"
|
||||
|
||||
if term != nil do
|
||||
message <> " in: #{inspect(term)}"
|
||||
else
|
||||
msg
|
||||
message
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def blame(exception = %{term: nil}, stacktrace) do
|
||||
message = message(exception.key, exception.term)
|
||||
{%{exception | message: message}, stacktrace}
|
||||
end
|
||||
|
||||
def blame(exception, stacktrace) do
|
||||
%{term: term, key: key} = exception
|
||||
message = message(key, term)
|
||||
|
||||
if is_atom(key) and (map_with_atom_keys_only?(term) or Keyword.keyword?(term)) do
|
||||
hint = did_you_mean(key, available_keys(term))
|
||||
message = message <> IO.iodata_to_binary(hint)
|
||||
{%{exception | message: message}, stacktrace}
|
||||
else
|
||||
{%{exception | message: message}, stacktrace}
|
||||
end
|
||||
end
|
||||
|
||||
defp map_with_atom_keys_only?(term) do
|
||||
is_map(term) and Enum.all?(Map.to_list(term), fn {k, _} -> is_atom(k) end)
|
||||
end
|
||||
|
||||
defp available_keys(term) when is_map(term), do: Map.keys(term)
|
||||
defp available_keys(term) when is_list(term), do: Keyword.keys(term)
|
||||
|
||||
@threshold 0.77
|
||||
@max_suggestions 5
|
||||
defp did_you_mean(missing_key, available_keys) do
|
||||
stringified_key = Atom.to_string(missing_key)
|
||||
|
||||
suggestions =
|
||||
for key <- available_keys,
|
||||
distance = String.jaro_distance(stringified_key, Atom.to_string(key)),
|
||||
distance >= @threshold,
|
||||
do: {distance, key}
|
||||
|
||||
case suggestions do
|
||||
[] -> []
|
||||
suggestions -> [". Did you mean one of:\n\n" | format_suggestions(suggestions)]
|
||||
end
|
||||
end
|
||||
|
||||
defp format_suggestions(suggestions) do
|
||||
suggestions
|
||||
|> Enum.sort(&(elem(&1, 0) >= elem(&2, 0)))
|
||||
|> Enum.take(@max_suggestions)
|
||||
|> Enum.sort(&(elem(&1, 1) <= elem(&2, 1)))
|
||||
|> Enum.map(fn {_, key} -> [" * ", inspect(key), ?\n] end)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule UnicodeConversionError do
|
||||
@@ -1090,6 +1229,7 @@ end
|
||||
defmodule File.Error do
|
||||
defexception [:reason, :path, action: ""]
|
||||
|
||||
@impl true
|
||||
def message(%{action: action, reason: reason, path: path}) do
|
||||
formatted =
|
||||
case {action, reason} do
|
||||
@@ -1107,6 +1247,7 @@ end
|
||||
defmodule File.CopyError do
|
||||
defexception [:reason, :source, :destination, on: "", action: ""]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
@@ -1124,6 +1265,7 @@ end
|
||||
defmodule File.LinkError do
|
||||
defexception [:reason, :existing, :new, action: ""]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
@@ -1135,6 +1277,7 @@ end
|
||||
defmodule ErlangError do
|
||||
defexception [:original]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"Erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
@@ -1182,11 +1325,12 @@ defmodule ErlangError do
|
||||
|
||||
def normalize({:badkey, key}, stacktrace) do
|
||||
term =
|
||||
case ensure_stacktrace(stacktrace) do
|
||||
case stacktrace do
|
||||
[{Map, :get_and_update!, [map, _, _], _} | _] -> map
|
||||
[{Map, :update!, [map, _, _], _} | _] -> map
|
||||
[{:maps, :update, [_, _, map], _} | _] -> map
|
||||
[{:maps, :get, [_, map], _} | _] -> map
|
||||
[{:erlang, :map_get, [_, map], _} | _] -> map
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
@@ -1210,13 +1354,12 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
stacktrace = ensure_stacktrace(stacktrace)
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(ensure_stacktrace(stacktrace))
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%FunctionClauseError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
@@ -1228,18 +1371,6 @@ defmodule ErlangError do
|
||||
%ErlangError{original: other}
|
||||
end
|
||||
|
||||
defp ensure_stacktrace(nil) do
|
||||
try do
|
||||
:erlang.get_stacktrace()
|
||||
rescue
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_stacktrace(stacktrace) do
|
||||
stacktrace
|
||||
end
|
||||
|
||||
defp from_stacktrace([{module, function, args, _} | _]) when is_list(args) do
|
||||
{module, function, length(args)}
|
||||
end
|
||||
|
||||
+34
-5
@@ -90,8 +90,13 @@ defmodule File do
|
||||
| :read
|
||||
| :read_ahead
|
||||
| :sync
|
||||
| :utf8
|
||||
| :write
|
||||
| {:read_ahead, pos_integer}
|
||||
| {:delayed_write, non_neg_integer, non_neg_integer}
|
||||
| encoding_mode()
|
||||
|
||||
@type encoding_mode ::
|
||||
:utf8
|
||||
| {
|
||||
:encoding,
|
||||
:latin1
|
||||
@@ -102,7 +107,11 @@ defmodule File do
|
||||
| {:utf16, :big | :little}
|
||||
| {:utf32, :big | :little}
|
||||
}
|
||||
| {:read_ahead, pos_integer}
|
||||
|
||||
@type stream_mode ::
|
||||
encoding_mode()
|
||||
| :trim_bom
|
||||
| {:read_ahead, pos_integer | false}
|
||||
| {:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
@@ -428,6 +437,7 @@ defmodule File do
|
||||
* `:enotsup` - symbolic links are not supported on the current platform
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec read_link(Path.t()) :: {:ok, binary} | {:error, posix}
|
||||
def read_link(path) do
|
||||
case path |> IO.chardata_to_string() |> :file.read_link() do
|
||||
@@ -440,6 +450,7 @@ defmodule File do
|
||||
Same as `read_link/1` but returns the target directly or throws `File.Error` if an error is
|
||||
returned.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec read_link!(Path.t()) :: binary | no_return
|
||||
def read_link!(path) do
|
||||
case read_link(path) do
|
||||
@@ -525,6 +536,8 @@ defmodule File do
|
||||
If the operating system does not support hard links, returns
|
||||
`{:error, :enotsup}`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def ln(existing, new) do
|
||||
:file.make_link(IO.chardata_to_string(existing), IO.chardata_to_string(new))
|
||||
end
|
||||
@@ -534,6 +547,8 @@ defmodule File do
|
||||
|
||||
Returns `:ok` otherwise
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln!(Path.t(), Path.t()) :: :ok | no_return
|
||||
def ln!(existing, new) do
|
||||
case ln(existing, new) do
|
||||
:ok ->
|
||||
@@ -555,6 +570,8 @@ defmodule File do
|
||||
If the operating system does not support symlinks, returns
|
||||
`{:error, :enotsup}`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln_s(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def ln_s(existing, new) do
|
||||
:file.make_symlink(IO.chardata_to_string(existing), IO.chardata_to_string(new))
|
||||
end
|
||||
@@ -564,6 +581,7 @@ defmodule File do
|
||||
|
||||
Returns `:ok` otherwise
|
||||
"""
|
||||
@spec ln_s!(Path.t(), Path.t()) :: :ok | no_return
|
||||
def ln_s!(existing, new) do
|
||||
case ln_s(existing, new) do
|
||||
:ok ->
|
||||
@@ -645,6 +663,7 @@ defmodule File do
|
||||
|
||||
# Rename directory "samples" to "tmp"
|
||||
File.rename "samples", "tmp"
|
||||
|
||||
"""
|
||||
@spec rename(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def rename(source, destination) do
|
||||
@@ -1014,14 +1033,19 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Tries to delete the dir at `path`.
|
||||
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
It returns `{:error, :eexist}` if the directory is not empty.
|
||||
|
||||
## Examples
|
||||
|
||||
File.rmdir('tmp_dir')
|
||||
File.rmdir("tmp_dir")
|
||||
#=> :ok
|
||||
|
||||
File.rmdir('file.txt')
|
||||
File.rmdir("non_empty_dir")
|
||||
#=> {:error, :eexist}
|
||||
|
||||
File.rmdir("file.txt")
|
||||
#=> {:error, :enotdir}
|
||||
|
||||
"""
|
||||
@@ -1488,7 +1512,8 @@ defmodule File do
|
||||
in raw mode for performance reasons. Therefore, Elixir **will** open
|
||||
streams in `:raw` mode with the `:read_ahead` option unless an encoding
|
||||
is specified. This means any data streamed into the file must be
|
||||
converted to `t:iodata/0` type. If you pass `[:utf8]` in the modes parameter,
|
||||
converted to `t:iodata/0` type. If you pass e.g. `[encoding: :utf8]`
|
||||
or `[encoding: {:utf16, :little}]` in the modes parameter,
|
||||
the underlying stream will use `IO.write/2` and the `String.Chars` protocol
|
||||
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
|
||||
|
||||
@@ -1500,6 +1525,9 @@ defmodule File do
|
||||
If you pass `:trim_bom` in the modes parameter, the stream will
|
||||
trim UTF-8, UTF-16 and UTF-32 byte order marks when reading from file.
|
||||
|
||||
Note that this function does not try to discover the file encoding basing
|
||||
on BOM.
|
||||
|
||||
## Examples
|
||||
|
||||
# Read in 2048 byte chunks rather than lines
|
||||
@@ -1510,6 +1538,7 @@ defmodule File do
|
||||
See `Stream.run/1` for an example of streaming into a file.
|
||||
|
||||
"""
|
||||
@spec stream!(Path.t(), stream_mode, :line | pos_integer) :: File.Stream.t()
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = normalize_modes(modes, true)
|
||||
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
|
||||
|
||||
@@ -22,10 +22,10 @@ defmodule File.Stream do
|
||||
modes =
|
||||
case raw do
|
||||
true ->
|
||||
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
|
||||
[:raw | modes]
|
||||
else
|
||||
[:raw, :read_ahead | modes]
|
||||
case :lists.keyfind(:read_ahead, 1, modes) do
|
||||
{:read_ahead, false} -> [:raw | :lists.keydelete(:read_ahead, 1, modes)]
|
||||
{:read_ahead, _} -> [:raw | modes]
|
||||
false -> [:raw, :read_ahead | modes]
|
||||
end
|
||||
|
||||
false ->
|
||||
@@ -77,7 +77,7 @@ defmodule File.Stream do
|
||||
start_fun = fn ->
|
||||
case :file.open(path, read_modes(modes)) do
|
||||
{:ok, device} ->
|
||||
if :trim_bom in modes, do: trim_bom(device), else: device
|
||||
if :trim_bom in modes, do: trim_bom(device, raw) |> elem(0), else: device
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
@@ -106,19 +106,24 @@ defmodule File.Stream do
|
||||
end
|
||||
end
|
||||
|
||||
def count(%{path: path, line_or_bytes: bytes}) do
|
||||
def count(%{path: path, line_or_bytes: bytes, raw: true, modes: modes}) do
|
||||
case File.stat(path) do
|
||||
{:ok, %{size: 0}} ->
|
||||
{:error, __MODULE__}
|
||||
|
||||
{:ok, %{size: size}} ->
|
||||
{:ok, div(size, bytes) + if(rem(size, bytes) == 0, do: 0, else: 1)}
|
||||
remainder = if rem(size, bytes) == 0, do: 0, else: 1
|
||||
{:ok, div(size, bytes) + remainder - count_raw_bom(path, modes)}
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
@@ -127,10 +132,30 @@ defmodule File.Stream do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp trim_bom(device) do
|
||||
header = IO.binread(device, 4)
|
||||
{:ok, _new_pos} = :file.position(device, bom_length(header))
|
||||
device
|
||||
defp count_raw_bom(path, modes) do
|
||||
if :trim_bom in modes do
|
||||
File.open!(path, read_modes(modes), &(&1 |> trim_bom(true) |> elem(1)))
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_bom(device, true) do
|
||||
bom_length = device |> IO.binread(4) |> bom_length()
|
||||
{:ok, new_pos} = :file.position(device, bom_length)
|
||||
{device, new_pos}
|
||||
end
|
||||
|
||||
defp trim_bom(device, false) do
|
||||
# Or we read the bom in the correct amount or it isn't there
|
||||
case bom_length(IO.read(device, 1)) do
|
||||
0 ->
|
||||
{:ok, _} = :file.position(device, 0)
|
||||
{device, 0}
|
||||
|
||||
_ ->
|
||||
{device, 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp bom_length(<<239, 187, 191, _rest::binary>>), do: 3
|
||||
|
||||
+49
-8
@@ -3,6 +3,41 @@ import Kernel, except: [round: 1]
|
||||
defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating-point numbers.
|
||||
|
||||
## Kernel functions
|
||||
|
||||
There are functions related to floating-point numbers on the `Kernel` module
|
||||
too. Here is a list of them:
|
||||
|
||||
* `Kernel.round/1`: rounds a number to the nearest integer.
|
||||
* `Kernel.trunc/1`: returns the integer part of a number.
|
||||
|
||||
## Known issues
|
||||
|
||||
There are some very well known problems with floating-point numbers
|
||||
and arithmetics due to the fact most decimal fractions cannot be
|
||||
represented by a floating-point binary and most operations are not exact,
|
||||
but operate on approximations. Those issues are not specific
|
||||
to Elixir, they are a property of floating point representation itself.
|
||||
|
||||
For example, the numbers 0.1 and 0.01 are two of them, what means the result
|
||||
of squaring 0.1 does not give 0.01 neither the closest representable. Here is
|
||||
what happens in this case:
|
||||
|
||||
* The closest representable number to 0.1 is 0.1000000014
|
||||
* The closest representable number to 0.01 is 0.0099999997
|
||||
* Doing 0.1 * 0.1 should return 0.01, but because 0.1 is actually 0.1000000014,
|
||||
the result is 0.010000000000000002, and because this is not the closest
|
||||
representable number to 0.01, you'll get the wrong result for this operation
|
||||
|
||||
There are also other known problems like flooring or rounding numbers. See
|
||||
`round/2` and `floor/2` for more details about them.
|
||||
|
||||
To learn more about floating-point arithmetic visit:
|
||||
|
||||
* [0.30000000000000004.com](http://0.30000000000000004.com/)
|
||||
* [What Every Programmer Should Know About Floating-Point Arithmetic](http://floating-point-gui.de/)
|
||||
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
@@ -79,13 +114,18 @@ defmodule Float do
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
Rounds a float to the largest number less than or equal to `num`.
|
||||
|
||||
`floor/2` also accepts a precision to round a floating-point value down
|
||||
to an arbitrary number of fractional digits (between 0 and 15).
|
||||
The operation is performed on the binary floating point, without a
|
||||
conversion to decimal.
|
||||
|
||||
This function always returns a float. `Kernel.trunc/1` may be used instead to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Known issues
|
||||
|
||||
The behaviour of `floor/2` for floats can be surprising. For example:
|
||||
|
||||
iex> Float.floor(12.52, 2)
|
||||
@@ -96,9 +136,6 @@ defmodule Float do
|
||||
and therefore the number above is internally represented as 12.51999999,
|
||||
which explains the behaviour above.
|
||||
|
||||
This function always returns a float. `Kernel.trunc/1` may be used instead to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
@@ -174,6 +211,8 @@ defmodule Float do
|
||||
`Kernel.round/1` if you want a function that accepts both floats
|
||||
and integers and always returns an integer.
|
||||
|
||||
## Known issues
|
||||
|
||||
The behaviour of `round/2` for floats can be surprising. For example:
|
||||
|
||||
iex> Float.round(5.5675, 3)
|
||||
@@ -348,6 +387,8 @@ defmodule Float do
|
||||
{-16, 1}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec ratio(float) :: {pos_integer | neg_integer, pos_integer}
|
||||
def ratio(float) when is_float(float) do
|
||||
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
|
||||
{num, _, den} = decompose(significant)
|
||||
@@ -432,21 +473,21 @@ defmodule Float do
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(float))
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use Float.to_charlist/1 instead"
|
||||
def to_char_list(float), do: Float.to_charlist(float)
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use :erlang.float_to_list/2 instead"
|
||||
def to_char_list(float, options) do
|
||||
:erlang.float_to_list(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use :erlang.float_to_binary/2 instead"
|
||||
def to_string(float, options) do
|
||||
:erlang.float_to_binary(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
defmodule Function do
|
||||
@moduledoc """
|
||||
A set of functions for working with functions.
|
||||
|
||||
There are two types of captured functions: **external** and **local**.
|
||||
External functions are functions residing in modules that are captured
|
||||
with `&/1`, such as `&String.length/1`. Local functions are anonymous functions
|
||||
defined with `fn/1` or with the capture operator `&/1` using `&1`, `&2`,
|
||||
and so on as replacements.
|
||||
"""
|
||||
|
||||
@type information ::
|
||||
:arity
|
||||
| :env
|
||||
| :index
|
||||
| :module
|
||||
| :name
|
||||
| :new_index
|
||||
| :new_uniq
|
||||
| :pid
|
||||
| :type
|
||||
| :uniq
|
||||
|
||||
@doc """
|
||||
Captures the given function.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Function.capture(String, :length, 1)
|
||||
&String.length/1
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec capture(module, atom, arity) :: fun
|
||||
def capture(module, function_name, arity) do
|
||||
:erlang.make_fun(module, function_name, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a keyword list with information about a function.
|
||||
|
||||
The returned keys (with the corresponding possible values) for
|
||||
all types of functions (local and external) are the following:
|
||||
|
||||
* `:type` - `:local` (for anonymous functions) or `:external` (for
|
||||
named functions).
|
||||
|
||||
* `:module` - an atom which is the module where the function is defined when
|
||||
anonymous or the module which the function refers to when it's a named function.
|
||||
|
||||
* `:arity` - (integer) the number of arguments the function is to be called with.
|
||||
|
||||
* `:name` - (atom) the name of the function.
|
||||
|
||||
* `:env` - a list of the environment or free variables. For named
|
||||
functions, the returned list is always empty.
|
||||
|
||||
When `fun` is an anonymous function (that is, the type is `:local`), the following
|
||||
additional keys are returned:
|
||||
|
||||
* `:pid` - PID of the process that originally created the function.
|
||||
|
||||
* `:index` - (integer) an index into the module function table.
|
||||
|
||||
* `:new_index` - (integer) an index into the module function table.
|
||||
|
||||
* `:new_uniq` - (binary) a unique value for this function. It's
|
||||
calculated from the compiled code for the entire module.
|
||||
|
||||
* `:uniq` - (integer) a unique value for this function. This integer is
|
||||
calculated from the compiled code for the entire module.
|
||||
|
||||
**Note**: this function must be used only for debugging purposes.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> fun = fn x -> x end
|
||||
iex> info = Function.info(fun)
|
||||
iex> Keyword.get(info, :arity)
|
||||
1
|
||||
iex> Keyword.get(info, :type)
|
||||
:local
|
||||
|
||||
iex> fun = &String.length/1
|
||||
iex> info = Function.info(fun)
|
||||
iex> Keyword.get(info, :type)
|
||||
:external
|
||||
iex> Keyword.get(info, :name)
|
||||
:length
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec info(fun) :: [{information, term}]
|
||||
def info(fun), do: :erlang.fun_info(fun)
|
||||
|
||||
@doc """
|
||||
Returns a specific information about the function.
|
||||
|
||||
The returned information is a two-element tuple in the shape of
|
||||
`{info, value}`.
|
||||
|
||||
For any function, the information asked for can be any of the atoms
|
||||
`:module`, `:name`, `:arity`, `:env`, or `:type`.
|
||||
|
||||
For anonymous functions, there is also information about any of the
|
||||
atoms `:index`, `:new_index`, `:new_uniq`, `:uniq`, and `:pid`.
|
||||
For a named function, the value of any of these items is always the
|
||||
atom `:undefined`.
|
||||
|
||||
For more information on each of the possible returned values, see
|
||||
`info/1`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> f = fn x -> x end
|
||||
iex> Function.info(f, :arity)
|
||||
{:arity, 1}
|
||||
iex> Function.info(f, :type)
|
||||
{:type, :local}
|
||||
|
||||
iex> fun = &String.length/1
|
||||
iex> Function.info(fun, :name)
|
||||
{:name, :length}
|
||||
iex> Function.info(fun, :pid)
|
||||
{:pid, :undefined}
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec info(fun, item) :: {item, term} when item: information
|
||||
def info(fun, item), do: :erlang.fun_info(fun, item)
|
||||
end
|
||||
@@ -4,7 +4,7 @@ defmodule GenEvent do
|
||||
# Functions from this module are deprecated in elixir_dispatch.
|
||||
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
A event manager with event handlers behaviour.
|
||||
|
||||
If you are interested in implementing an event manager, please read the
|
||||
"Alternatives" section below. If you have to implement an event handler to
|
||||
@@ -43,6 +43,8 @@ defmodule GenEvent do
|
||||
[`:gen_event`](http://erlang.org/doc/man/gen_event.html) Erlang module.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use Erlang/OTP's :gen_event module instead"
|
||||
|
||||
@callback init(args :: term) ::
|
||||
{:ok, state}
|
||||
| {:ok, state, :hibernate}
|
||||
@@ -83,6 +85,9 @@ defmodule GenEvent do
|
||||
|
||||
@type handler :: atom | {atom, term}
|
||||
|
||||
message = "Use one of the alternatives described in the documentation for the GenEvent module"
|
||||
|
||||
@deprecated message
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
%{file: file, line: line} = __CALLER__
|
||||
@@ -148,12 +153,14 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec start_link(options) :: on_start
|
||||
def start_link(options \\ []) when is_list(options) do
|
||||
do_start(:link, options)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec start(options) :: on_start
|
||||
def start(options \\ []) when is_list(options) do
|
||||
do_start(:nolink, options)
|
||||
@@ -177,7 +184,7 @@ defmodule GenEvent do
|
||||
|
||||
other ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
expected :name option to be one of the following:
|
||||
|
||||
* nil
|
||||
* atom
|
||||
@@ -190,24 +197,28 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec stream(manager, keyword) :: GenEvent.Stream.t()
|
||||
def stream(manager, options \\ []) do
|
||||
%GenEvent.Stream{manager: manager, timeout: Keyword.get(options, :timeout, :infinity)}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec add_handler(manager, handler, term) :: :ok | {:error, term}
|
||||
def add_handler(manager, handler, args) do
|
||||
rpc(manager, {:add_handler, handler, args})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec add_mon_handler(manager, handler, term) :: :ok | {:error, term}
|
||||
def add_mon_handler(manager, handler, args) do
|
||||
rpc(manager, {:add_mon_handler, handler, args, self()})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec notify(manager, term) :: :ok
|
||||
def notify(manager, event)
|
||||
|
||||
@@ -238,18 +249,21 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec sync_notify(manager, term) :: :ok
|
||||
def sync_notify(manager, event) do
|
||||
rpc(manager, {:sync_notify, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec ack_notify(manager, term) :: :ok
|
||||
def ack_notify(manager, event) do
|
||||
rpc(manager, {:ack_notify, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec call(manager, handler, term, timeout) :: term | {:error, term}
|
||||
def call(manager, handler, request, timeout \\ 5000) do
|
||||
try do
|
||||
@@ -263,30 +277,35 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec remove_handler(manager, handler, term) :: term | {:error, term}
|
||||
def remove_handler(manager, handler, args) do
|
||||
rpc(manager, {:delete_handler, handler, args})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec swap_handler(manager, handler, term, handler, term) :: :ok | {:error, term}
|
||||
def swap_handler(manager, handler1, args1, handler2, args2) do
|
||||
rpc(manager, {:swap_handler, handler1, args1, handler2, args2})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec swap_mon_handler(manager, handler, term, handler, term) :: :ok | {:error, term}
|
||||
def swap_mon_handler(manager, handler1, args1, handler2, args2) do
|
||||
rpc(manager, {:swap_mon_handler, handler1, args1, handler2, args2, self()})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec which_handlers(manager) :: [handler]
|
||||
def which_handlers(manager) do
|
||||
rpc(manager, :which_handlers)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
@spec stop(manager, reason :: term, timeout) :: :ok
|
||||
def stop(manager, reason \\ :normal, timeout \\ :infinity) do
|
||||
:gen.stop(manager, reason, timeout)
|
||||
@@ -829,7 +848,7 @@ defmodule GenEvent do
|
||||
apply(mod, fun, args)
|
||||
catch
|
||||
:throw, val -> {:ok, val}
|
||||
:error, val -> {:error, {val, System.stacktrace()}}
|
||||
:error, val -> {:error, {val, __STACKTRACE__}}
|
||||
:exit, val -> {:error, val}
|
||||
else
|
||||
res -> {:ok, res}
|
||||
|
||||
@@ -161,7 +161,8 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
{_from, {_pid, ^ref}, {notify, _event}}
|
||||
when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
|
||||
+207
-113
@@ -23,10 +23,17 @@ defmodule GenServer do
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [head | tail]) do
|
||||
{:reply, head, tail}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
end
|
||||
@@ -45,7 +52,7 @@ defmodule GenServer do
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
We start our `Stack` by calling `start_link/3`, passing the module
|
||||
We start our `Stack` by calling `start_link/2`, 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**
|
||||
@@ -56,18 +63,64 @@ defmodule GenServer do
|
||||
that must be handled by the `c:handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `c:handle_cast/2`.
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
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
|
||||
the calls in new functions representing the public API of the server.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Client
|
||||
|
||||
def start_link(default) when is_list(default) do
|
||||
GenServer.start_link(__MODULE__, default)
|
||||
end
|
||||
|
||||
def push(pid, item) do
|
||||
GenServer.cast(pid, {:push, item})
|
||||
end
|
||||
|
||||
def pop(pid) do
|
||||
GenServer.call(pid, :pop)
|
||||
end
|
||||
|
||||
# Server (callbacks)
|
||||
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [head | tail]) do
|
||||
{:reply, head, tail}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
end
|
||||
end
|
||||
|
||||
In practice, it is common to have both server and client functions in
|
||||
the same module. If the server and/or client implementations are growing
|
||||
complex, you may want to have them in different modules.
|
||||
|
||||
## use GenServer and callbacks
|
||||
|
||||
There are 6 callbacks required to be implemented in a `GenServer`. By
|
||||
adding `use GenServer` to your module, Elixir will automatically define
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize.
|
||||
There are 7 callbacks to be implemented when you use a `GenServer`.
|
||||
The only required callback is `init/1`.
|
||||
|
||||
`use GenServer` also defines a `child_spec/1` function, allowing the
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -78,7 +131,7 @@ defmodule GenServer do
|
||||
|
||||
See the `Supervisor` docs for more information.
|
||||
|
||||
## Name Registration
|
||||
## 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
|
||||
@@ -87,7 +140,7 @@ defmodule GenServer do
|
||||
* 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
|
||||
* `{:global, term}` - the GenServer is registered globally with the given
|
||||
term using the functions in the [`:global` module](http://www.erlang.org/doc/man/global.html).
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
@@ -108,80 +161,30 @@ defmodule GenServer do
|
||||
GenServer.call(MyStack, :pop) #=> :hello
|
||||
|
||||
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:
|
||||
`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
|
||||
* 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
|
||||
|
||||
If there is an interest to register dynamic names locally, do not use
|
||||
atoms, as atoms are never garbage collected and therefore dynamically
|
||||
generated atoms won't be garbage collected. For such cases, you can
|
||||
atoms, as atoms are never garbage-collected and therefore dynamically
|
||||
generated atoms won't be garbage-collected. For such cases, you can
|
||||
set up your own local registry by using the `Registry` module.
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
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
|
||||
the calls in new functions representing the public API of the server.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Client
|
||||
|
||||
def start_link(default) do
|
||||
GenServer.start_link(__MODULE__, default)
|
||||
end
|
||||
|
||||
def push(pid, item) do
|
||||
GenServer.cast(pid, {:push, item})
|
||||
end
|
||||
|
||||
def pop(pid) do
|
||||
GenServer.call(pid, :pop)
|
||||
end
|
||||
|
||||
# Server (callbacks)
|
||||
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
# Call the default implementation from GenServer
|
||||
super(request, from, state)
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
end
|
||||
|
||||
def handle_cast(request, state) do
|
||||
super(request, state)
|
||||
end
|
||||
end
|
||||
|
||||
In practice, it is common to have both server and client functions in
|
||||
the same module. If the server and/or client implementations are growing
|
||||
complex, you may want to have them in different modules.
|
||||
|
||||
## Receiving "regular" messages
|
||||
|
||||
The goal of a `GenServer` is to abstract the "receive" loop for developers,
|
||||
automatically handling system messages, support code change, synchronous
|
||||
automatically handling system messages, supporting code change, synchronous
|
||||
calls and more. Therefore, you should never call your own "receive" inside
|
||||
the GenServer callbacks as doing so will cause the GenServer to misbehave.
|
||||
|
||||
Besides the synchronous and asynchronous communication provided by `call/3`
|
||||
and `cast/2`, "regular" messages sent by functions such `Kernel.send/2`,
|
||||
and `cast/2`, "regular" messages sent by functions such as `Kernel.send/2`,
|
||||
`Process.send_after/4` and similar, can be handled inside the `c:handle_info/2`
|
||||
callback.
|
||||
|
||||
@@ -196,11 +199,13 @@ defmodule GenServer do
|
||||
GenServer.start_link(__MODULE__, %{})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(state) do
|
||||
schedule_work() # Schedule work to be performed on start
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:work, state) do
|
||||
# Do the desired work here
|
||||
schedule_work() # Reschedule once more
|
||||
@@ -212,13 +217,55 @@ defmodule GenServer do
|
||||
end
|
||||
end
|
||||
|
||||
## When (not) to use a GenServer
|
||||
|
||||
So far, we have learned that a `GenServer` can be used as a supervised process
|
||||
that handles sync and async calls. It can also handle system messages, such as
|
||||
periodic messages and monitoring events. GenServer processes may also be named.
|
||||
|
||||
A GenServer, or a process in general, must be used to model runtime characteristics
|
||||
of your system. A GenServer must never be used for code organization purposes.
|
||||
|
||||
In Elixir, code organization is done by modules and functions, processes are not
|
||||
necessary. For example, imagine you are implementing a calculator and you decide
|
||||
to put all the calculator operations behind a GenServer:
|
||||
|
||||
def add(a, b) do
|
||||
GenServer.call(__MODULE__, {:add, a, b})
|
||||
end
|
||||
|
||||
def handle_call({:add, a, b}, _from, state) do
|
||||
{:reply, a + b, state}
|
||||
end
|
||||
|
||||
def handle_call({:subtract, a, b}, _from, state) do
|
||||
{:reply, a - b, state}
|
||||
end
|
||||
|
||||
This is an anti-pattern not only because it convolutes the calculator logic but
|
||||
also because you put the calculator logic behind a single process that will
|
||||
potentially become a bottleneck in your system, especially as the number of
|
||||
calls grow. Instead just define the functions directly:
|
||||
|
||||
def add(a, b) do
|
||||
a + b
|
||||
end
|
||||
|
||||
def subtract(a, b) do
|
||||
a - b
|
||||
end
|
||||
|
||||
If you don't need a process, then you don't need a process. Use processes only to
|
||||
model runtime properties, such as mutable state, concurrency and failures, never
|
||||
for code organization.
|
||||
|
||||
## Debugging with the :sys module
|
||||
|
||||
GenServers, as [special processes](http://erlang.org/doc/design_principles/spec_proc.html),
|
||||
can be debugged using the [`:sys` module](http://www.erlang.org/doc/man/sys.html). Through various hooks, this module
|
||||
allows developers to introspect the state of the process and trace
|
||||
system events that happen during its execution, such as received messages,
|
||||
sent replies and state changes.
|
||||
can be debugged using the [`:sys` module](http://www.erlang.org/doc/man/sys.html).
|
||||
Through various hooks, this module allows developers to introspect the state of
|
||||
the process and trace system events that happen during its execution, such as
|
||||
received messages, sent replies and state changes.
|
||||
|
||||
Let's explore the basic functions from the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) used for debugging:
|
||||
@@ -279,7 +326,7 @@ defmodule GenServer do
|
||||
|
||||
## Learn more
|
||||
|
||||
If you wish to find out more about gen servers, the Elixir Getting Started
|
||||
If you wish to find out more about GenServers, the Elixir Getting Started
|
||||
guide provides a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
@@ -287,6 +334,7 @@ defmodule GenServer do
|
||||
* [`:gen_server` module documentation](http://www.erlang.org/doc/man/gen_server.html)
|
||||
* [gen_server Behaviour – OTP Design Principles](http://www.erlang.org/doc/design_principles/gen_server_concepts.html)
|
||||
* [Clients and Servers – Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/clients-and-servers)
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -302,18 +350,24 @@ defmodule GenServer do
|
||||
except `handle_info(:timeout, state)` will be called after `timeout`
|
||||
milliseconds if no messages are received within the timeout.
|
||||
|
||||
Returning `{:ok, state, :hibernate}` is similar to
|
||||
`{:ok, state}` except the process is hibernated before entering the loop. See
|
||||
Returning `{:ok, state, :hibernate}` is similar to `{:ok, state}`
|
||||
except the process is hibernated before entering the loop. See
|
||||
`c:handle_call/3` for more information on hibernation.
|
||||
|
||||
Returning `:ignore` will cause `start_link/3` to return `:ignore` and the
|
||||
process will exit normally without entering the loop or calling `c:terminate/2`.
|
||||
If used when part of a supervision tree the parent supervisor will not fail
|
||||
to start nor immediately try to restart the `GenServer`. The remainder of the
|
||||
supervision tree will be (re)started and so the `GenServer` should not be
|
||||
required by other processes. It can be started later with
|
||||
`Supervisor.restart_child/2` as the child specification is saved in the parent
|
||||
supervisor. The main use cases for this are:
|
||||
Returning `{:ok, state, {:continue, continue}}` is similar to
|
||||
`{:ok, state}` except that immediately after entering the loop
|
||||
the `c:handle_continue/2` callback will be invoked with the value
|
||||
`continue` as first argument.
|
||||
|
||||
Returning `:ignore` will cause `start_link/3` to return `:ignore` and
|
||||
the process will exit normally without entering the loop or calling
|
||||
`c:terminate/2`. If used when part of a supervision tree the parent
|
||||
supervisor will not fail to start nor immediately try to restart the
|
||||
`GenServer`. The remainder of the supervision tree will be started
|
||||
and so the `GenServer` should not be required by other processes.
|
||||
It can be started later with `Supervisor.restart_child/2` as the child
|
||||
specification is saved in the parent supervisor. The main use cases for
|
||||
this are:
|
||||
|
||||
* The `GenServer` is disabled by configuration but might be enabled later.
|
||||
* An error occurred and it will be handled by a different mechanism than the
|
||||
@@ -326,7 +380,7 @@ defmodule GenServer do
|
||||
"""
|
||||
@callback init(args :: term) ::
|
||||
{:ok, state}
|
||||
| {:ok, state, timeout | :hibernate}
|
||||
| {:ok, state, timeout | :hibernate | {:continue, term}}
|
||||
| :ignore
|
||||
| {:stop, reason :: any}
|
||||
when state: any
|
||||
@@ -353,6 +407,10 @@ defmodule GenServer do
|
||||
`GenServer` causes garbage collection and leaves a continuous heap that
|
||||
minimises the memory used by the process.
|
||||
|
||||
Returning `{:reply, reply, new_state, {:continue, continue}}` is similar to
|
||||
`{:reply, reply, new_state}` except `c:handle_continue/2` will be invoked
|
||||
immediately after with the value `continue` as first argument.
|
||||
|
||||
Hibernating should not be used aggressively as too much time could be spent
|
||||
garbage collecting. Normally it should only be used when a message is not
|
||||
expected soon and minimising the memory of the process is shown to be
|
||||
@@ -374,9 +432,9 @@ defmodule GenServer do
|
||||
process exits without replying as the caller will be blocking awaiting a
|
||||
reply.
|
||||
|
||||
Returning `{:noreply, new_state, timeout | :hibernate}` is similar to
|
||||
`{:noreply, new_state}` except a timeout or hibernation occurs as with a
|
||||
`:reply` tuple.
|
||||
Returning `{:noreply, new_state, timeout | :hibernate | {:continue, continue}}`
|
||||
is similar to `{:noreply, new_state}` except a timeout, hibernation or continue
|
||||
occurs as with a `:reply` tuple.
|
||||
|
||||
Returning `{:stop, reason, reply, new_state}` stops the loop and `c:terminate/2`
|
||||
is called with reason `reason` and state `new_state`. Then the `reply` is sent
|
||||
@@ -385,15 +443,14 @@ defmodule GenServer do
|
||||
Returning `{:stop, reason, new_state}` is similar to
|
||||
`{:stop, reason, reply, new_state}` except a reply is not sent.
|
||||
|
||||
If this callback is not implemented, the default implementation by
|
||||
`use GenServer` will fail with a `RuntimeError` exception with a message:
|
||||
attempted to call `GenServer` but no `handle_call/3` clause was provided.
|
||||
This callback is optional. If one is not implemented, the server will fail
|
||||
if a call is performed against it.
|
||||
"""
|
||||
@callback handle_call(request :: term, from, state :: term) ::
|
||||
{:reply, reply, new_state}
|
||||
| {:reply, reply, new_state, timeout | :hibernate}
|
||||
| {:reply, reply, new_state, timeout | :hibernate | {:continue, term}}
|
||||
| {:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:noreply, new_state, timeout | :hibernate, {:continue, term}}
|
||||
| {:stop, reason, reply, new_state}
|
||||
| {:stop, reason, new_state}
|
||||
when reply: term, new_state: term, reason: term
|
||||
@@ -414,17 +471,20 @@ defmodule GenServer do
|
||||
`{:noreply, new_state}` except the process is hibernated before continuing the
|
||||
loop. See `c:handle_call/3` for more information.
|
||||
|
||||
Returning `{:noreply, new_state, {:continue, continue}}` is similar to
|
||||
`{:noreply, new_state}` except `c:handle_continue/2` will be invoked
|
||||
immediately after with the value `continue` as first argument.
|
||||
|
||||
Returning `{:stop, reason, new_state}` stops the loop and `c:terminate/2` is
|
||||
called with the reason `reason` and state `new_state`. The process exits with
|
||||
reason `reason`.
|
||||
|
||||
If this callback is not implemented, the default implementation by
|
||||
`use GenServer` will fail with a `RuntimeError` exception with a message:
|
||||
attempted to call `GenServer` but no `handle_cast/2` clause was provided.
|
||||
This callback is optional. If one is not implemented, the server will fail
|
||||
if a cast is performed against it.
|
||||
"""
|
||||
@callback handle_cast(request :: term, state :: term) ::
|
||||
{:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:noreply, new_state, timeout | :hibernate | {:continue, term}}
|
||||
| {:stop, reason :: term, new_state}
|
||||
when new_state: term
|
||||
|
||||
@@ -436,12 +496,31 @@ defmodule GenServer do
|
||||
|
||||
Return values are the same as `c:handle_cast/2`.
|
||||
|
||||
If this callback is not implemented, the default implementation by
|
||||
`use GenServer` will return `{:noreply, state}`.
|
||||
This callback is optional. If one is not implemented, the received message
|
||||
will be logged.
|
||||
"""
|
||||
@callback handle_info(msg :: :timeout | term, state :: term) ::
|
||||
{:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:noreply, new_state, timeout | :hibernate | {:continue, term}}
|
||||
| {:stop, reason :: term, new_state}
|
||||
when new_state: term
|
||||
|
||||
@doc """
|
||||
Invoked to handle `continue` instructions.
|
||||
|
||||
It is useful for performing work after initialization or for splitting the work
|
||||
in a callback in multiple steps, updating the process state along the way.
|
||||
|
||||
Return values are the same as `c:handle_cast/2`.
|
||||
|
||||
This callback is optional. If one is not implemented, the server will fail
|
||||
if a continue instruction is used.
|
||||
|
||||
This callback is only supported on Erlang/OTP 21+.
|
||||
"""
|
||||
@callback handle_continue(continue :: term, state :: term) ::
|
||||
{:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate | {:continue, term}}
|
||||
| {:stop, reason :: term, new_state}
|
||||
when new_state: term
|
||||
|
||||
@@ -478,12 +557,15 @@ defmodule GenServer do
|
||||
Therefore it is not guaranteed that `c:terminate/2` is called when a `GenServer`
|
||||
exits. For such reasons, we usually recommend important clean-up rules to
|
||||
happen in separated processes either by use of monitoring or by links
|
||||
themselves. For example if the `GenServer` controls a `port` (e.g.
|
||||
`:gen_tcp.socket`) or `t:File.io_device/0`, they will be closed on receiving a
|
||||
`GenServer`'s exit signal and do not need to be closed in `c:terminate/2`.
|
||||
themselves. There is no cleanup needed when the `GenServer` controls a `port` (e.g.
|
||||
`:gen_tcp.socket`) or `t:File.io_device/0`, because these will be closed on
|
||||
receiving a `GenServer`'s exit signal and do not need to be closed manually
|
||||
in `c:terminate/2`.
|
||||
|
||||
If `reason` is not `:normal`, `:shutdown`, nor `{:shutdown, term}` an error is
|
||||
logged.
|
||||
|
||||
This callback is optional.
|
||||
"""
|
||||
@callback terminate(reason, state :: term) :: term
|
||||
when reason: :normal | :shutdown | {:shutdown, term}
|
||||
@@ -506,6 +588,8 @@ defmodule GenServer do
|
||||
|
||||
If `c:code_change/3` raises the code change fails and the loop will continue
|
||||
with its previous state. Therefore this callback does not usually contain side effects.
|
||||
|
||||
This callback is optional.
|
||||
"""
|
||||
@callback code_change(old_vsn, state :: term, extra :: term) ::
|
||||
{:ok, new_state :: term}
|
||||
@@ -533,7 +617,13 @@ defmodule GenServer do
|
||||
@callback format_status(reason, pdict_and_state :: list) :: term
|
||||
when reason: :normal | :terminate
|
||||
|
||||
@optional_callbacks format_status: 2
|
||||
@optional_callbacks code_change: 3,
|
||||
terminate: 2,
|
||||
handle_info: 2,
|
||||
handle_cast: 2,
|
||||
handle_call: 3,
|
||||
format_status: 2,
|
||||
handle_continue: 2
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
|
||||
@@ -567,18 +657,21 @@ defmodule GenServer do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour GenServer
|
||||
@opts unquote(opts)
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]}
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
Supervisor.child_spec(default, unquote(Macro.escape(opts)))
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
@@ -645,7 +738,7 @@ defmodule GenServer do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable GenServer
|
||||
defoverridable code_change: 3, terminate: 2, handle_info: 2, handle_cast: 2, handle_call: 3
|
||||
end
|
||||
end
|
||||
|
||||
@@ -661,8 +754,8 @@ defmodule GenServer do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
But you want to define your own implementation that converts the \
|
||||
arguments given to GenServer.start_link/3 to the server state
|
||||
You can copy the implementation above or define your own that converts \
|
||||
the arguments given to GenServer.start_link/3 to the server state.
|
||||
"""
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
@@ -696,7 +789,7 @@ defmodule GenServer do
|
||||
## Options
|
||||
|
||||
* `:name` - used for name registration as described in the "Name
|
||||
registration" section of the module documentation
|
||||
registration" section in the documentation for `GenServer`
|
||||
|
||||
* `:timeout` - if present, the server is allowed to spend the given number of
|
||||
milliseconds initializing or it will be terminated and the start function
|
||||
@@ -750,7 +843,7 @@ defmodule GenServer do
|
||||
|
||||
{other, _} ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
expected :name option to be one of the following:
|
||||
|
||||
* nil
|
||||
* atom
|
||||
@@ -886,7 +979,8 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ [node() | Node.list()], name, request) when is_list(nodes) and is_atom(name) do
|
||||
def abcast(nodes \\ [node() | Node.list()], 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
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
Tuple-based HashDict implementation.
|
||||
|
||||
Use the `Map` module instead.
|
||||
This module is deprecated. Use the `Map` module instead.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use Map instead"
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
|
||||
use Dict
|
||||
|
||||
@@ -23,19 +24,24 @@ defmodule HashDict do
|
||||
@compile :inline_list_funcs
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
message = "Use maps and the Map module instead"
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t()
|
||||
@deprecated message
|
||||
def new do
|
||||
%HashDict{}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def put(%HashDict{root: root, size: size}, key, value) do
|
||||
{root, counter} = do_put(root, key, value, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} =
|
||||
do_update(root, key, fn -> raise KeyError, key: key, term: dict end, fun, key_hash(key))
|
||||
@@ -43,15 +49,18 @@ defmodule HashDict do
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def update(%HashDict{root: root, size: size}, key, initial, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def fetch(%HashDict{root: root}, key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, _value} -> dict
|
||||
@@ -59,6 +68,7 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, value} -> {value, dict}
|
||||
@@ -66,11 +76,13 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def size(%HashDict{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
def reduce(%HashDict{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
defmodule HashSet do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
Tuple-based HashSet implementation.
|
||||
|
||||
Use the `MapSet` module instead.
|
||||
This module is deprecated. Use the `MapSet` module instead.
|
||||
"""
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@moduledoc deprecated: "Use MapSet instead"
|
||||
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
message = "Use the MapSet module instead"
|
||||
|
||||
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
@@ -21,33 +22,40 @@ defmodule HashSet do
|
||||
@compile :inline_list_funcs
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@deprecated message
|
||||
@spec new :: Set.t()
|
||||
def new do
|
||||
%HashSet{}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
|
||||
set_fold(set1, set2, fn v, acc -> put(acc, v) end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def union(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set2, set1, fn v, acc -> put(acc, v) end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set1, %HashSet{}, fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def difference(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set2, set1, fn v, acc -> delete(acc, v) end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def to_list(set) do
|
||||
set_fold(set, [], &[&1 | &2]) |> :lists.reverse()
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
@@ -55,6 +63,7 @@ defmodule HashSet do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def subset?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
@@ -65,6 +74,7 @@ defmodule HashSet do
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
@@ -75,15 +85,18 @@ defmodule HashSet do
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def member?(%HashSet{root: root}, term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def put(%HashSet{root: root, size: size}, term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
%HashSet{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def delete(%HashSet{root: root, size: size} = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> %HashSet{root: root, size: size - 1}
|
||||
@@ -100,6 +113,7 @@ defmodule HashSet do
|
||||
end)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def size(%HashSet{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
+26
-21
@@ -5,15 +5,17 @@ alias Code.Identifier
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
data structure into an algebra document. This document is then
|
||||
formatted, either in pretty printing format or a regular one.
|
||||
The `Inspect` protocol converts an Elixir data structure into an
|
||||
algebra document.
|
||||
|
||||
This documentation refers to implementing the `Inspect` protocol
|
||||
for your own data structures. To learn more about using inspect,
|
||||
see `Kernel.inspect/2` and `IO.inspect/2`.
|
||||
|
||||
The `inspect/2` function receives the entity to be inspected
|
||||
followed by the inspecting options, represented by the struct
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
`Inspect.Opts`. Building of the algebra document is done with
|
||||
`Inspect.Algebra`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -262,6 +264,10 @@ defimpl Inspect, for: Integer do
|
||||
|
||||
defp prepend_prefix(value, :decimal), do: value
|
||||
|
||||
defp prepend_prefix(<<?-, value::binary>>, base) do
|
||||
"-" <> prepend_prefix(value, base)
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, base) do
|
||||
prefix =
|
||||
case base do
|
||||
@@ -299,26 +305,25 @@ end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
def inspect(function, _opts) do
|
||||
fun_info = :erlang.fun_info(function)
|
||||
fun_info = Function.info(function)
|
||||
mod = fun_info[:module]
|
||||
name = fun_info[:name]
|
||||
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
inspected_as_atom = Identifier.inspect_as_atom(mod)
|
||||
inspected_as_function = Identifier.inspect_as_function(name)
|
||||
"&#{inspected_as_atom}.#{inspected_as_function}/#{fun_info[:arity]}"
|
||||
else
|
||||
case Atom.to_charlist(mod) do
|
||||
'elixir_compiler_' ++ _ ->
|
||||
if function_exported?(mod, :__RELATIVE__, 0) do
|
||||
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
|
||||
else
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
cond do
|
||||
fun_info[:type] == :external and fun_info[:env] == [] ->
|
||||
inspected_as_atom = Identifier.inspect_as_atom(mod)
|
||||
inspected_as_function = Identifier.inspect_as_function(name)
|
||||
"&#{inspected_as_atom}.#{inspected_as_function}/#{fun_info[:arity]}"
|
||||
|
||||
_ ->
|
||||
match?('elixir_compiler_' ++ _, Atom.to_charlist(mod)) ->
|
||||
if function_exported?(mod, :__RELATIVE__, 0) do
|
||||
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
|
||||
else
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
end
|
||||
|
||||
true ->
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -25,16 +25,19 @@ defmodule Inspect.Opts do
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples,
|
||||
bitstrings, maps, lists and any other collection of items. It does not
|
||||
apply to strings nor charlists and defaults to 50.
|
||||
apply to strings nor charlists and defaults to 50. If you don't want to limit
|
||||
the number of items to a particular number, use `:infinity`.
|
||||
|
||||
* `:printable_limit` - limits the number of bytes that are printed for strings
|
||||
and char lists. Defaults to 4096.
|
||||
and char lists. Defaults to 4096. If you don't want to limit the number of items
|
||||
to a particular number, use `:infinity`.
|
||||
|
||||
* `:pretty` - if set to `true` enables pretty printing, defaults to `false`.
|
||||
|
||||
* `:width` - defaults to 80 characters, used when pretty is `true` or when
|
||||
printing to IO devices. Set to 0 to force each item to be printed on its
|
||||
own line.
|
||||
own line. If you don't want to limit the number of items to a particular
|
||||
number, use `:infinity`.
|
||||
|
||||
* `:base` - prints integers as `:binary`, `:octal`, `:decimal`, or `:hex`,
|
||||
defaults to `:decimal`. When inspecting binaries any `:base` other than
|
||||
@@ -101,7 +104,7 @@ defmodule Inspect.Algebra do
|
||||
additions, like support for binary nodes and a break mode that
|
||||
maximises use of horizontal space.
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
iex> Inspect.Algebra.empty()
|
||||
:doc_nil
|
||||
|
||||
iex> "foo"
|
||||
@@ -110,7 +113,7 @@ defmodule Inspect.Algebra do
|
||||
With the functions in this module, we can concatenate different
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty(), "foo")
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["foo"]
|
||||
|
||||
@@ -229,19 +232,6 @@ defmodule Inspect.Algebra do
|
||||
quote do: {:doc_color, unquote(doc), unquote(color)}
|
||||
end
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if Macro.Env.in_guard?(__CALLER__) do
|
||||
do_is_doc(doc)
|
||||
else
|
||||
var = quote(do: doc)
|
||||
|
||||
quote do
|
||||
unquote(var) = unquote(doc)
|
||||
unquote(do_is_doc(var))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@docs [
|
||||
:doc_string,
|
||||
:doc_cons,
|
||||
@@ -254,12 +244,9 @@ defmodule Inspect.Algebra do
|
||||
:doc_collapse
|
||||
]
|
||||
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
is_binary(unquote(doc)) or unquote(doc) in [:doc_nil, :doc_line] or
|
||||
(is_tuple(unquote(doc)) and elem(unquote(doc), 0) in unquote(@docs))
|
||||
end
|
||||
end
|
||||
defguard is_doc(doc)
|
||||
when is_binary(doc) or doc in [:doc_nil, :doc_line] or
|
||||
(is_tuple(doc) and elem(doc, 0) in @docs)
|
||||
|
||||
# Elixir + Inspect.Opts conveniences
|
||||
|
||||
@@ -276,8 +263,6 @@ defmodule Inspect.Algebra do
|
||||
Inspect.inspect(struct, opts)
|
||||
rescue
|
||||
caught_exception ->
|
||||
stacktrace = System.stacktrace()
|
||||
|
||||
# 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
|
||||
@@ -302,7 +287,7 @@ defmodule Inspect.Algebra do
|
||||
if opts.safe do
|
||||
Inspect.inspect(exception, opts)
|
||||
else
|
||||
reraise(exception, stacktrace)
|
||||
reraise(exception, __STACKTRACE__)
|
||||
end
|
||||
after
|
||||
Process.delete(:inspect_trap)
|
||||
@@ -341,20 +326,21 @@ defmodule Inspect.Algebra do
|
||||
|
||||
iex> doc = Inspect.Algebra.container_doc("[", 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
|
||||
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary()
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.container_doc("[", 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
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary()
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.container_doc("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, separator: "!")
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary()
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec container_doc(t, [any], t, Inspect.Opts.t(), (term, Inspect.Opts.t() -> t), keyword()) ::
|
||||
t
|
||||
def container_doc(left, collection, right, inspect, fun, opts \\ [])
|
||||
@@ -447,7 +433,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
iex> Inspect.Algebra.empty()
|
||||
:doc_nil
|
||||
|
||||
"""
|
||||
@@ -482,6 +468,7 @@ defmodule Inspect.Algebra do
|
||||
["olá", " ", "mundo"]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec string(String.t()) :: doc_string
|
||||
def string(string) when is_binary(string) do
|
||||
doc_string(string, String.length(string))
|
||||
@@ -520,6 +507,7 @@ defmodule Inspect.Algebra do
|
||||
@doc ~S"""
|
||||
Colors a document if the `color_key` has a color in the options.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec color(t, Inspect.Opts.color_key(), Inspect.Opts.t()) :: doc_color
|
||||
def color(doc, color_key, %Inspect.Opts{syntax_colors: syntax_colors}) when is_doc(doc) do
|
||||
if precolor = Keyword.get(syntax_colors, color_key) do
|
||||
@@ -604,6 +592,7 @@ defmodule Inspect.Algebra do
|
||||
Collapse any new lines and whitespace following this
|
||||
node, emitting up to `max` new lines.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec collapse_lines(pos_integer) :: doc_collapse
|
||||
def collapse_lines(max) when is_integer(max) and max > 0 do
|
||||
doc_collapse(max)
|
||||
@@ -650,6 +639,7 @@ defmodule Inspect.Algebra do
|
||||
})
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec next_break_fits(t) :: doc_fits
|
||||
def next_break_fits(doc, mode \\ @next_break_fits)
|
||||
when is_doc(doc) and mode in [:enabled, :disabled] do
|
||||
@@ -659,6 +649,7 @@ defmodule Inspect.Algebra do
|
||||
@doc """
|
||||
Forces the current group to be unfit.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec force_unfit(t) :: doc_force
|
||||
def force_unfit(doc) when is_doc(doc) do
|
||||
doc_force(doc)
|
||||
@@ -692,6 +683,7 @@ defmodule Inspect.Algebra do
|
||||
This function is used by `container_doc/4` and friends to the
|
||||
maximum number of entries on the same line.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec flex_break(binary) :: doc_break
|
||||
def flex_break(string \\ " ") when is_binary(string) do
|
||||
doc_break(string, :flex)
|
||||
@@ -704,6 +696,7 @@ defmodule Inspect.Algebra do
|
||||
This function is used by `container_doc/6` and friends
|
||||
to the maximum number of entries on the same line.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec flex_glue(t, binary, t) :: t
|
||||
def flex_glue(doc1, break_string \\ " ", doc2) when is_binary(break_string) do
|
||||
concat(doc1, concat(flex_break(break_string), doc2))
|
||||
@@ -789,16 +782,18 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> "Hughes",
|
||||
...> Inspect.Algebra.line()
|
||||
...> ), "Wadler"
|
||||
...> )
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", "\n", "Wadler"]
|
||||
iex> doc =
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> "Hughes",
|
||||
...> Inspect.Algebra.line()
|
||||
...> ), "Wadler"
|
||||
...> )
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", "\n", "Wadler"]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec line() :: t
|
||||
def line(), do: :doc_line
|
||||
|
||||
@@ -934,7 +929,7 @@ defmodule Inspect.Algebra do
|
||||
do: fits?(w, k, b?, [{apply_nesting(i, k, j), m, x} | t])
|
||||
|
||||
defp fits?(w, k, b?, [{i, m, doc_cons(x, y)} | t]),
|
||||
do: fits?(w, k, b?, [{i, m, x} | [{i, m, y} | t]])
|
||||
do: fits?(w, k, b?, [{i, m, x}, {i, m, y} | t])
|
||||
|
||||
defp fits?(w, k, b?, [{i, m, doc_group(x, _)} | t]),
|
||||
do: fits?(w, k, b?, [{i, m, x} | {:tail, b?, t}])
|
||||
@@ -943,7 +938,7 @@ defmodule Inspect.Algebra do
|
||||
defp format(_, _, []), do: []
|
||||
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, 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_cons(x, y)} | t]), do: format(w, k, [{i, m, x}, {i, m, y} | t])
|
||||
defp format(w, k, [{i, m, doc_color(x, c)} | t]), do: [ansi(c) | format(w, k, [{i, m, x} | t])]
|
||||
defp format(w, k, [{_, _, doc_string(s, l)} | t]), do: [s | format(w, k + l, t)]
|
||||
defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, k + byte_size(s), t)]
|
||||
|
||||
+18
-15
@@ -1,6 +1,15 @@
|
||||
defmodule Integer do
|
||||
@moduledoc """
|
||||
Functions for working with integers.
|
||||
|
||||
Some functions that work on integers are found in `Kernel`:
|
||||
|
||||
* `abs/2`
|
||||
* `div/2`
|
||||
* `max/2`
|
||||
* `min/2`
|
||||
* `rem/2`
|
||||
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
@@ -72,6 +81,7 @@ defmodule Integer do
|
||||
-2
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec mod(integer, neg_integer | pos_integer) :: integer
|
||||
def mod(dividend, divisor) do
|
||||
remainder = rem(dividend, divisor)
|
||||
@@ -105,6 +115,7 @@ defmodule Integer do
|
||||
-50
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec floor_div(integer, neg_integer | pos_integer) :: integer
|
||||
def floor_div(dividend, divisor) do
|
||||
if dividend * divisor < 0 and rem(dividend, divisor) != 0 do
|
||||
@@ -138,10 +149,7 @@ defmodule Integer do
|
||||
do_digits(integer, base, [])
|
||||
end
|
||||
|
||||
defp do_digits(digit, base, []) when abs(digit) < base, do: [digit]
|
||||
defp do_digits(digit, base, []) when digit == -base, do: [-1, 0]
|
||||
defp do_digits(base, base, []), do: [1, 0]
|
||||
defp do_digits(0, _base, acc), do: acc
|
||||
defp do_digits(integer, base, acc) when abs(integer) < base, do: [integer | acc]
|
||||
|
||||
defp do_digits(integer, base, acc),
|
||||
do: do_digits(div(integer, base), base, [rem(integer, base) | acc])
|
||||
@@ -169,10 +177,6 @@ defmodule Integer do
|
||||
do_undigits(digits, base, 0)
|
||||
end
|
||||
|
||||
defp do_undigits([], _base, 0), do: 0
|
||||
defp do_undigits([digit], base, 0) when is_integer(digit) and digit < base, do: digit
|
||||
defp do_undigits([1, 0], base, 0), do: base
|
||||
defp do_undigits([0 | tail], base, 0), do: do_undigits(tail, base, 0)
|
||||
defp do_undigits([], _base, acc), do: acc
|
||||
|
||||
defp do_undigits([digit | _], base, _) when is_integer(digit) and digit >= base,
|
||||
@@ -192,7 +196,7 @@ defmodule Integer do
|
||||
|
||||
Raises an error if `base` is less than 2 or more than 36.
|
||||
|
||||
If you want to convert a string-formatted integer directly to a integer,
|
||||
If you want to convert a string-formatted integer directly to an integer,
|
||||
`String.to_integer/1` or `String.to_integer/2` can be used instead.
|
||||
|
||||
## Examples
|
||||
@@ -307,7 +311,7 @@ defmodule Integer do
|
||||
iex> Integer.to_string(-100, 16)
|
||||
"-64"
|
||||
|
||||
iex> Integer.to_string(882681651, 36)
|
||||
iex> Integer.to_string(882_681_651, 36)
|
||||
"ELIXIR"
|
||||
|
||||
"""
|
||||
@@ -356,7 +360,7 @@ defmodule Integer do
|
||||
iex> Integer.to_charlist(-100, 16)
|
||||
'-64'
|
||||
|
||||
iex> Integer.to_charlist(882681651, 36)
|
||||
iex> Integer.to_charlist(882_681_651, 36)
|
||||
'ELIXIR'
|
||||
|
||||
"""
|
||||
@@ -394,6 +398,7 @@ defmodule Integer do
|
||||
0
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec gcd(0, 0) :: 0
|
||||
@spec gcd(integer, integer) :: pos_integer
|
||||
def gcd(integer1, integer2) when is_integer(integer1) and is_integer(integer2) do
|
||||
@@ -405,14 +410,12 @@ defmodule Integer do
|
||||
defp gcd_positive(integer1, integer2), do: gcd_positive(integer2, rem(integer1, integer2))
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@doc false
|
||||
@spec to_char_list(integer) :: charlist
|
||||
@deprecated "Use Integer.to_charlist/1 instead"
|
||||
def to_char_list(integer), do: Integer.to_charlist(integer)
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@doc false
|
||||
@spec to_char_list(integer, 2..36) :: charlist
|
||||
@deprecated "Use Integer.to_charlist/2 instead"
|
||||
def to_char_list(integer, base), do: Integer.to_charlist(integer, base)
|
||||
end
|
||||
|
||||
@@ -170,6 +170,33 @@ defmodule IO.ANSI do
|
||||
@doc "Sends cursor home."
|
||||
defsequence(:home, "", "H")
|
||||
|
||||
@doc """
|
||||
Sends cursor to the absolute position specified by `line` and `column`.
|
||||
|
||||
Line `0` and column `0` would mean the top left corner.
|
||||
"""
|
||||
@spec cursor(non_neg_integer, non_neg_integer) :: String.t()
|
||||
def cursor(line, column)
|
||||
when is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
|
||||
"\e[#{line};#{column}H"
|
||||
end
|
||||
|
||||
@doc "Sends cursor `lines` up."
|
||||
@spec cursor_up(pos_integer) :: String.t()
|
||||
def cursor_up(lines \\ 1) when is_integer(lines) and lines >= 1, do: "\e[#{lines}A"
|
||||
|
||||
@doc "Sends cursor `lines` down."
|
||||
@spec cursor_down(pos_integer) :: String.t()
|
||||
def cursor_down(lines \\ 1) when is_integer(lines) and lines >= 1, do: "\e[#{lines}B"
|
||||
|
||||
@doc "Sends cursor `columns` to the right."
|
||||
@spec cursor_right(pos_integer) :: String.t()
|
||||
def cursor_right(columns \\ 1) when is_integer(columns) and columns >= 1, do: "\e[#{columns}C"
|
||||
|
||||
@doc "Sends cursor `columns` to the left."
|
||||
@spec cursor_left(pos_integer) :: String.t()
|
||||
def cursor_left(columns \\ 1) when is_integer(columns) and columns >= 1, do: "\e[#{columns}D"
|
||||
|
||||
@doc "Clears screen."
|
||||
defsequence(:clear, "2", "J")
|
||||
|
||||
|
||||
@@ -7,14 +7,15 @@ defmodule IO.ANSI.Docs do
|
||||
@doc """
|
||||
The default options used by this module.
|
||||
|
||||
The supported values are:
|
||||
The supported keys are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan)
|
||||
* `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow)
|
||||
* `:doc_metadata` - documentation metadata keys (yellow)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - style for table headings
|
||||
* `:doc_table_heading` - the style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
@@ -22,12 +23,14 @@ defmodule IO.ANSI.Docs do
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
@spec default_options() :: keyword
|
||||
def default_options do
|
||||
[
|
||||
enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan],
|
||||
doc_headings: [:yellow],
|
||||
doc_metadata: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
@@ -41,6 +44,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
@spec print_heading(String.t(), keyword) :: :ok
|
||||
def print_heading(heading, options \\ []) do
|
||||
IO.puts(IO.ANSI.reset())
|
||||
options = Keyword.merge(default_options(), options)
|
||||
@@ -51,12 +55,46 @@ defmodule IO.ANSI.Docs do
|
||||
newline_after_block()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints documentation metadata (only `since` and `deprecated` for now).
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
@spec print_metadata(map, keyword) :: :ok
|
||||
def print_metadata(metadata, options \\ []) when is_map(metadata) do
|
||||
options = Keyword.merge(default_options(), options)
|
||||
print_each_metadata(metadata, options) && IO.write("\n")
|
||||
end
|
||||
|
||||
@metadata_filter [:deprecated, :since]
|
||||
|
||||
defp print_each_metadata(metadata, options) do
|
||||
Enum.reduce(metadata, false, fn
|
||||
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
|
||||
label = metadata_label(key, options)
|
||||
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
|
||||
write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true)
|
||||
|
||||
_metadata, printed ->
|
||||
printed
|
||||
end)
|
||||
end
|
||||
|
||||
defp metadata_label(key, options) do
|
||||
if options[:enabled] do
|
||||
"#{color(:doc_metadata, options)}#{key}:#{IO.ANSI.reset()}"
|
||||
else
|
||||
"#{key}:"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the printing string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
defined in `default_options/0`.
|
||||
"""
|
||||
@spec print(String.t(), keyword) :: :ok
|
||||
def print(doc, options \\ []) do
|
||||
options = Keyword.merge(default_options(), options)
|
||||
|
||||
@@ -386,17 +424,22 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, length}, width}, :center}) do
|
||||
ansi_diff = byte_size(col) - length
|
||||
width = width + ansi_diff
|
||||
|
||||
col
|
||||
|> String.pad_leading(div(width, 2) - div(length, 2) + length)
|
||||
|> String.pad_trailing(width + 1 - rem(width, 2))
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, _length}, width}, :right}) do
|
||||
String.pad_leading(col, width)
|
||||
defp generate_table_cell({{{col, length}, width}, :right}) do
|
||||
ansi_diff = byte_size(col) - length
|
||||
String.pad_leading(col, width + ansi_diff)
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, _length}, width}, :left}) do
|
||||
String.pad_trailing(col, width)
|
||||
defp generate_table_cell({{{col, length}, width}, :left}) do
|
||||
ansi_diff = byte_size(col) - length
|
||||
String.pad_trailing(col, width + ansi_diff)
|
||||
end
|
||||
|
||||
defp table_line?(line) do
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
defmodule IO.StreamError do
|
||||
defexception [:reason, :message]
|
||||
|
||||
@impl true
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = IO.iodata_to_binary(:file.format_error(reason))
|
||||
|
||||
+345
-229
File diff suppressed because it is too large
Load Diff
@@ -116,10 +116,9 @@ defmodule Kernel.CLI do
|
||||
exit(reason)
|
||||
|
||||
kind, reason ->
|
||||
stack = System.stacktrace()
|
||||
print_error(kind, reason, stack)
|
||||
print_error(kind, reason, __STACKTRACE__)
|
||||
send(parent, {self(), {:shutdown, 1}})
|
||||
exit(to_exit(kind, reason, stack))
|
||||
exit(to_exit(kind, reason, __STACKTRACE__))
|
||||
else
|
||||
_ ->
|
||||
send(parent, {self(), res})
|
||||
@@ -176,7 +175,7 @@ defmodule Kernel.CLI do
|
||||
" " <> String.replace(string, "\n", "\n ")
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir, :elixir_expand, :elixir_compiler, :elixir_module] ++
|
||||
@elixir_internals [:elixir, :elixir_aliases, :elixir_expand, :elixir_compiler, :elixir_module] ++
|
||||
[:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map] ++
|
||||
[:elixir_erl, :elixir_erl_clauses, :elixir_erl_pass, Kernel.ErrorHandler]
|
||||
|
||||
|
||||
@@ -23,28 +23,19 @@ defmodule Kernel.LexicalTracker do
|
||||
:gen_server.call(to_pid(arg), :remote_dispatches, @timeout)
|
||||
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, {:elixir, :lexical_tracker})
|
||||
val
|
||||
{set, _} = :elixir_module.data_tables(mod)
|
||||
:ets.lookup_element(set, {:elixir, :lexical_tracker}, 2)
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its PID.
|
||||
@doc false
|
||||
def start_link(dest) do
|
||||
:gen_server.start_link(__MODULE__, dest, [])
|
||||
def start_link() do
|
||||
:gen_server.start_link(__MODULE__, :ok, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -125,14 +116,13 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(dest) do
|
||||
def init(:ok) do
|
||||
state = %{
|
||||
directives: %{},
|
||||
references: %{},
|
||||
compile: %{},
|
||||
runtime: %{},
|
||||
structs: %{},
|
||||
dest: dest,
|
||||
cache: %{},
|
||||
file: nil
|
||||
}
|
||||
@@ -159,10 +149,6 @@ defmodule Kernel.LexicalTracker do
|
||||
{:reply, {state.compile, state.runtime}, state}
|
||||
end
|
||||
|
||||
def handle_call(:dest, _from, state) do
|
||||
{:reply, state.dest, state}
|
||||
end
|
||||
|
||||
def handle_call({:read_cache, key}, _from, %{cache: cache} = state) do
|
||||
{:reply, :maps.get(key, cache), state}
|
||||
end
|
||||
|
||||
@@ -14,14 +14,17 @@ defmodule Kernel.ParallelCompiler do
|
||||
See `Task.async/1` for more information. The task spawned must be
|
||||
always awaited on by calling `Task.await/1`
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
def async(fun) when is_function(fun) do
|
||||
if parent = :erlang.get(:elixir_compiler_pid) do
|
||||
file = :erlang.get(:elixir_compiler_file)
|
||||
dest = :erlang.get(:elixir_compiler_dest)
|
||||
{:error_handler, error_handler} = :erlang.process_info(self(), :error_handler)
|
||||
|
||||
Task.async(fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
dest != :undefined and :erlang.put(:elixir_compiler_dest, dest)
|
||||
:erlang.process_flag(:error_handler, error_handler)
|
||||
fun.()
|
||||
end)
|
||||
@@ -69,10 +72,12 @@ defmodule Kernel.ParallelCompiler do
|
||||
`dest`, use `compile_to_path/3` instead.
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
def compile(files, options \\ []) when is_list(options) do
|
||||
spawn_workers(files, :compile, options)
|
||||
end
|
||||
|
||||
@doc since: "1.6.0"
|
||||
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
|
||||
spawn_workers(files, {:compile, path}, options)
|
||||
end
|
||||
@@ -97,6 +102,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
the file, module and the module bytecode
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
def require(files, options \\ []) when is_list(options) do
|
||||
spawn_workers(files, :require, options)
|
||||
end
|
||||
@@ -120,7 +126,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
defp spawn_workers(files, output, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
{:module, _} = :code.ensure_loaded(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
@@ -191,11 +197,13 @@ defmodule Kernel.ParallelCompiler do
|
||||
case output do
|
||||
{:compile, path} ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:elixir_compiler.file_to_path(file, path)
|
||||
:erlang.put(:elixir_compiler_dest, path)
|
||||
:elixir_compiler.file_to_path(Path.expand(file), path)
|
||||
|
||||
:compile ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:elixir_compiler.file(file, dest)
|
||||
:erlang.put(:elixir_compiler_dest, dest)
|
||||
Code.compile_file(file)
|
||||
|
||||
:require ->
|
||||
Code.require_file(file)
|
||||
@@ -204,7 +212,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
:ok
|
||||
catch
|
||||
kind, reason ->
|
||||
{kind, reason, System.stacktrace()}
|
||||
{kind, reason, __STACKTRACE__}
|
||||
end
|
||||
|
||||
send(parent, {:file_done, self(), file, result})
|
||||
@@ -230,19 +238,33 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
|
||||
# Single entry, just release it.
|
||||
defp spawn_workers([], [_] = waiting, [_] = queued, result, warnings, state) do
|
||||
[{_, _, ref, _, _}] = waiting
|
||||
spawn_workers([{ref, :not_found}], waiting, queued, result, warnings, state)
|
||||
end
|
||||
|
||||
# Multiple entries, try to release modules.
|
||||
defp spawn_workers([], waiting, queued, result, warnings, state)
|
||||
when length(waiting) == length(queued) do
|
||||
# The goal of this function is to find leaves in the dependency graph,
|
||||
# i.e. to find code that depends on code that we know is not being defined.
|
||||
# Note we only release modules because those can be rescued. A missing
|
||||
# struct is a guaranteed compile error, so we never release it and treat
|
||||
# it exclusively a missing entry/deadlock.
|
||||
pending =
|
||||
for {pid, _, _, _} <- queued,
|
||||
entry = waiting_on_without_definition(waiting, pid),
|
||||
{_, _, ref, on, _} = entry,
|
||||
{kind, _, ref, on, _} = entry,
|
||||
kind == :module,
|
||||
do: {on, {ref, :not_found}}
|
||||
|
||||
# Instead of releasing all files at once, we release them in groups
|
||||
# based on the module they are waiting on. We pick the module being
|
||||
# depended on with less edges, as it is the mostly likely source of
|
||||
# error (for example, someone made a typo). This may not always be
|
||||
# true though: for example, if there is a macro injecting code into
|
||||
# true though. For example, if there is a macro injecting code into
|
||||
# multiple modules and such code becomes faulty, now multiple modules
|
||||
# are waiting on the same module required by the faulty code. However,
|
||||
# since we need to pick something to be first, the one with fewer edges
|
||||
@@ -356,6 +378,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
{:file_done, child_pid, file, {kind, reason, stack}} ->
|
||||
discard_down(child_pid)
|
||||
print_error(file, kind, reason, stack)
|
||||
cancel_waiting_timer(queued, child_pid)
|
||||
terminate(queued)
|
||||
{:error, [to_error(file, kind, reason, stack)], warnings}
|
||||
|
||||
@@ -395,8 +418,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
{:current_stacktrace, stacktrace} = Process.info(pid, :current_stacktrace)
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
{_kind, ^pid, _, on, _} = List.keyfind(waiting, pid, 1)
|
||||
description = "deadlocked waiting on module #{inspect(on)}"
|
||||
{kind, ^pid, _, on, _} = List.keyfind(waiting, pid, 1)
|
||||
description = "deadlocked waiting on #{kind} #{inspect(on)}"
|
||||
error = CompileError.exception(description: description, file: nil, line: nil)
|
||||
print_error(file, :error, error, stacktrace)
|
||||
|
||||
@@ -418,7 +441,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts([" ", String.pad_leading(file, max), " => " | inspect(mod)])
|
||||
end
|
||||
|
||||
IO.puts("")
|
||||
IO.puts(
|
||||
"\nEnsure there are no compile-time dependencies between those files " <>
|
||||
"and that the modules they reference exist and are correctly named\n"
|
||||
)
|
||||
|
||||
for {file, _, description} <- deadlock, do: {Path.absname(file), nil, description}
|
||||
end
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro unquote(:%{})(args), do: error!([args])
|
||||
|
||||
@doc """
|
||||
Creates a struct.
|
||||
Matches on or builds a struct.
|
||||
|
||||
A struct is a tagged map that allows developers to provide
|
||||
default values for keys, tags to be used in polymorphic
|
||||
@@ -96,16 +96,25 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
%User{} == %{__struct__: User, name: "john", age: 27}
|
||||
|
||||
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:
|
||||
The struct fields can be given when building the struct:
|
||||
|
||||
%User{full_name: "john doe"}
|
||||
%User{age: 31}
|
||||
#=> %{__struct__: User, name: "john", age: 31}
|
||||
|
||||
Or also on pattern matching to extract values out:
|
||||
|
||||
%User{age: age} = user
|
||||
|
||||
An update operation specific for structs is also available:
|
||||
|
||||
%User{user | age: 28}
|
||||
|
||||
The advantage of structs is that they validate 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:
|
||||
|
||||
%User{full_name: "john doe"}
|
||||
|
||||
The syntax above will guarantee the given keys are valid at
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
@@ -116,6 +125,24 @@ defmodule Kernel.SpecialForms do
|
||||
can be used with protocols for polymorphic dispatch. Also
|
||||
see `Kernel.struct/2` and `Kernel.struct!/2` for examples on
|
||||
how to create and update structs dynamically.
|
||||
|
||||
## Pattern matching on struct names
|
||||
|
||||
Besides allowing pattern matching on struct fields, such as:
|
||||
|
||||
%User{age: age} = user
|
||||
|
||||
Structs also allow pattern matching on the struct name:
|
||||
|
||||
%struct_name{} = user
|
||||
struct_name #=> User
|
||||
|
||||
You can also assign the struct name to `_` when you want to
|
||||
check if something is a struct but you are not interested in
|
||||
its name:
|
||||
|
||||
%_{} = user
|
||||
|
||||
"""
|
||||
defmacro unquote(:%)(struct, map), do: error!([struct, map])
|
||||
|
||||
@@ -294,7 +321,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Or as a part of function definitions to pattern match:
|
||||
|
||||
defmodule ImageTyper
|
||||
defmodule ImageTyper do
|
||||
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
@jpg_signature <<255::size(8), 216::size(8)>>
|
||||
@@ -331,7 +358,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
The dot may be used to invoke anonymous functions too:
|
||||
|
||||
iex> (fn(n) -> n end).(7)
|
||||
iex> (fn n -> n end).(7)
|
||||
7
|
||||
|
||||
in which case there is a function on the left hand side.
|
||||
@@ -360,21 +387,18 @@ defmodule Kernel.SpecialForms do
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
|
||||
iex> Kernel."length"([1, 2, 3])
|
||||
iex> Kernel."+"(1, 2)
|
||||
3
|
||||
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
Note that `Kernel."FUNCTION_NAME"` 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 regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
Note that wrapping the function name in single- or double-quotes is always a
|
||||
remote call. Therefore `Kernel."Foo"` will attempt to call the function "Foo"
|
||||
and not return the alias `Kernel.Foo`. This is done by design as module names
|
||||
are more strict than function names.
|
||||
|
||||
When the dot is used to invoke an anonymous function there is only one
|
||||
operand, but it is still written using a postfix notation:
|
||||
|
||||
iex> negate = fn(n) -> -n end
|
||||
iex> negate = fn n -> -n end
|
||||
iex> negate.(7)
|
||||
-7
|
||||
|
||||
@@ -398,15 +422,7 @@ defmodule Kernel.SpecialForms do
|
||||
with the name as first argument, some keyword list as metadata as second,
|
||||
and the list of arguments as third. In this case, the arguments are the
|
||||
alias `String` and the atom `:downcase`. The second argument in a remote call
|
||||
is **always** an atom regardless of the literal used in the call:
|
||||
|
||||
iex> quote do
|
||||
...> String."downcase"("FOO")
|
||||
...> end
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
The tuple containing `:.` is wrapped in another tuple, which actually
|
||||
represents the function call, and has `"FOO"` as argument.
|
||||
is **always** an atom.
|
||||
|
||||
In the case of calls to anonymous functions, the inner tuple with the dot
|
||||
special form has only one argument, reflecting the fact that the operator is
|
||||
@@ -670,6 +686,14 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __CALLER__, do: error!([])
|
||||
|
||||
@doc """
|
||||
Returns the stacktrace for the curently handled exception.
|
||||
|
||||
It is available only in the `catch` and `rescue` clauses of `try/1`
|
||||
expressions.
|
||||
"""
|
||||
defmacro __STACKTRACE__, do: error!([])
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses. Also known as the pin operator.
|
||||
|
||||
@@ -856,7 +880,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Now invoking `square(my_number.())` as before will print the value just
|
||||
Now invoking `squared(my_number.())` as before will print the value just
|
||||
once.
|
||||
|
||||
In fact, this pattern is so common that most of the times you will want
|
||||
@@ -1328,7 +1352,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> for(x <- [1, 1, 2, 3], uniq: true, do: x * 2)
|
||||
[2, 4, 6]
|
||||
|
||||
iex> for(<<x <- "abcabc">>, uniq: true, into: "", do: <<x-32>>)
|
||||
iex> for(<<x <- "abcabc">>, uniq: true, into: "", do: <<x - 32>>)
|
||||
"ABC"
|
||||
|
||||
"""
|
||||
@@ -1341,8 +1365,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
...> {:ok, height} <- Map.fetch(opts, :height) do
|
||||
...> {:ok, width * height}
|
||||
...> end
|
||||
{:ok, 150}
|
||||
|
||||
If all clauses match, the `do` block is executed, returning its result.
|
||||
@@ -1350,15 +1375,17 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> opts = %{width: 10}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
...> {:ok, height} <- Map.fetch(opts, :height) do
|
||||
...> {:ok, width * height}
|
||||
...> end
|
||||
:error
|
||||
|
||||
Guards can be used in patterns as well:
|
||||
|
||||
iex> users = %{"melany" => "guest", "bob" => :admin}
|
||||
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob"),
|
||||
...> do: {:ok, to_string(role)}
|
||||
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob") do
|
||||
...> {:ok, to_string(role)}
|
||||
...> end
|
||||
{:ok, "admin"}
|
||||
|
||||
As in `for/1`, variables bound inside `with/1` won't leak;
|
||||
@@ -1368,8 +1395,9 @@ defmodule Kernel.SpecialForms do
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> double_width = width * 2,
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, double_width * height}
|
||||
...> {:ok, height} <- Map.fetch(opts, :height) do
|
||||
...> {:ok, double_width * height}
|
||||
...> end
|
||||
{:ok, 300}
|
||||
iex> width
|
||||
nil
|
||||
@@ -1380,6 +1408,20 @@ defmodule Kernel.SpecialForms do
|
||||
with :foo = :bar, do: :ok
|
||||
#=> ** (MatchError) no match of right hand side value: :bar
|
||||
|
||||
As with any other function or macro call in Elixir, explicit parens can
|
||||
also be used around the arguments before the `do`/`end` block:
|
||||
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with(
|
||||
...> {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height)
|
||||
...> ) do
|
||||
...> {:ok, width * height}
|
||||
...> end
|
||||
{:ok, 150}
|
||||
|
||||
The choice between parens and no parens is a matter of preference.
|
||||
|
||||
An `else` option can be given to modify what is being returned from
|
||||
`with` in the case of a failed match:
|
||||
|
||||
@@ -1683,7 +1725,9 @@ defmodule Kernel.SpecialForms do
|
||||
pattern matching (similar to the `case` special form).
|
||||
|
||||
Note that calls inside `try/1` are not tail recursive since the VM
|
||||
needs to keep the stacktrace in case an exception happens.
|
||||
needs to keep the stacktrace in case an exception happens. To
|
||||
retrieve the stacktrace, access `__STACKTRACE__/0` inside the `rescue`
|
||||
or `catch` clause.
|
||||
|
||||
## `rescue` clauses
|
||||
|
||||
@@ -1776,19 +1820,19 @@ defmodule Kernel.SpecialForms do
|
||||
allows matching on both the *kind* of the caught value as well as the value
|
||||
itself:
|
||||
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, value
|
||||
IO.puts "Exited with value #{inspect(value)}"
|
||||
end
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, value
|
||||
IO.puts "Exited with value #{inspect(value)}"
|
||||
end
|
||||
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
kind, value when kind in [:exit, :throw] ->
|
||||
IO.puts "Caught exit or throw with value #{inspect(value)}"
|
||||
end
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
kind, value when kind in [:exit, :throw] ->
|
||||
IO.puts "Caught exit or throw with value #{inspect(value)}"
|
||||
end
|
||||
|
||||
The `catch` clause also supports `:error` alongside `:exit` and `:throw` as
|
||||
in Erlang, although this is commonly avoided in favor of `raise`/`rescue` control
|
||||
@@ -1953,10 +1997,10 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
receive do
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
{:selector, number, name} when is_integer(number) ->
|
||||
name
|
||||
name when is_atom(name) ->
|
||||
name
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
end
|
||||
@@ -1965,10 +2009,10 @@ defmodule Kernel.SpecialForms do
|
||||
received after the given timeout period, specified in milliseconds:
|
||||
|
||||
receive do
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
{:selector, number, name} when is_integer(number) ->
|
||||
name
|
||||
name when is_atom(name) ->
|
||||
name
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
after
|
||||
@@ -1981,13 +2025,13 @@ defmodule Kernel.SpecialForms do
|
||||
one of the allowed values:
|
||||
|
||||
* `:infinity` - the process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout
|
||||
message, this is the same as not using the after clause
|
||||
|
||||
* `0` - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately
|
||||
|
||||
* positive integer smaller than `4_294_967_295` (`0xFFFFFFFF`
|
||||
in hex notation) - it should be possible to represent the timeout
|
||||
* positive integer smaller than or equal to `4_294_967_295` (`0xFFFFFFFF`
|
||||
in hexadecimal notation) - it should be possible to represent the timeout
|
||||
value as an unsigned 32-bit integer.
|
||||
|
||||
## Variables handling
|
||||
|
||||
+252
-720
File diff suppressed because it is too large
Load Diff
@@ -125,8 +125,8 @@ defmodule Kernel.Utils do
|
||||
RuntimeError.exception(msg)
|
||||
end
|
||||
|
||||
def raise(atom) when is_atom(atom) do
|
||||
atom.exception([])
|
||||
def raise(module) when is_atom(module) do
|
||||
module.exception([])
|
||||
end
|
||||
|
||||
def raise(%_{__exception__: true} = exception) do
|
||||
@@ -179,6 +179,7 @@ defmodule Kernel.Utils do
|
||||
is_integer(value) and rem(value, 2) == 0
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro defguard(args, expr) do
|
||||
defguard(args, expr, __CALLER__)
|
||||
@@ -187,7 +188,8 @@ defmodule Kernel.Utils do
|
||||
@spec defguard([Macro.t()], Macro.t(), Macro.Env.t()) :: Macro.t()
|
||||
def defguard(args, expr, env) do
|
||||
{^args, vars} = extract_refs_from_args(args)
|
||||
_valid? = :elixir_expand.expand(expr, %{env | context: :guard, vars: vars})
|
||||
env = :elixir_env.with_vars(%{env | context: :guard}, vars)
|
||||
{expr, _scope} = :elixir_expand.expand(expr, env)
|
||||
|
||||
quote do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
@@ -199,19 +201,19 @@ defmodule Kernel.Utils do
|
||||
|
||||
defp extract_refs_from_args(args) do
|
||||
Macro.postwalk(args, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
{var, [{ref, context} | acc]}
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
{var, [{ref, var_context(meta, context)} | acc]}
|
||||
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
# Finds every reference to `refs` in `expr` and wraps them in an unquote.
|
||||
defp unquote_every_ref(expr, refs) do
|
||||
Macro.postwalk(expr, fn
|
||||
{ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs do
|
||||
# Finds every reference to `refs` in `guard` and wraps them in an unquote.
|
||||
defp unquote_every_ref(guard, refs) do
|
||||
Macro.postwalk(guard, fn
|
||||
{ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, var_context(meta, context)} in refs do
|
||||
true -> literal_unquote(var)
|
||||
false -> var
|
||||
end
|
||||
@@ -221,13 +223,15 @@ defmodule Kernel.Utils do
|
||||
end)
|
||||
end
|
||||
|
||||
# Prefaces `expr` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(expr, refs) do
|
||||
{^expr, used_refs} =
|
||||
Macro.postwalk(expr, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs and {ref, context} not in acc do
|
||||
true -> {var, [{ref, context} | acc]}
|
||||
# Prefaces `guard` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(guard, refs) do
|
||||
{_, used_refs} =
|
||||
Macro.postwalk(guard, [], fn
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
pair = {ref, var_context(meta, context)}
|
||||
|
||||
case pair in refs and pair not in acc do
|
||||
true -> {var, [pair | acc]}
|
||||
false -> {var, acc}
|
||||
end
|
||||
|
||||
@@ -235,17 +239,30 @@ defmodule Kernel.Utils do
|
||||
{node, acc}
|
||||
end)
|
||||
|
||||
for {ref, context} <- :lists.reverse(used_refs) do
|
||||
var = {ref, [], context}
|
||||
quote do: unquote(var) = unquote(literal_unquote(var))
|
||||
end ++ List.wrap(expr)
|
||||
vars = for {ref, context} <- :lists.reverse(used_refs), do: context_to_var(ref, context)
|
||||
exprs = for var <- vars, do: literal_unquote(var)
|
||||
|
||||
quote do
|
||||
{unquote_splicing(vars)} = {unquote_splicing(exprs)}
|
||||
unquote(guard)
|
||||
end
|
||||
end
|
||||
|
||||
defp literal_quote(ast) do
|
||||
{:quote, [], [[do: {:__block__, [], List.wrap(ast)}]]}
|
||||
{:quote, [], [[do: ast]]}
|
||||
end
|
||||
|
||||
defp literal_unquote(ast) do
|
||||
{:unquote, [], List.wrap(ast)}
|
||||
end
|
||||
|
||||
defp context_to_var(ref, ctx) when is_atom(ctx), do: {ref, [], ctx}
|
||||
defp context_to_var(ref, ctx) when is_integer(ctx), do: {ref, [counter: ctx], nil}
|
||||
|
||||
defp var_context(meta, kind) do
|
||||
case :lists.keyfind(:counter, 1, meta) do
|
||||
{:counter, counter} -> counter
|
||||
false -> kind
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+67
-31
@@ -20,10 +20,19 @@ defmodule Keyword do
|
||||
iex> [{:active, :once}]
|
||||
[active: :once]
|
||||
|
||||
The two syntaxes are completely equivalent. Note that when keyword
|
||||
lists are passed as the last argument to a function, if the short-hand
|
||||
syntax is used then the square brackets around the keyword list can
|
||||
be omitted as well. For example, the following:
|
||||
The two syntaxes are completely equivalent. If the keyword has foreign
|
||||
characters, such as spaces, you can wrap it in quotes:
|
||||
|
||||
iex> ["exit on close": true]
|
||||
["exit on close": true]
|
||||
|
||||
Wrapping a keyword in quotes does not make it a string. Keywords are
|
||||
always atoms and quotes should only be used to handle foreign characters.
|
||||
In fact, if you attempt use quotes when not necessary, Elixir will warn.
|
||||
|
||||
Note that when keyword lists are passed as the last argument to a function,
|
||||
if the short-hand syntax is used then the square brackets around the keyword list
|
||||
can be omitted as well. For example, the following:
|
||||
|
||||
String.split("1-0", "-", trim: true, parts: 2)
|
||||
|
||||
@@ -133,7 +142,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([:a, :b], fn(x) -> {x, x} end)
|
||||
iex> Keyword.new([:a, :b], fn x -> {x, x} end)
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@@ -456,19 +465,23 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
delete_key_value(keywords, key, value, _deleted? = false)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
case :lists.keymember(key, 1, keywords) do
|
||||
true -> delete_key_value(keywords, key, value)
|
||||
_ -> keywords
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_key_value([{key, value} | rest], key, value, _deleted?),
|
||||
do: delete_key_value(rest, key, value, true)
|
||||
defp delete_key_value([{key, value} | tail], key, value) do
|
||||
delete_key_value(tail, key, value)
|
||||
end
|
||||
|
||||
defp delete_key_value([{_, _} = pair | rest], key, value, deleted?),
|
||||
do: [pair | delete_key_value(rest, key, value, deleted?)]
|
||||
defp delete_key_value([{_, _} = pair | tail], key, value) do
|
||||
[pair | delete_key_value(tail, key, value)]
|
||||
end
|
||||
|
||||
defp delete_key_value([], _key, _value, _deleted? = true), do: []
|
||||
defp delete_key_value([], _key, _value, _deleted? = false), do: throw(:not_deleted)
|
||||
defp delete_key_value([], _key, _value) do
|
||||
[]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
@@ -490,18 +503,23 @@ defmodule Keyword do
|
||||
@spec delete(t, key) :: t
|
||||
@compile {:inline, delete: 2}
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
delete_key(keywords, key, _deleted? = false)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
case :lists.keymember(key, 1, keywords) do
|
||||
true -> delete_key(keywords, key)
|
||||
_ -> keywords
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_key([{key, _} | rest], key, _deleted?), do: delete_key(rest, key, true)
|
||||
defp delete_key([{key, _} | tail], key) do
|
||||
delete_key(tail, key)
|
||||
end
|
||||
|
||||
defp delete_key([{_, _} = pair | rest], key, deleted?),
|
||||
do: [pair | delete_key(rest, key, deleted?)]
|
||||
defp delete_key([{_, _} = pair | tail], key) do
|
||||
[pair | delete_key(tail, key)]
|
||||
end
|
||||
|
||||
defp delete_key([], _key, _deleted? = true), do: []
|
||||
defp delete_key([], _key, _deleted? = false), do: throw(:not_deleted)
|
||||
defp delete_key([], _key) do
|
||||
[]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
@@ -518,14 +536,23 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
delete_first_key(keywords, key)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
case :lists.keymember(key, 1, keywords) do
|
||||
true -> delete_first_key(keywords, key)
|
||||
_ -> keywords
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_first_key([{key, _} | rest], key), do: rest
|
||||
defp delete_first_key([{_, _} = pair | rest], key), do: [pair | delete_first_key(rest, key)]
|
||||
defp delete_first_key([], _key), do: throw(:not_deleted)
|
||||
defp delete_first_key([{key, _} | tail], key) do
|
||||
tail
|
||||
end
|
||||
|
||||
defp delete_first_key([{_, _} = pair | tail], key) do
|
||||
[pair | delete_first_key(tail, key)]
|
||||
end
|
||||
|
||||
defp delete_first_key([], _key) do
|
||||
[]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key`.
|
||||
@@ -598,6 +625,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use Keyword.fetch/2 + Keyword.put/3 instead"
|
||||
def replace(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, _} -> [{key, value} | delete(keywords, key)]
|
||||
@@ -606,8 +634,10 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `replace/3`, but will raise a `KeyError`
|
||||
if the entry `key` does not exist.
|
||||
Alters the value stored under `key` to `value`, but only
|
||||
if the entry `key` already exists in `keywords`.
|
||||
|
||||
If `key` is not present in `keywords`, a `KeyError` exception is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -618,6 +648,7 @@ defmodule Keyword do
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec replace!(t, key, value) :: t
|
||||
def replace!(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
@@ -668,6 +699,11 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(keywords1, keywords2)
|
||||
|
||||
def merge(keywords1, []), do: keywords1
|
||||
def merge([], keywords2), do: keywords2
|
||||
|
||||
def merge(keywords1, keywords2) when is_list(keywords1) and is_list(keywords2) do
|
||||
if keyword?(keywords2) do
|
||||
fun = fn
|
||||
@@ -1010,7 +1046,7 @@ defmodule Keyword do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Kernel.length/1 instead"
|
||||
def size(keyword) do
|
||||
length(keyword)
|
||||
end
|
||||
|
||||
+56
-49
@@ -2,6 +2,19 @@ defmodule List do
|
||||
@moduledoc """
|
||||
Functions that work on (linked) lists.
|
||||
|
||||
Many of the functions provided for lists, which implement
|
||||
the `Enumerable` protocol, are found in the `Enum` module.
|
||||
|
||||
Additionally, the following functions and operators for lists are
|
||||
found in `Kernel`:
|
||||
|
||||
* `++/2`
|
||||
* `--/2`
|
||||
* `hd/1`
|
||||
* `tl/1`
|
||||
* `in/2`
|
||||
* `length/1`
|
||||
|
||||
Lists in Elixir are specified between square brackets:
|
||||
|
||||
iex> [1, "two", 3, :four]
|
||||
@@ -45,17 +58,15 @@ defmodule List do
|
||||
slower as the list grows in size (linear time):
|
||||
|
||||
iex> list = [1, 2, 3]
|
||||
iex> [0 | list] # fast
|
||||
iex> [0 | list] # fast
|
||||
[0, 1, 2, 3]
|
||||
iex> list ++ [4] # slow
|
||||
iex> list ++ [4] # slow
|
||||
[1, 2, 3, 4]
|
||||
|
||||
The `Kernel` module contains many functions to manipulate lists
|
||||
and that are allowed in guards. For example, `Kernel.hd/1` to
|
||||
retrieve the head, `Kernel.tl/1` to fetch the tail and
|
||||
`Kernel.length/1` for calculating the length. Keep in mind that,
|
||||
similar to appending to a list, calculating the length needs to
|
||||
traverse the whole list.
|
||||
Additonally, getting a list's length and accessing it by index are
|
||||
linear time operations. Negative indexes are also supported but
|
||||
they imply the list will be iterated twice, once to calculate the
|
||||
proper index and another time to perform the operation.
|
||||
|
||||
## Charlists
|
||||
|
||||
@@ -85,19 +96,6 @@ defmodule List do
|
||||
|
||||
A list can be checked if it is made of printable ascii
|
||||
codepoints with `ascii_printable?/2`.
|
||||
|
||||
## List and Enum modules
|
||||
|
||||
This module aims to provide operations that are specific
|
||||
to lists, like conversion between data types, updates,
|
||||
deletions and key lookups (for lists of tuples). For traversing
|
||||
lists in general, developers should use the functions in the
|
||||
`Enum` module that work across a variety of data types.
|
||||
|
||||
In both `Enum` and `List` modules, any kind of index access
|
||||
on a list is linear. Negative indexes are also supported but
|
||||
they imply the list will be iterated twice, one to calculate
|
||||
the proper index and another to perform the operation.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -177,10 +175,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldl([5, 5], 10, fn(x, acc) -> x + acc end)
|
||||
iex> List.foldl([5, 5], 10, fn x, acc -> x + acc end)
|
||||
20
|
||||
|
||||
iex> List.foldl([1, 2, 3, 4], 0, fn(x, acc) -> x - acc end)
|
||||
iex> List.foldl([1, 2, 3, 4], 0, fn x, acc -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -195,7 +193,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldr([1, 2, 3, 4], 0, fn(x, acc) -> x - acc end)
|
||||
iex> List.foldr([1, 2, 3, 4], 0, fn x, acc -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
@@ -390,10 +388,10 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Wraps the argument in a list.
|
||||
Wraps `term` in a list if this is not list.
|
||||
|
||||
If the argument is already a list, returns the list.
|
||||
If the argument is `nil`, returns an empty list.
|
||||
If `term` is already a list, it returns the list.
|
||||
If `term` is `nil`, it returns an empty list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -407,7 +405,11 @@ defmodule List do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec wrap(list | any) :: list
|
||||
@spec wrap(nil) :: []
|
||||
@spec wrap(list) :: list when list: maybe_improper_list()
|
||||
@spec wrap(term) :: nonempty_list(term) when term: any()
|
||||
def wrap(term)
|
||||
|
||||
def wrap(list) when is_list(list) do
|
||||
list
|
||||
end
|
||||
@@ -466,6 +468,7 @@ defmodule List do
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
def ascii_printable?(list, counter \\ :infinity)
|
||||
|
||||
def ascii_printable?(_, 0) do
|
||||
@@ -647,6 +650,7 @@ defmodule List do
|
||||
{3, [1, 2]}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec pop_at(list, integer, any) :: {any, list}
|
||||
def pop_at(list, index, default \\ nil) when is_integer(index) do
|
||||
if index < 0 do
|
||||
@@ -676,6 +680,7 @@ defmodule List do
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec starts_with?(list, list) :: boolean
|
||||
@spec starts_with?(list, []) :: true
|
||||
@spec starts_with?([], nonempty_list) :: false
|
||||
@@ -862,16 +867,13 @@ defmodule List do
|
||||
[eq: [1], del: [4], eq: [2, 3], ins: [4]]
|
||||
|
||||
"""
|
||||
@spec myers_difference(list, list) :: [{:eq | :ins | :del, list}] | nil
|
||||
@doc since: "1.4.0"
|
||||
@spec myers_difference(list, list) :: [{:eq | :ins | :del, list}]
|
||||
def myers_difference(list1, list2) when is_list(list1) and is_list(list2) do
|
||||
path = {0, 0, list1, list2, []}
|
||||
path = {0, list1, list2, []}
|
||||
find_script(0, length(list1) + length(list2), [path])
|
||||
end
|
||||
|
||||
defp find_script(envelope, max, _paths) when envelope > max do
|
||||
nil
|
||||
end
|
||||
|
||||
defp find_script(envelope, max, paths) do
|
||||
case each_diagonal(-envelope, envelope, paths, []) do
|
||||
{:done, edits} -> compact_reverse(edits, [])
|
||||
@@ -885,19 +887,24 @@ defmodule List do
|
||||
compact_reverse(rest, [{kind, [elem | result]} | acc])
|
||||
end
|
||||
|
||||
defp compact_reverse(rest, [{:eq, elem}, {:ins, elem}, {:eq, other} | acc]) do
|
||||
compact_reverse(rest, [{:ins, elem}, {:eq, elem ++ other} | acc])
|
||||
end
|
||||
|
||||
defp compact_reverse([{kind, elem} | rest], acc) do
|
||||
compact_reverse(rest, [{kind, [elem]} | acc])
|
||||
end
|
||||
|
||||
defp each_diagonal(diag, limit, _paths, next_paths) when diag > limit do
|
||||
{:next, Enum.reverse(next_paths)}
|
||||
{:next, :lists.reverse(next_paths)}
|
||||
end
|
||||
|
||||
defp each_diagonal(diag, limit, paths, next_paths) do
|
||||
{path, rest} = proceed_path(diag, limit, paths)
|
||||
|
||||
with {:cont, path} <- follow_snake(path) do
|
||||
each_diagonal(diag + 2, limit, rest, [path | next_paths])
|
||||
case follow_snake(path) do
|
||||
{:cont, path} -> each_diagonal(diag + 2, limit, rest, [path | next_paths])
|
||||
{:done, edits} -> {:done, edits}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -912,34 +919,34 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp proceed_path(_diag, _limit, [path1, path2 | rest]) do
|
||||
if elem(path1, 1) > elem(path2, 1) do
|
||||
if elem(path1, 0) > elem(path2, 0) do
|
||||
{move_right(path1), [path2 | rest]}
|
||||
else
|
||||
{move_down(path2), [path2 | rest]}
|
||||
end
|
||||
end
|
||||
|
||||
defp move_right({x, y, list1, [elem | rest], edits}) do
|
||||
{x + 1, y, list1, rest, [{:ins, elem} | edits]}
|
||||
defp move_right({y, list1, [elem | rest], edits}) do
|
||||
{y, list1, rest, [{:ins, elem} | edits]}
|
||||
end
|
||||
|
||||
defp move_right({x, y, list1, [], edits}) do
|
||||
{x + 1, y, list1, [], edits}
|
||||
defp move_right({y, list1, [], edits}) do
|
||||
{y, list1, [], edits}
|
||||
end
|
||||
|
||||
defp move_down({x, y, [elem | rest], list2, edits}) do
|
||||
{x, y + 1, rest, list2, [{:del, elem} | edits]}
|
||||
defp move_down({y, [elem | rest], list2, edits}) do
|
||||
{y + 1, rest, list2, [{:del, elem} | edits]}
|
||||
end
|
||||
|
||||
defp move_down({x, y, [], list2, edits}) do
|
||||
{x, y + 1, [], list2, edits}
|
||||
defp move_down({y, [], list2, edits}) do
|
||||
{y + 1, [], list2, edits}
|
||||
end
|
||||
|
||||
defp follow_snake({x, y, [elem | rest1], [elem | rest2], edits}) do
|
||||
follow_snake({x + 1, y + 1, rest1, rest2, [{:eq, elem} | edits]})
|
||||
defp follow_snake({y, [elem | rest1], [elem | rest2], edits}) do
|
||||
follow_snake({y + 1, rest1, rest2, [{:eq, elem} | edits]})
|
||||
end
|
||||
|
||||
defp follow_snake({_x, _y, [], [], edits}) do
|
||||
defp follow_snake({_y, [], [], edits}) do
|
||||
{:done, edits}
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ defprotocol List.Chars do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use List.Chars.to_charlist/1 instead"
|
||||
Kernel.def to_char_list(term) do
|
||||
__MODULE__.to_charlist(term)
|
||||
end
|
||||
|
||||
+156
-35
@@ -127,6 +127,10 @@ defmodule Macro do
|
||||
raise ArgumentError, bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
def pipe(expr, {:<<>>, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
# {:fn, _, _} is what we get when we pipe into an anonymous function without
|
||||
# calling it, e.g., `:foo |> (fn x -> x end)`.
|
||||
def pipe(expr, {:fn, _, _}, _integer) do
|
||||
@@ -197,6 +201,7 @@ defmodule Macro do
|
||||
[{:var1, [], __MODULE__}, {:var2, [], __MODULE__}]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def generate_arguments(0, _), do: []
|
||||
|
||||
def generate_arguments(amount, context)
|
||||
@@ -351,12 +356,7 @@ defmodule Macro do
|
||||
def decompose_call(_), do: :error
|
||||
|
||||
@doc """
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `escape/2`
|
||||
which leaves `unquote/1` statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
Recursively escapes a value so it can be inserted into a syntax tree.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -369,10 +369,57 @@ defmodule Macro do
|
||||
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
|
||||
1
|
||||
|
||||
## Options
|
||||
|
||||
* `:unquote` - when true, this function leaves `unquote/1` and
|
||||
`unquote_splicing/1` statements unescaped, effectively unquoting
|
||||
the contents on escape. This option is useful only when escaping
|
||||
ASTs which may have quoted fragments in them. Defaults to false.
|
||||
|
||||
* `:prune_metadata` - when true, removes metadata from escaped AST
|
||||
nodes. Note this option changes the semantics of escaped code and
|
||||
it should only be used when escaping ASTs, never values. Defaults
|
||||
to false.
|
||||
|
||||
As an example, `ExUnit` stores the AST of every assertion, so when
|
||||
an assertion fails we can show code snippets to users. Without this
|
||||
option, each time the test module is compiled, we get a different
|
||||
MD5 of the module byte code, because the AST contains metadata,
|
||||
such as counters, specific to the compilation environment. By pruning
|
||||
the metadata, we ensure that the module is deterministic and reduce
|
||||
the amount of data `ExUnit` needs to keep around.
|
||||
|
||||
## Comparison to `Kernel.quote/2`
|
||||
|
||||
The `escape/2` function is sometimes confused with `Kernel.SpecialForms.quote/2`,
|
||||
because the above examples behave the same with both. The key difference is
|
||||
best illustrated when the value to escape is stored in a variable.
|
||||
|
||||
iex> Macro.escape({:a, :b, :c})
|
||||
{:{}, [], [:a, :b, :c]}
|
||||
iex> quote do: {:a, :b, :c}
|
||||
{:{}, [], [:a, :b, :c]}
|
||||
|
||||
iex> value = {:a, :b, :c}
|
||||
iex> Macro.escape(value)
|
||||
{:{}, [], [:a, :b, :c]}
|
||||
|
||||
iex> quote do: value
|
||||
{:value, [], __MODULE__}
|
||||
|
||||
iex> value = {:a, :b, :c}
|
||||
iex> quote do: unquote(value)
|
||||
{:a, :b, :c}
|
||||
|
||||
`escape/2` is used to escape *values* (either directly passed or variable
|
||||
bound), while `Kernel.SpecialForms.quote/2` produces syntax trees for
|
||||
expressions.
|
||||
"""
|
||||
@spec escape(term, keyword) :: Macro.t()
|
||||
def escape(expr, opts \\ []) do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
unquote = Keyword.get(opts, :unquote, false)
|
||||
kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :default
|
||||
elem(:elixir_quote.escape(expr, kind, unquote), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -416,8 +463,8 @@ defmodule Macro do
|
||||
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
|
||||
unless Function.info(fun, :env) == {:env, []} and
|
||||
Function.info(fun, :type) == {:type, :external} do
|
||||
{:error, fun}
|
||||
end
|
||||
end
|
||||
@@ -500,13 +547,21 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Traverse over the arguments using Enum.map/2 instead"
|
||||
def unescape_tokens(tokens) do
|
||||
:elixir_interpolation.unescape_tokens(tokens)
|
||||
case :elixir_interpolation.unescape_tokens(tokens) do
|
||||
{:ok, unescaped_tokens} -> unescaped_tokens
|
||||
{:error, reason} -> raise ArgumentError, to_string(reason)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Traverse over the arguments using Enum.map/2 instead"
|
||||
def unescape_tokens(tokens, map) do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
case :elixir_interpolation.unescape_tokens(tokens, map) do
|
||||
{:ok, unescaped_tokens} -> unescaped_tokens
|
||||
{:error, reason} -> raise ArgumentError, to_string(reason)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -719,7 +774,13 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
def to_string(other, fun) do
|
||||
fun.(other, inspect_no_limit(other))
|
||||
end
|
||||
|
||||
defp inspect_no_limit(value) do
|
||||
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
|
||||
end
|
||||
|
||||
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
|
||||
result =
|
||||
@@ -745,7 +806,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Block keywords
|
||||
kw_keywords = [:do, :catch, :rescue, :after, :else]
|
||||
kw_keywords = [:do, :rescue, :catch, :else, :after]
|
||||
|
||||
defp kw_blocks?([{:do, _} | _] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in unquote(kw_keywords), &1))
|
||||
@@ -773,15 +834,15 @@ defmodule Macro do
|
||||
"\#{" <> to_string(arg, fun) <> "}"
|
||||
|
||||
binary when is_binary(binary) ->
|
||||
binary = inspect(binary, [])
|
||||
:binary.part(binary, 1, byte_size(binary) - 2)
|
||||
binary = inspect_no_limit(binary)
|
||||
binary_part(binary, 1, byte_size(binary) - 2)
|
||||
end)
|
||||
|
||||
<<?", parts::binary, ?">>
|
||||
end
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom) do
|
||||
inspect(atom, [])
|
||||
inspect_no_limit(atom)
|
||||
end
|
||||
|
||||
defp module_to_string({:&, _, [val]} = expr, fun) when not is_integer(val) do
|
||||
@@ -839,11 +900,25 @@ defmodule Macro do
|
||||
:error
|
||||
end
|
||||
|
||||
defp sigil_call({sigil, _, [{:<<>>, _, _} = bin, args]} = ast, fun)
|
||||
defp sigil_call({sigil, _, [{:<<>>, _, _} = parts, args]} = ast, fun)
|
||||
when is_atom(sigil) and is_list(args) do
|
||||
case Atom.to_string(sigil) do
|
||||
<<"sigil_", name>> ->
|
||||
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(bin, fun) <> sigil_args(args, fun))}
|
||||
<<"sigil_", name>> when name >= ?A and name <= ?Z ->
|
||||
{:<<>>, _, [binary]} = parts
|
||||
|
||||
formatted =
|
||||
if :binary.last(binary) == ?\n do
|
||||
binary = String.replace(binary, ~s["""], ~s["\\""])
|
||||
<<?~, name, ~s["""\n], binary::binary, ~s["""], sigil_args(args, fun)::binary>>
|
||||
else
|
||||
{left, right} = select_sigil_container(binary)
|
||||
<<?~, name, left, binary::binary, right, sigil_args(args, fun)::binary>>
|
||||
end
|
||||
|
||||
{:ok, fun.(ast, formatted)}
|
||||
|
||||
<<"sigil_", name>> when name >= ?a and name <= ?z ->
|
||||
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(parts, fun) <> sigil_args(args, fun))}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
@@ -854,6 +929,18 @@ defmodule Macro do
|
||||
:error
|
||||
end
|
||||
|
||||
defp select_sigil_container(binary) do
|
||||
cond do
|
||||
:binary.match(binary, ["\""]) == :nomatch -> {?", ?"}
|
||||
:binary.match(binary, ["\'"]) == :nomatch -> {?', ?'}
|
||||
:binary.match(binary, ["(", ")"]) == :nomatch -> {?(, ?)}
|
||||
:binary.match(binary, ["[", "]"]) == :nomatch -> {?[, ?]}
|
||||
:binary.match(binary, ["{", "}"]) == :nomatch -> {?{, ?}}
|
||||
:binary.match(binary, ["<", ">"]) == :nomatch -> {?<, ?>}
|
||||
true -> {?/, ?/}
|
||||
end
|
||||
end
|
||||
|
||||
defp sigil_args([], _fun), do: ""
|
||||
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
|
||||
|
||||
@@ -952,8 +1039,9 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
Enum.map_join(list, ", ", fn
|
||||
{key, value} -> to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
other -> to_string(other, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -1019,7 +1107,7 @@ defmodule Macro do
|
||||
|
||||
* Macros (local or remote)
|
||||
* Aliases are expanded (if possible) and return atoms
|
||||
* Compilation environment macros (`__ENV__/0`, `__MODULE__/0` and `__DIR__/0`)
|
||||
* Compilation environment macros (`__CALLER__/0`, `__DIR__/0`, `__ENV__/0` and `__MODULE__/0`)
|
||||
* Module attributes reader (`@foo`)
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
@@ -1054,7 +1142,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
The compilation will fail because `My.Module` when quoted
|
||||
is not an atom, but a syntax tree as follow:
|
||||
is not an atom, but a syntax tree as follows:
|
||||
|
||||
{:__aliases__, [], [:My, :Module]}
|
||||
|
||||
@@ -1134,7 +1222,7 @@ defmodule Macro do
|
||||
# Expand possible macro import invocation
|
||||
defp do_expand_once({atom, meta, context} = original, env)
|
||||
when is_atom(atom) and is_list(meta) and is_atom(context) do
|
||||
if :lists.member({atom, Keyword.get(meta, :counter, context)}, env.vars) do
|
||||
if Macro.Env.has_var?(env, {atom, Keyword.get(meta, :counter, context)}) do
|
||||
{original, false}
|
||||
else
|
||||
case do_expand_once({atom, meta, []}, env) do
|
||||
@@ -1148,7 +1236,7 @@ defmodule Macro do
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
arity = length(args)
|
||||
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
if special_form?(atom, arity) do
|
||||
{original, false}
|
||||
else
|
||||
module = env.module
|
||||
@@ -1201,6 +1289,39 @@ defmodule Macro do
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given name and arity is a special form.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec special_form?(name :: atom(), arity()) :: boolean()
|
||||
def special_form?(name, arity) when is_atom(name) and is_integer(arity) do
|
||||
:elixir_import.special_form(name, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given name and arity is an operator.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec operator?(name :: atom(), arity()) :: boolean()
|
||||
def operator?(name, 2) when is_atom(name), do: Identifier.binary_op(name) != :error
|
||||
def operator?(name, 1) when is_atom(name), do: Identifier.unary_op(name) != :error
|
||||
def operator?(name, arity) when is_atom(name) and is_integer(arity), do: false
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given quoted expression is an AST literal.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec quoted_literal?(literal) :: true
|
||||
@spec quoted_literal?(expr) :: false
|
||||
def quoted_literal?(term)
|
||||
|
||||
def quoted_literal?({left, right}), do: quoted_literal?(left) and quoted_literal?(right)
|
||||
def quoted_literal?(list) when is_list(list), do: Enum.all?(list, "ed_literal?/1)
|
||||
|
||||
def quoted_literal?(term) do
|
||||
is_atom(term) or is_number(term) or is_binary(term) or is_function(term)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
@@ -1233,25 +1354,25 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.underscore "FooBar"
|
||||
iex> Macro.underscore("FooBar")
|
||||
"foo_bar"
|
||||
|
||||
iex> Macro.underscore "Foo.Bar"
|
||||
iex> Macro.underscore("Foo.Bar")
|
||||
"foo/bar"
|
||||
|
||||
iex> Macro.underscore Foo.Bar
|
||||
iex> Macro.underscore(Foo.Bar)
|
||||
"foo/bar"
|
||||
|
||||
In general, `underscore` can be thought of as the reverse of
|
||||
`camelize`, however, in some cases formatting may be lost:
|
||||
|
||||
iex> Macro.underscore "SAPExample"
|
||||
iex> Macro.underscore("SAPExample")
|
||||
"sap_example"
|
||||
|
||||
iex> Macro.camelize "sap_example"
|
||||
iex> Macro.camelize("sap_example")
|
||||
"SapExample"
|
||||
|
||||
iex> Macro.camelize "hello_10"
|
||||
iex> Macro.camelize("hello_10")
|
||||
"Hello10"
|
||||
|
||||
"""
|
||||
@@ -1300,15 +1421,15 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.camelize "foo_bar"
|
||||
iex> Macro.camelize("foo_bar")
|
||||
"FooBar"
|
||||
|
||||
If uppercase characters are present, they are not modified in anyway
|
||||
If uppercase characters are present, they are not modified in any way
|
||||
as a mechanism to preserve acronyms:
|
||||
|
||||
iex> Macro.camelize "API.V1"
|
||||
iex> Macro.camelize("API.V1")
|
||||
"API.V1"
|
||||
iex> Macro.camelize "API_SPEC"
|
||||
iex> Macro.camelize("API_SPEC")
|
||||
"API_SPEC"
|
||||
|
||||
"""
|
||||
|
||||
+57
-23
@@ -38,18 +38,19 @@ defmodule Macro.Env do
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `lexical_tracker` - PID of the lexical tracker which is responsible for
|
||||
keeping user info
|
||||
|
||||
The following fields pertain to variable handling and must not be accessed or
|
||||
relied on. To get a list of all variables, see `vars/1`:
|
||||
|
||||
* `current_vars`
|
||||
* `unused_vars`
|
||||
* `prematch_vars`
|
||||
* `contextual_vars`
|
||||
|
||||
The following fields are deprecated and must not be accessed or relied on:
|
||||
|
||||
* `vars` - a list keeping all defined variables as `{var, context}`
|
||||
|
||||
The following fields are private and must not be accessed or relied on:
|
||||
|
||||
* `export_vars` - a list keeping all variables to be exported in a
|
||||
construct (may be `nil`)
|
||||
* `match_vars` - controls how "new" variables are handled. Inside a
|
||||
match it is a list with all variables in a match. Outside of a match
|
||||
is either `:warn` or `:apply`
|
||||
* `prematch_vars` - a list of variables defined before a match (is
|
||||
`nil` when not inside a match)
|
||||
|
||||
"""
|
||||
|
||||
@type name_arity :: {atom, arity}
|
||||
@@ -62,13 +63,16 @@ defmodule Macro.Env do
|
||||
@type functions :: [{module, [name_arity]}]
|
||||
@type macros :: [{module, [name_arity]}]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{atom, atom | non_neg_integer}]
|
||||
@type lexical_tracker :: pid | nil
|
||||
@type local :: atom | nil
|
||||
@type var :: {atom, atom | non_neg_integer}
|
||||
|
||||
@opaque export_vars :: vars | nil
|
||||
@opaque match_vars :: vars | :warn | :apply
|
||||
@opaque prematch_vars :: vars | nil
|
||||
@typep vars :: [var]
|
||||
@typep var_type :: :term
|
||||
@typep var_version :: non_neg_integer
|
||||
@typep unused_vars :: %{{var, var_version} => non_neg_integer | false}
|
||||
@typep current_vars :: %{var => {var_version, var_type}}
|
||||
@typep prematch_vars :: current_vars | :warn | :raise | :pin | :apply
|
||||
@typep contextual_vars :: [atom]
|
||||
|
||||
@type t :: %{
|
||||
__struct__: __MODULE__,
|
||||
@@ -84,12 +88,14 @@ defmodule Macro.Env do
|
||||
macro_aliases: aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
export_vars: export_vars,
|
||||
match_vars: match_vars,
|
||||
unused_vars: unused_vars,
|
||||
current_vars: current_vars,
|
||||
prematch_vars: prematch_vars,
|
||||
lexical_tracker: lexical_tracker
|
||||
lexical_tracker: lexical_tracker,
|
||||
contextual_vars: contextual_vars
|
||||
}
|
||||
|
||||
# TODO: Remove :vars field on v2.0
|
||||
def __struct__ do
|
||||
%{
|
||||
__struct__: __MODULE__,
|
||||
@@ -105,10 +111,11 @@ defmodule Macro.Env do
|
||||
macro_aliases: [],
|
||||
context_modules: [],
|
||||
vars: [],
|
||||
unused_vars: %{},
|
||||
current_vars: %{},
|
||||
prematch_vars: :warn,
|
||||
lexical_tracker: nil,
|
||||
export_vars: nil,
|
||||
match_vars: :warn,
|
||||
prematch_vars: nil
|
||||
contextual_vars: []
|
||||
}
|
||||
end
|
||||
|
||||
@@ -116,6 +123,33 @@ defmodule Macro.Env do
|
||||
Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of variables in the current environment.
|
||||
|
||||
Each variable is identified by a tuple of two elements,
|
||||
where the first element is the variable name as an atom
|
||||
and the second element is its context, which may be an
|
||||
atom or an integer.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec vars(t) :: [var]
|
||||
def vars(env)
|
||||
|
||||
def vars(%{__struct__: Macro.Env, current_vars: current_vars}) do
|
||||
Map.keys(current_vars)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a variable belongs to the environment.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec has_var?(t, var) :: boolean()
|
||||
def has_var?(env, var)
|
||||
|
||||
def has_var?(%{__struct__: Macro.Env, current_vars: current_vars}, var) do
|
||||
Map.has_key?(current_vars, var)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a keyword list containing the file and line
|
||||
information as keys.
|
||||
@@ -135,8 +169,8 @@ defmodule Macro.Env do
|
||||
env
|
||||
end
|
||||
|
||||
def to_match(%{__struct__: Macro.Env, prematch_vars: nil, vars: vars} = env) do
|
||||
%{env | context: :match, match_vars: [], prematch_vars: vars}
|
||||
def to_match(%{__struct__: Macro.Env, current_vars: vars} = env) do
|
||||
%{env | context: :match, prematch_vars: vars}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+48
-28
@@ -2,6 +2,12 @@ defmodule Map do
|
||||
@moduledoc """
|
||||
A set of functions for working with maps.
|
||||
|
||||
Many functions for maps, which implement the `Enumerable` protocol,
|
||||
are found in the `Enum` module. Additionally, the following functions
|
||||
for maps are found in `Kernel`:
|
||||
|
||||
* `map_size/1`
|
||||
|
||||
Maps are the "go to" key-value data structure in Elixir. Maps can be created
|
||||
with the `%{}` syntax, and key-value pairs can be expressed as `key => value`:
|
||||
|
||||
@@ -15,7 +21,7 @@ defmodule Map do
|
||||
|
||||
Maps do not impose any restriction on the key type: anything can be a key in a
|
||||
map. As a key-value structure, maps do not allow duplicated keys. Keys are
|
||||
compared using the exact-equality operator (`===`). If colliding keys are defined
|
||||
compared using the exact-equality operator (`===/2`). If colliding keys are defined
|
||||
in a map literal, the last one prevails.
|
||||
|
||||
When the key in a key-value pair is an atom, the `key: value` shorthand syntax
|
||||
@@ -26,8 +32,8 @@ defmodule Map do
|
||||
%{:a => 1, :b => 2, "hello" => "world"}
|
||||
|
||||
Keys in maps can be accessed through some of the functions in this module
|
||||
(such as `Map.get/3` or `Map.fetch/2`) or through the `[]` syntax provided by
|
||||
the `Access` module:
|
||||
(such as `Map.get/3` or `Map.fetch/2`) or through the `map[]` syntax provided
|
||||
by the `Access` module:
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> Map.fetch(map, :a)
|
||||
@@ -37,10 +43,9 @@ defmodule Map do
|
||||
iex> map["non_existing_key"]
|
||||
nil
|
||||
|
||||
The alternative access syntax `map.key` is provided alongside `[]` when the
|
||||
map has a `:key` key; note that while `map[key]` will return `nil` if `map`
|
||||
doesn't contain `key`, `map.key` will raise if `map` doesn't contain
|
||||
the key `:key`.
|
||||
For accessing atom keys, one may also `map.key`. Note that while `map[key]` will
|
||||
return `nil` if `map` doesn't contain `key`, `map.key` will raise if `map` doesn't
|
||||
contain the key `:key`.
|
||||
|
||||
iex> map = %{foo: "bar", baz: "bong"}
|
||||
iex> map.foo
|
||||
@@ -48,7 +53,13 @@ defmodule Map do
|
||||
iex> map.non_existing_key
|
||||
** (KeyError) key :non_existing_key not found in: %{baz: "bong", foo: "bar"}
|
||||
|
||||
Maps can be pattern matched on; when a map is on the left-hand side of a
|
||||
The two syntaxes for accessing keys reveal the dual nature of maps. The `map[key]`
|
||||
syntax is used for dynamically created maps that may have any key, of any type.
|
||||
`map.key` is used with maps that hold a predetermined set of atoms keys, which are
|
||||
expected to always be present. Structs, defined via `defstruct/1`, are one example
|
||||
of such "static maps", where the keys can also be checked during compile time.
|
||||
|
||||
Maps can be pattern matched on. When a map is on the left-hand side of a
|
||||
pattern match, it will match if the map on the right-hand side contains the
|
||||
keys on the left-hand side and their values match the ones on the left-hand
|
||||
side. This means that an empty map matches every map.
|
||||
@@ -80,16 +91,6 @@ defmodule Map do
|
||||
iex> %{map | three: 3}
|
||||
** (KeyError) key :three not found
|
||||
|
||||
## Modules to work with maps
|
||||
|
||||
This module aims to provide functions that perform operations specific to maps
|
||||
(like accessing keys, updating values, and so on). For traversing maps as
|
||||
collections, developers should use the `Enum` module that works across a
|
||||
variety of data types.
|
||||
|
||||
The `Kernel` module also provides a few functions to work with maps: for
|
||||
example, `Kernel.map_size/1` to know the number of key-value pairs in a map or
|
||||
`Kernel.is_map/1` to know if a term is a map.
|
||||
"""
|
||||
|
||||
@type key :: any
|
||||
@@ -99,6 +100,8 @@ defmodule Map do
|
||||
@doc """
|
||||
Returns all keys from `map`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.keys(%{a: 1, b: 2})
|
||||
@@ -111,6 +114,8 @@ defmodule Map do
|
||||
@doc """
|
||||
Returns all values from `map`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.values(%{a: 1, b: 2})
|
||||
@@ -126,6 +131,8 @@ defmodule Map do
|
||||
Each key-value pair in the map is converted to a two-element tuple `{key,
|
||||
value}` in the resulting list.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.to_list(%{a: 1})
|
||||
@@ -142,7 +149,7 @@ defmodule Map do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new
|
||||
iex> Map.new()
|
||||
%{}
|
||||
|
||||
"""
|
||||
@@ -206,6 +213,8 @@ defmodule Map do
|
||||
@doc """
|
||||
Returns whether the given `key` exists in the given `map`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.has_key?(%{a: 1}, :a)
|
||||
@@ -213,7 +222,6 @@ defmodule Map do
|
||||
iex> Map.has_key?(%{a: 1}, :b)
|
||||
false
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec has_key?(map, key) :: boolean
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
@@ -224,6 +232,8 @@ defmodule Map do
|
||||
If `map` contains the given `key` with value `value`, then `{:ok, value}` is
|
||||
returned. If `map` doesn't contain `key`, `:error` is returned.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.fetch(%{a: 1}, :a)
|
||||
@@ -231,7 +241,6 @@ defmodule Map do
|
||||
iex> Map.fetch(%{a: 1}, :b)
|
||||
:error
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec fetch(map, key) :: {:ok, value} | :error
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
@@ -243,6 +252,8 @@ defmodule Map do
|
||||
If `map` contains the given `key`, the corresponding value is returned. If
|
||||
`map` doesn't contain `key`, a `KeyError` exception is raised.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.fetch!(%{a: 1}, :a)
|
||||
@@ -283,6 +294,7 @@ defmodule Map do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use Map.fetch/2 + Map.put/3 instead"
|
||||
def replace(map, key, value) do
|
||||
case map do
|
||||
%{^key => _value} ->
|
||||
@@ -297,8 +309,12 @@ defmodule Map do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `replace/3`, but will raise a `KeyError`
|
||||
if the key does not exist in the map.
|
||||
Alters the value stored under `key` to `value`, but only
|
||||
if the entry `key` already exists in `map`.
|
||||
|
||||
If `key` is not present in `map`, a `KeyError` exception is raised.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -308,8 +324,8 @@ defmodule Map do
|
||||
iex> Map.replace!(%{a: 1}, :b, 2)
|
||||
** (KeyError) key :b not found in: %{a: 1}
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec replace!(map, key, value) :: map
|
||||
def replace!(map, key, value) do
|
||||
:maps.update(key, value, map)
|
||||
@@ -461,6 +477,8 @@ defmodule Map do
|
||||
@doc """
|
||||
Puts the given `value` under `key` in `map`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.put(%{a: 1}, :b, 2)
|
||||
@@ -468,7 +486,6 @@ defmodule Map do
|
||||
iex> Map.put(%{a: 1, b: 2}, :a, 3)
|
||||
%{a: 3, b: 2}
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec put(map, key, value) :: map
|
||||
def put(map, key, value) do
|
||||
@@ -480,6 +497,8 @@ defmodule Map do
|
||||
|
||||
If the `key` does not exist, returns `map` unchanged.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.delete(%{a: 1, b: 2}, :a)
|
||||
@@ -487,7 +506,6 @@ defmodule Map do
|
||||
iex> Map.delete(%{b: 2}, :a)
|
||||
%{b: 2}
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec delete(map, key) :: map
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
@@ -503,6 +521,8 @@ defmodule Map do
|
||||
side into the struct, even if the key is not part of the struct. Instead,
|
||||
use `Kernel.struct/2`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
|
||||
@@ -740,7 +760,7 @@ defmodule Map do
|
||||
(the retrieved value, which can be operated on before being returned) and the
|
||||
new value to be stored under `key` in the resulting new map. `fun` may also
|
||||
return `:pop`, which means the current value shall be removed from `map` and
|
||||
returned (making this function behave like `Map.pop(map, key)`.
|
||||
returned (making this function behave like `Map.pop(map, key)`).
|
||||
|
||||
The returned value is a tuple with the "get" value returned by
|
||||
`fun` and a new map with the updated value under `key`.
|
||||
@@ -873,7 +893,7 @@ defmodule Map do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Kernel.map_size/1 instead"
|
||||
def size(map) do
|
||||
map_size(map)
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule MapSet do
|
||||
`MapSet` is the "go to" set data structure in Elixir. A set can be constructed
|
||||
using `MapSet.new/0`:
|
||||
|
||||
iex> MapSet.new
|
||||
iex> MapSet.new()
|
||||
#MapSet<[]>
|
||||
|
||||
A set can contain any kind of elements, and elements in a set don't have to be
|
||||
@@ -13,7 +13,7 @@ defmodule MapSet do
|
||||
inserting an element in a set where it's already present, the insertion is
|
||||
simply a no-op.
|
||||
|
||||
iex> map_set = MapSet.new
|
||||
iex> map_set = MapSet.new()
|
||||
iex> MapSet.put(map_set, "foo")
|
||||
#MapSet<["foo"]>
|
||||
iex> map_set |> MapSet.put("foo") |> MapSet.put("foo")
|
||||
@@ -49,7 +49,7 @@ defmodule MapSet do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.new
|
||||
iex> MapSet.new()
|
||||
#MapSet<[]>
|
||||
|
||||
"""
|
||||
@@ -150,7 +150,8 @@ defmodule MapSet do
|
||||
# If the first set is less than twice the size of the second map,
|
||||
# it is fastest to re-accumulate items in the first set that are not
|
||||
# present in the second set.
|
||||
def difference(%MapSet{map: map1}, %MapSet{map: map2}) when map_size(map1) < map_size(map2) * 2 do
|
||||
def difference(%MapSet{map: map1}, %MapSet{map: map2})
|
||||
when map_size(map1) < map_size(map2) * 2 do
|
||||
map =
|
||||
map1
|
||||
|> Map.keys()
|
||||
@@ -209,7 +210,7 @@ defmodule MapSet do
|
||||
@doc """
|
||||
Checks if two sets are equal.
|
||||
|
||||
The comparison between elements must be done using `===`.
|
||||
The comparison between elements must be done using `===/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+483
-281
File diff suppressed because it is too large
Load Diff
@@ -5,47 +5,22 @@
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses ets to track all dependencies
|
||||
# resembling a graph. The graph has the following vertices:
|
||||
# resembling a graph. The keys and what they point to are:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
# * `{:import, name, arity}` - an invoked function/arity import
|
||||
# * `:reattach` - points to reattached functions
|
||||
#
|
||||
# Those vertices can associate to other vertices as described
|
||||
# below:
|
||||
#
|
||||
# * `{name, arity}`
|
||||
# * in neighbours: `:reattach`, `{name, arity}`
|
||||
# * out neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{:import, name, arity}`
|
||||
# * in neighbours: `{name, arity}`
|
||||
# * out neighbours: `Module`
|
||||
# * `:reattach` points to `{name, arity}`
|
||||
# * `{:local, {name, arity}}` points to `{name, arity}`
|
||||
# * `{:import, {name, arity}}` points to `Module`
|
||||
#
|
||||
# This is built on top of the internal module tables.
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Starts the tracker table.
|
||||
"""
|
||||
def init do
|
||||
:ets.new(__MODULE__, [:bag, :public])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the tracker table.
|
||||
"""
|
||||
def delete(d) do
|
||||
:ets.delete(d)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds and tracks defaults for a definition into the tracker.
|
||||
"""
|
||||
def add_defaults(d, _kind, {name, arity}, defaults) do
|
||||
def add_defaults({_set, bag}, _kind, {name, arity} = pair, defaults) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
put_edge(d, {name, i}, {name, arity})
|
||||
put_edge(bag, {:local, {name, i}}, pair)
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -54,57 +29,53 @@ defmodule Module.LocalsTracker do
|
||||
@doc """
|
||||
Adds a local dispatch from-to the given target.
|
||||
"""
|
||||
def add_local(d, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
put_edge(d, from, to)
|
||||
def add_local({_set, bag}, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
if from != to do
|
||||
put_edge(bag, {:local, from}, to)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an import dispatch to the given target.
|
||||
"""
|
||||
def add_import(d, function, module, {name, arity}) when is_tuple(function) and is_atom(module) do
|
||||
tuple = {:import, name, arity}
|
||||
put_edge(d, tuple, module)
|
||||
put_edge(d, function, tuple)
|
||||
def add_import({set, _bag}, function, module, imported)
|
||||
when is_tuple(function) and is_atom(module) do
|
||||
put_edge(set, {:import, imported}, module)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
"""
|
||||
def yank(d, local) do
|
||||
{[], take_out_neighbours(d, local)}
|
||||
def yank({_set, bag}, local) do
|
||||
:lists.usort(take_out_neighbours(bag, {:local, local}))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reattach a previously yanked node.
|
||||
"""
|
||||
def reattach(d, tuple, _kind, function, {in_neigh, out_neigh}) do
|
||||
# Reattach the old function
|
||||
for from <- in_neigh do
|
||||
put_edge(d, from, function)
|
||||
end
|
||||
|
||||
def reattach({_set, bag}, tuple, _kind, function, out_neigh) do
|
||||
for to <- out_neigh do
|
||||
put_edge(d, function, to)
|
||||
put_edge(bag, {:local, function}, to)
|
||||
end
|
||||
|
||||
# Make a call from the old function to the new one
|
||||
if function != tuple do
|
||||
put_edge(d, function, tuple)
|
||||
put_edge(bag, {:local, function}, tuple)
|
||||
end
|
||||
|
||||
# Finally marked the new one as reattached
|
||||
put_edge(d, :reattach, tuple)
|
||||
put_edge(bag, :reattach, tuple)
|
||||
:ok
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@doc false
|
||||
def collect_imports_conflicts(d, all_defined) do
|
||||
for {{name, arity}, _, meta, _} <- all_defined,
|
||||
n = out_neighbours(d, {:import, name, arity}),
|
||||
n != [] do
|
||||
{meta, {n, name, arity}}
|
||||
def collect_imports_conflicts({set, _bag}, all_defined) do
|
||||
for {pair, _, meta, _} <- all_defined, n = out_neighbour(set, {:import, pair}) do
|
||||
{meta, {n, pair}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -113,17 +84,17 @@ defmodule Module.LocalsTracker do
|
||||
given, also accounting the expected number of default
|
||||
clauses a private function have.
|
||||
"""
|
||||
def collect_unused_locals(d, all_defined, private) do
|
||||
def collect_unused_locals({_set, bag}, all_defined, private) do
|
||||
reachable =
|
||||
Enum.reduce(all_defined, %{}, fn {pair, kind, _, _}, acc ->
|
||||
if kind in [:def, :defmacro] do
|
||||
reachable_from(d, pair, acc)
|
||||
reachable_from(bag, pair, acc)
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
reattached = out_neighbours(d, :reattach)
|
||||
reattached = :lists.usort(out_neighbours(bag, :reattach))
|
||||
{unreachable(reachable, reattached, private), collect_warnings(reachable, private)}
|
||||
end
|
||||
|
||||
@@ -190,24 +161,20 @@ defmodule Module.LocalsTracker do
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
def reachable_from(d, vertex) do
|
||||
d
|
||||
|> reachable_from(vertex, %{})
|
||||
def reachable_from({_, bag}, local) do
|
||||
bag
|
||||
|> reachable_from(local, %{})
|
||||
|> Map.keys()
|
||||
end
|
||||
|
||||
defp reachable_from(d, vertex, vertices) do
|
||||
vertices = Map.put(vertices, vertex, true)
|
||||
defp reachable_from(bag, local, vertices) do
|
||||
vertices = Map.put(vertices, local, true)
|
||||
|
||||
Enum.reduce(out_neighbours(d, vertex), vertices, fn
|
||||
{_, _} = local, acc ->
|
||||
case acc do
|
||||
%{^local => true} -> acc
|
||||
_ -> reachable_from(d, local, acc)
|
||||
end
|
||||
|
||||
_, acc ->
|
||||
acc
|
||||
Enum.reduce(out_neighbours(bag, {:local, local}), vertices, fn {_, _} = local, acc ->
|
||||
case acc do
|
||||
%{^local => true} -> acc
|
||||
_ -> reachable_from(bag, local, acc)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -217,6 +184,14 @@ defmodule Module.LocalsTracker do
|
||||
:ets.insert(d, {from, to})
|
||||
end
|
||||
|
||||
defp out_neighbour(d, from) do
|
||||
try do
|
||||
:ets.lookup_element(d, from, 2)
|
||||
catch
|
||||
:error, :badarg -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp out_neighbours(d, from) do
|
||||
try do
|
||||
:ets.lookup_element(d, from, 2)
|
||||
|
||||
@@ -59,6 +59,8 @@ defmodule Node do
|
||||
the local node.
|
||||
|
||||
Same as `list(:visible)`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec list :: [t]
|
||||
def list do
|
||||
@@ -72,6 +74,8 @@ defmodule Node do
|
||||
satisfying the disjunction(s) of the list elements.
|
||||
|
||||
For more information, see `:erlang.nodes/1`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@type state :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(state | [state]) :: [t]
|
||||
|
||||
+97
-102
@@ -34,14 +34,14 @@ defmodule OptionParser do
|
||||
|
||||
When parsing, it is common to list switches and their expected types:
|
||||
|
||||
iex> OptionParser.parse(["--debug"], switches: [debug: :boolean])
|
||||
iex> OptionParser.parse(["--debug"], strict: [debug: :boolean])
|
||||
{[debug: true], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib"], switches: [source: :string])
|
||||
iex> OptionParser.parse(["--source", "lib"], strict: [source: :string])
|
||||
{[source: "lib"], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"],
|
||||
...> switches: [source_path: :string, verbose: :boolean])
|
||||
...> strict: [source_path: :string, verbose: :boolean])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
We will explore the valid switches and operation modes of option parser below.
|
||||
@@ -51,24 +51,24 @@ defmodule OptionParser do
|
||||
The following options are supported:
|
||||
|
||||
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||
* `:allow_nonexistent_atoms` - see the "Parsing dynamic switches" section below
|
||||
* `:allow_nonexistent_atoms` - see the "Parsing unknown switches" section below
|
||||
* `:aliases` - see the "Aliases" section below
|
||||
|
||||
## Switch definitions
|
||||
|
||||
Switches can be specified via one of two options:
|
||||
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
* `:strict` - defines strict switches. Any switch in `argv` that is not
|
||||
specified in the list is returned in the invalid options list.
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
|
||||
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||
is an atom defining the name of the switch and `type` is an atom that
|
||||
specifies the type for the value of this switch (see the "Types" section below
|
||||
for the possible types and more information about type casting).
|
||||
|
||||
Note that you should only supply the `:switches` or the`:strict` option.
|
||||
Note that you should only supply the `:switches` or the `:strict` option.
|
||||
If you supply both, an `ArgumentError` exception will be raised.
|
||||
|
||||
### Types
|
||||
@@ -110,50 +110,52 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
### Parsing dynamic switches
|
||||
### Parsing unknown switches
|
||||
|
||||
`OptionParser` also includes a dynamic mode where it will attempt to parse
|
||||
switches dynamically. Such can be done by not specifying the `:switches` or
|
||||
`:strict` option.
|
||||
When the `:switches` option is given, `OptionParser` will attempt to parse
|
||||
unknown switches:
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
iex> OptionParser.parse(["--debug"], switches: [key: :string])
|
||||
{[debug: true], [], []}
|
||||
|
||||
Even though we haven't specified `--debug` in the list of switches, it is part
|
||||
of the returned options. This would also work:
|
||||
|
||||
iex> OptionParser.parse(["--debug", "value"], switches: [key: :string])
|
||||
{[debug: "value"], [], []}
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string. Switches
|
||||
without an argument, like `--debug` in the examples above, will automatically be
|
||||
set to `true`.
|
||||
without an argument will be set automatically to `true`. Since we cannot assert
|
||||
the type of the switch value, it is preferred to use the `:strict` option that
|
||||
accepts only known switches and always verify their types.
|
||||
|
||||
Since Elixir converts switches to atoms, the dynamic mode will only parse
|
||||
switches that translate to atoms used by the runtime. Therefore, the code below
|
||||
likely won't parse the given option since the `:option_parser_example` atom is
|
||||
never used anywhere:
|
||||
If you do want to parse unknown switches, remember that Elixir converts switches
|
||||
to atoms. Since atoms are not garbage-collected, OptionParser will only parse
|
||||
switches that translate to atoms used by the runtime to avoid leaking atoms.
|
||||
For instance, the code below will discard the `--option-parser-example` switch
|
||||
because the `:option_parser_example` atom is never used anywhere:
|
||||
|
||||
OptionParser.parse(["--option-parser-example"])
|
||||
OptionParser.parse(["--option-parser-example"], switches: [debug: :boolean])
|
||||
# The :option_parser_example atom is not used anywhere below
|
||||
|
||||
However, the code below does since the `:option_parser_example` atom is used
|
||||
at some point later (or earlier) on:
|
||||
However, the code below would work as long as `:option_parser_example` atom is
|
||||
used at some point later (or earlier) **in the same module**:
|
||||
|
||||
{opts, _, _} = OptionParser.parse(["--option-parser-example"])
|
||||
{opts, _, _} = OptionParser.parse(["--option-parser-example"], switches: [debug: :boolean])
|
||||
opts[:option_parser_example]
|
||||
|
||||
In other words, when using dynamic mode, Elixir will do the correct thing and
|
||||
only parse options that are used by the runtime, ignoring all others. If you
|
||||
would like to parse all switches, regardless if they exist or not, you can
|
||||
force creation of atoms by passing `allow_nonexistent_atoms: true` as option.
|
||||
Such option is useful when you are building command-line applications that
|
||||
receive dynamically-named arguments but must be used with care on long-running
|
||||
systems.
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
In other words, Elixir will do the correct thing and only parse options that are
|
||||
used by the runtime, ignoring all others. If you would like to parse all switches,
|
||||
regardless if they exist or not, you can force creation of atoms by passing
|
||||
`allow_nonexistent_atoms: true` as option. Use this option with care. It is only
|
||||
useful when you are building command-line applications that receive
|
||||
dynamically-named arguments and must be avoided in long-running systems.
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be specified in the `:aliases` option:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug], strict: [debug: :boolean])
|
||||
{[debug: true], [], []}
|
||||
|
||||
## Examples
|
||||
@@ -279,6 +281,7 @@ defmodule OptionParser do
|
||||
** (OptionParser.ParseError) 2 errors found!
|
||||
--verbose : Missing argument of type integer
|
||||
--source : Expected type integer, got "lib"
|
||||
|
||||
"""
|
||||
@spec parse_head!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -305,8 +308,8 @@ defmodule OptionParser do
|
||||
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], all?)
|
||||
invalid = if config.strict?, do: [{option, nil} | invalid], else: invalid
|
||||
do_parse(rest, config, opts, args, invalid, all?)
|
||||
|
||||
{:error, ["--" | rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
@@ -335,7 +338,7 @@ defmodule OptionParser do
|
||||
(returned when the value cannot be parsed according to the switch type)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned in strict mode when the switch is unknown)
|
||||
(returned in strict mode when the switch is unknown or on nonexistent atoms)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||
|
||||
@@ -369,15 +372,21 @@ defmodule OptionParser do
|
||||
# Handles --foo or --foo=bar
|
||||
defp next_with_config(["--" <> option | rest], config) do
|
||||
{option, value} = split_option(option)
|
||||
tagged = tag_option(option, config)
|
||||
next_tagged(tagged, value, "--" <> option, rest, config)
|
||||
|
||||
if String.contains?(option, ["_"]) do
|
||||
{:undefined, "--" <> option, value, rest}
|
||||
else
|
||||
tagged = tag_option(option, config)
|
||||
next_tagged(tagged, value, "--" <> option, rest, config)
|
||||
end
|
||||
end
|
||||
|
||||
# Handles -a, -abc, -abc=something
|
||||
defp next_with_config(["-" <> option | rest] = argv, config) do
|
||||
%{aliases: aliases, allow_nonexistent_atoms?: allow_nonexistent_atoms?} = config
|
||||
%{allow_nonexistent_atoms?: allow_nonexistent_atoms?} = config
|
||||
{option, value} = split_option(option)
|
||||
original = "-" <> option
|
||||
letters = String.graphemes(option)
|
||||
|
||||
cond do
|
||||
is_nil(value) and negative_number?(original) ->
|
||||
@@ -386,21 +395,22 @@ defmodule OptionParser do
|
||||
String.contains?(option, ["-", "_"]) ->
|
||||
{:undefined, original, value, rest}
|
||||
|
||||
String.length(option) > 1 ->
|
||||
key = get_option_key(option, allow_nonexistent_atoms?)
|
||||
option_key = aliases[key]
|
||||
|
||||
if key && option_key do
|
||||
IO.warn("multi-letter aliases are deprecated, got: #{inspect(key)}")
|
||||
next_tagged({:default, option_key}, value, original, rest, config)
|
||||
else
|
||||
next_with_config(expand_multiletter_alias(option, value) ++ rest, config)
|
||||
end
|
||||
|
||||
true ->
|
||||
tl(letters) == [] ->
|
||||
# We have a regular one-letter alias here
|
||||
tagged = tag_oneletter_alias(option, config)
|
||||
next_tagged(tagged, value, original, rest, config)
|
||||
|
||||
true ->
|
||||
key = get_option_key(option, allow_nonexistent_atoms?)
|
||||
option_key = config.aliases[key]
|
||||
|
||||
if key && option_key do
|
||||
# TODO: Remove this in Elixir v2.0
|
||||
IO.warn("multi-letter aliases are deprecated, got: #{inspect(key)}")
|
||||
next_tagged({:default, option_key}, value, original, rest, config)
|
||||
else
|
||||
next_with_config(expand_multiletter_alias(letters, value) ++ rest, config)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -408,12 +418,17 @@ defmodule OptionParser do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next_tagged(tagged, value, original, rest, %{switches: switches, strict?: strict?}) do
|
||||
if strict? and not option_defined?(tagged, switches) do
|
||||
defp next_tagged(:unknown, value, original, rest, _) do
|
||||
{value, _kinds, rest} = normalize_value(value, [], rest)
|
||||
{:undefined, original, value, rest}
|
||||
end
|
||||
|
||||
defp next_tagged({tag, option}, value, original, rest, %{switches: switches, strict?: strict?}) do
|
||||
if strict? and not Keyword.has_key?(switches, option) do
|
||||
{:undefined, original, value, rest}
|
||||
else
|
||||
{option, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict?)
|
||||
{kinds, value} = normalize_tag(tag, option, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest)
|
||||
|
||||
case validate_option(value, kinds) do
|
||||
{:ok, new_value} -> {:ok, option, new_value, rest}
|
||||
@@ -436,9 +451,9 @@ defmodule OptionParser do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.to_argv([foo_bar: "baz"])
|
||||
iex> OptionParser.to_argv(foo_bar: "baz")
|
||||
["--foo-bar", "baz"]
|
||||
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
|
||||
iex> OptionParser.to_argv(bool: true, bool: false, discarded: nil)
|
||||
["--bool", "--no-bool"]
|
||||
|
||||
Some switches will output different values based on the switches
|
||||
@@ -543,6 +558,8 @@ defmodule OptionParser do
|
||||
{strict, true}
|
||||
|
||||
true ->
|
||||
# TODO: Remove this in Elixir v2.0
|
||||
IO.warn("not passing the :switches or :strict option to OptionParser is deprecated")
|
||||
{[], false}
|
||||
end
|
||||
|
||||
@@ -569,7 +586,7 @@ defmodule OptionParser do
|
||||
|
||||
:count in kinds ->
|
||||
case value do
|
||||
1 -> {false, value}
|
||||
nil -> {false, 1}
|
||||
_ -> {true, value}
|
||||
end
|
||||
|
||||
@@ -609,10 +626,9 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option("no-" <> option = original, %{
|
||||
switches: switches,
|
||||
allow_nonexistent_atoms?: allow_nonexistent_atoms?
|
||||
}) do
|
||||
defp tag_option("no-" <> option = original, config) do
|
||||
%{switches: switches, allow_nonexistent_atoms?: allow_nonexistent_atoms?} = config
|
||||
|
||||
cond do
|
||||
(negated = get_option_key(option, allow_nonexistent_atoms?)) &&
|
||||
:boolean in List.wrap(switches[negated]) ->
|
||||
@@ -626,7 +642,9 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option(option, %{allow_nonexistent_atoms?: allow_nonexistent_atoms?}) do
|
||||
defp tag_option(option, config) do
|
||||
%{allow_nonexistent_atoms?: allow_nonexistent_atoms?} = config
|
||||
|
||||
if option_key = get_option_key(option, allow_nonexistent_atoms?) do
|
||||
{:default, option_key}
|
||||
else
|
||||
@@ -634,11 +652,9 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_oneletter_alias(alias, %{
|
||||
aliases: aliases,
|
||||
allow_nonexistent_atoms?: allow_nonexistent_atoms?
|
||||
})
|
||||
when is_binary(alias) do
|
||||
defp tag_oneletter_alias(alias, config) when is_binary(alias) do
|
||||
%{aliases: aliases, allow_nonexistent_atoms?: allow_nonexistent_atoms?} = config
|
||||
|
||||
if option_key = aliases[to_existing_key(alias, allow_nonexistent_atoms?)] do
|
||||
{:default, option_key}
|
||||
else
|
||||
@@ -646,59 +662,39 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_multiletter_alias(letters, value) when is_binary(letters) do
|
||||
defp expand_multiletter_alias(letters, value) do
|
||||
{last, expanded} =
|
||||
letters
|
||||
|> String.codepoints()
|
||||
|> Enum.map(&("-" <> &1))
|
||||
|> List.pop_at(-1)
|
||||
|
||||
expanded ++ [last <> if(value, do: "=" <> value, else: "")]
|
||||
end
|
||||
|
||||
defp option_defined?(:unknown, _switches) do
|
||||
false
|
||||
end
|
||||
|
||||
defp option_defined?({:negated, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp option_defined?({:default, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp normalize_option(:unknown, value, _switches) do
|
||||
{nil, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, value, switches) do
|
||||
defp normalize_tag(:negated, option, value, switches) do
|
||||
if value do
|
||||
{option, [:invalid], value}
|
||||
{[:invalid], value}
|
||||
else
|
||||
{option, List.wrap(switches[option]), false}
|
||||
{List.wrap(switches[option]), false}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
defp normalize_tag(:default, option, value, switches) do
|
||||
{List.wrap(switches[option]), value}
|
||||
end
|
||||
|
||||
defp normalize_value(nil, kinds, t, strict?) do
|
||||
defp normalize_value(nil, kinds, t) do
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
|
||||
:count in kinds ->
|
||||
{1, kinds, t}
|
||||
{nil, kinds, t}
|
||||
|
||||
value_in_tail?(t) ->
|
||||
[h | t] = t
|
||||
{h, kinds, t}
|
||||
|
||||
kinds == [] and strict? ->
|
||||
{nil, kinds, t}
|
||||
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
|
||||
@@ -707,7 +703,7 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_value(value, kinds, t, _strict?) do
|
||||
defp normalize_value(value, kinds, t) do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
@@ -725,15 +721,14 @@ defmodule OptionParser do
|
||||
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_key(option, allow_nonexistent_atoms?) do
|
||||
if string = to_underscore(option) do
|
||||
to_existing_key(string, allow_nonexistent_atoms?)
|
||||
end
|
||||
option
|
||||
|> to_underscore()
|
||||
|> to_existing_key(allow_nonexistent_atoms?)
|
||||
end
|
||||
|
||||
defp to_existing_key(option, true), do: String.to_atom(option)
|
||||
|
||||
+14
-8
@@ -592,24 +592,23 @@ defmodule Path do
|
||||
Traverses paths according to the given `glob` expression and returns a
|
||||
list of matches.
|
||||
|
||||
The wildcard looks like an ordinary path, except that certain
|
||||
"wildcard characters" are interpreted in a special way. The
|
||||
following characters are special:
|
||||
The wildcard looks like an ordinary path, except that the following
|
||||
"wildcard characters" are interpreted in a special way:
|
||||
|
||||
* `?` - matches one character
|
||||
* `?` - matches one character.
|
||||
|
||||
* `*` - matches any number of characters up to the end of the filename, the
|
||||
next dot, or the next slash
|
||||
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
|
||||
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.
|
||||
Do not add spaces before and after the comma as it would then match
|
||||
paths containing the space character itself.
|
||||
|
||||
* `{item1,item2,...}` - matches one of the alternatives
|
||||
* `{item1,item2,...}` - matches one of the alternatives.
|
||||
Do not add spaces before and after the comma as it would then match
|
||||
paths containing the space character itself.
|
||||
|
||||
@@ -618,7 +617,14 @@ defmodule Path do
|
||||
that matching is case-sensitive: `"a"` will not match `"A"`.
|
||||
|
||||
By default, the patterns `*` and `?` do not match files starting
|
||||
with a dot `.` unless `match_dot: true` is given in `opts`.
|
||||
with a dot `.`. See the `:match_dot` option in the "Options" section
|
||||
below.
|
||||
|
||||
## Options
|
||||
|
||||
* `:match_dot` - (boolean) if `false`, the special wildcard characters `*` and `?`
|
||||
will not match files starting with a dot (`.`). If `true`, files starting with
|
||||
a `.` will not be treated specially. Defaults to `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+11
-8
@@ -8,12 +8,12 @@ defmodule Port do
|
||||
## Example
|
||||
|
||||
iex> port = Port.open({:spawn, "cat"}, [:binary])
|
||||
iex> send port, {self(), {:command, "hello"}}
|
||||
iex> send port, {self(), {:command, "world"}}
|
||||
iex> send(port, {self(), {:command, "hello"}})
|
||||
iex> send(port, {self(), {:command, "world"}})
|
||||
iex> flush()
|
||||
{#Port<0.1444>, {:data, "hello"}}
|
||||
{#Port<0.1444>, {:data, "world"}}
|
||||
iex> send port, {self(), :close}
|
||||
iex> send(port, {self(), :close})
|
||||
:ok
|
||||
iex> flush()
|
||||
{#Port<0.1464>, :closed}
|
||||
@@ -117,7 +117,7 @@ defmodule Port do
|
||||
reimplementing core part of the Runtime System, such as the `:user` and
|
||||
`:shell` processes.
|
||||
|
||||
## Zombie processes
|
||||
## Zombie OS processes
|
||||
|
||||
A port can be closed via the `close/1` function or by sending a `{pid, :close}`
|
||||
message. However, if the VM crashes, a long-running program started by the port
|
||||
@@ -132,7 +132,7 @@ defmodule Port do
|
||||
script in bash:
|
||||
|
||||
#!/bin/sh
|
||||
"$@"
|
||||
"$@" &
|
||||
pid=$!
|
||||
while read line ; do
|
||||
:
|
||||
@@ -177,8 +177,8 @@ defmodule Port do
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec open(name, list) :: port
|
||||
def open(name, settings) do
|
||||
:erlang.open_port(name, settings)
|
||||
def open(name, options) do
|
||||
:erlang.open_port(name, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,8 +236,9 @@ defmodule Port do
|
||||
|
||||
def info(port, :registered_name) do
|
||||
case :erlang.port_info(port, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, []}
|
||||
other -> nillify(other)
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@@ -264,6 +265,7 @@ defmodule Port do
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec monitor(port | {name :: atom, node :: atom} | name :: atom) :: reference
|
||||
def monitor(port) do
|
||||
:erlang.monitor(:port, port)
|
||||
@@ -280,6 +282,7 @@ defmodule Port do
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
|
||||
defdelegate demonitor(monitor_ref, options \\ []), to: :erlang
|
||||
|
||||
|
||||
@@ -20,16 +20,25 @@ defmodule Process do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Tells whether the given process is alive.
|
||||
Tells whether the given process is alive on the local node.
|
||||
|
||||
If the process identified by `pid` is alive (that is, it's not exiting and has
|
||||
not exited yet) than this function returns `true`. Otherwise, it returns
|
||||
`false`.
|
||||
|
||||
`pid` must refer to a process running on the local node.
|
||||
`pid` must refer to a process running on the local node or `ArgumentError` is raised.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
A process destination.
|
||||
|
||||
A remote or local PID, a local port, a locally registered name, or a tuple in
|
||||
the form of `{registered_name, node}` for a registered name at another node.
|
||||
"""
|
||||
@type dest :: pid | port | registered_name :: atom | {registered_name :: atom, node}
|
||||
|
||||
@spec alive?(pid) :: boolean
|
||||
defdelegate alive?(pid), to: :erlang, as: :is_process_alive
|
||||
|
||||
@@ -220,7 +229,13 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given process.
|
||||
Sends a message to the given `dest`.
|
||||
|
||||
`dest` may be a remote or local PID, a local port, a locally
|
||||
registered name, or a tuple in the form of `{registered_name, node}` for a
|
||||
registered name at another node.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -238,10 +253,9 @@ defmodule Process do
|
||||
iex> Process.send({:name, :node_that_does_not_exist}, :hi, [:noconnect])
|
||||
:noconnect
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend
|
||||
when dest: pid | port | atom | {atom, node},
|
||||
when dest: dest(),
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend
|
||||
defdelegate send(dest, msg, options), to: :erlang
|
||||
@@ -296,6 +310,8 @@ defmodule Process do
|
||||
Even if the timer had expired and the message was sent, this function does not
|
||||
tell you if the timeout message has arrived at its destination yet.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Options
|
||||
|
||||
* `:async` - (boolean) when `false`, the request for cancellation is
|
||||
@@ -313,7 +329,6 @@ defmodule Process do
|
||||
cancellation has been performed. If `:async` is `true` and `:info` is
|
||||
`false`, no message is sent. Defaults to `true`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec cancel_timer(reference, options) :: non_neg_integer | false | :ok
|
||||
when options: [async: boolean, info: boolean]
|
||||
@@ -394,6 +409,9 @@ defmodule Process do
|
||||
a PID) or `{name, node}` (if monitoring a remote or local name);
|
||||
* `reason` is the exit reason.
|
||||
|
||||
If the process is already dead when calling `Process.monitor/1`, a
|
||||
`:DOWN` message is delivered immediately.
|
||||
|
||||
See [the need for monitoring](http://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
|
||||
for an example. See `:erlang.monitor/2` for more info.
|
||||
|
||||
@@ -578,8 +596,6 @@ defmodule Process do
|
||||
|
||||
See `:erlang.process_flag/2` for more info.
|
||||
|
||||
Note that `flag` values `:max_heap_size` and `:message_queue_data` are only available since OTP 19.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec flag(:error_handler, module) :: module
|
||||
@@ -620,7 +636,7 @@ defmodule Process do
|
||||
|
||||
See `:erlang.process_info/1` for more info.
|
||||
"""
|
||||
@spec info(pid) :: keyword
|
||||
@spec info(pid) :: keyword | nil
|
||||
def info(pid) do
|
||||
nillify(:erlang.process_info(pid))
|
||||
end
|
||||
|
||||
+12
-30
@@ -44,7 +44,7 @@ defmodule Protocol do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.__spec__?(__MODULE__, name, arity) ||
|
||||
Module.spec_to_callback(__MODULE__, {name, arity}) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
@@ -309,7 +309,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:abstract_code, :attributes, :compile_info, 'ExDc', 'ExDp']
|
||||
chunk_ids = [:abstract_code, :attributes, :compile_info, 'Docs', 'ExDp']
|
||||
opts = [:allow_missing_chunks]
|
||||
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
@@ -317,14 +317,16 @@ defmodule Protocol do
|
||||
[
|
||||
{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{:compile_info, compile_info},
|
||||
{'ExDc', docs},
|
||||
{'ExDp', deprecated}
|
||||
{:compile_info, compile_info} | extra_chunks
|
||||
] = entries
|
||||
|
||||
extra_chunks =
|
||||
for {name, contents} when is_binary(contents) <- extra_chunks,
|
||||
do: {List.to_string(name), contents}
|
||||
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any] ->
|
||||
{:ok, {protocol, any, abstract_code}, {compile_info, docs, deprecated}}
|
||||
{:ok, {protocol, any, abstract_code}, {compile_info, extra_chunks}}
|
||||
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
@@ -493,15 +495,11 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile(protocol, code, {compile_info, docs, deprecated}) do
|
||||
defp compile(protocol, code, {compile_info, extra_chunks}) do
|
||||
opts = Keyword.take(compile_info, [:source])
|
||||
opts = if Code.compiler_options()[:debug_info], do: [:debug_info | opts], else: opts
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return | opts])
|
||||
|
||||
case docs do
|
||||
:missing_chunk -> {:ok, binary}
|
||||
_ -> {:ok, :elixir_erl.add_beam_chunks(binary, [{"ExDc", docs}, {"ExDp", deprecated}])}
|
||||
end
|
||||
{:ok, :elixir_erl.add_beam_chunks(binary, extra_chunks)}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
@@ -614,7 +612,7 @@ defmodule Protocol do
|
||||
# Inline struct implementation for performance
|
||||
@compile {:inline, struct_impl_for: 1}
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
unless Module.defines_type?(__MODULE__, {:t, 0}) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
@@ -717,7 +715,7 @@ defmodule Protocol do
|
||||
assert_impl!(protocol, Any, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
env = :elixir_env.reset_vars(env)
|
||||
args = [for, struct, opts]
|
||||
impl = Module.concat(protocol, Any)
|
||||
|
||||
@@ -759,22 +757,6 @@ defmodule Protocol do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
|
||||
mapper = fn {:spec, expr, pos} ->
|
||||
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Module.store_typespec(module, :callback, {:callback, expr, pos})
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
found = :lists.map(mapper, specs)
|
||||
:lists.any(&(&1 == true), found)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -59,7 +59,7 @@ defmodule Range do
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@spec range?(term) :: boolean
|
||||
@deprecated "Pattern match on first..last instead"
|
||||
def range?(term)
|
||||
def range?(first..last) when is_integer(first) and is_integer(last), do: true
|
||||
def range?(_), do: false
|
||||
@@ -70,20 +70,20 @@ defimpl Enumerable, for: Range do
|
||||
reduce(first, last, acc, fun, _up? = last >= first)
|
||||
end
|
||||
|
||||
defp reduce(_x, _y, {:halt, acc}, _fun, _up?) do
|
||||
defp reduce(_first, _last, {:halt, acc}, _fun, _up?) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp reduce(x, y, {:suspend, acc}, fun, up?) do
|
||||
{:suspended, acc, &reduce(x, y, &1, fun, up?)}
|
||||
defp reduce(first, last, {:suspend, acc}, fun, up?) do
|
||||
{:suspended, acc, &reduce(first, last, &1, fun, up?)}
|
||||
end
|
||||
|
||||
defp reduce(x, y, {:cont, acc}, fun, _up? = true) when x <= y do
|
||||
reduce(x + 1, y, fun.(x, acc), fun, _up? = true)
|
||||
defp reduce(first, last, {:cont, acc}, fun, _up? = true) when first <= last do
|
||||
reduce(first + 1, last, fun.(first, acc), fun, _up? = true)
|
||||
end
|
||||
|
||||
defp reduce(x, y, {:cont, acc}, fun, _up? = false) when x >= y do
|
||||
reduce(x - 1, y, fun.(x, acc), fun, _up? = false)
|
||||
defp reduce(first, last, {:cont, acc}, fun, _up? = false) when first >= last do
|
||||
reduce(first - 1, last, fun.(first, acc), fun, _up? = false)
|
||||
end
|
||||
|
||||
defp reduce(_, _, {:cont, acc}, _fun, _up) do
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
iex> Record.is_record({User, "john", 27})
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -54,11 +54,20 @@ defmodule Record do
|
||||
that contains the record definition to extract; with this option, this
|
||||
function uses the same path lookup used by the `-include` attribute used in
|
||||
Erlang modules.
|
||||
|
||||
* `:from_lib` - (binary representing a path to a file) path to the Erlang
|
||||
file that contains the record definition to extract; with this option,
|
||||
this function uses the same path lookup used by the `-include_lib`
|
||||
attribute used in Erlang modules.
|
||||
|
||||
* `:includes` - (a list of directories as binaries) if the record being
|
||||
extracted depends on relative includes, this option allows developers
|
||||
to specify the directory those relative includes exist
|
||||
|
||||
* `:macros` - (keyword list of macro names and values) if the record
|
||||
being extract depends on the values of macros, this option allows
|
||||
the value of those macros to be set
|
||||
|
||||
These options are expected to be literals (including the binary values) at
|
||||
compile time.
|
||||
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
defmodule Record.Extractor do
|
||||
@moduledoc false
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
extract_record(name, from_file(file))
|
||||
def extract(name, opts) do
|
||||
extract_record(name, from_or_from_lib_file(opts))
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
extract_record(name, from_lib_file(file))
|
||||
def extract_all(opts) do
|
||||
extract_all_records(from_or_from_lib_file(opts))
|
||||
end
|
||||
|
||||
# Retrieve all records definitions from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract_all(from: file) when is_binary(file) do
|
||||
extract_all_records(from_file(file))
|
||||
end
|
||||
defp from_or_from_lib_file(opts) do
|
||||
cond do
|
||||
file = opts[:from] ->
|
||||
{from_file(file), Keyword.delete(opts, :from)}
|
||||
|
||||
# Retrieve all records definitions from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract_all(from_lib: file) when is_binary(file) do
|
||||
extract_all_records(from_lib_file(file))
|
||||
file = opts[:from_lib] ->
|
||||
{from_lib_file(file), Keyword.delete(opts, :from_lib)}
|
||||
|
||||
true ->
|
||||
raise ArgumentError, "expected :from or :from_lib to be given as option"
|
||||
end
|
||||
end
|
||||
|
||||
# Find file using the same lookup as the *include* attribute from Erlang modules.
|
||||
@@ -49,20 +46,22 @@ defmodule Record.Extractor do
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp extract_record(name, file) do
|
||||
form = read_file(file)
|
||||
defp extract_record(name, {file, opts}) do
|
||||
form = read_file(file, opts)
|
||||
records = extract_records(form)
|
||||
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError, "no record #{name} found at #{file}"
|
||||
raise ArgumentError,
|
||||
"no record #{name} found at #{file}. Or the record does not exist or " <>
|
||||
"its entry is malformed or depends on other include files"
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve all records from the given file
|
||||
defp extract_all_records(file) do
|
||||
form = read_file(file)
|
||||
defp extract_all_records({file, opts}) do
|
||||
form = read_file(file, opts)
|
||||
records = extract_records(form)
|
||||
for rec = {name, _fields} <- records, do: {name, parse_record(rec, form)}
|
||||
end
|
||||
@@ -76,8 +75,8 @@ defmodule Record.Extractor do
|
||||
# includes record but with macros and other attributes expanded,
|
||||
# such as "-include(...)" and "-include_lib(...)". This is done
|
||||
# by using Erlang's epp.
|
||||
defp read_file(file) do
|
||||
case :epp.parse_file(file, []) do
|
||||
defp read_file(file, opts) do
|
||||
case :epp.parse_file(file, opts) do
|
||||
{:ok, form} ->
|
||||
form
|
||||
|
||||
|
||||
+38
-24
@@ -7,9 +7,9 @@ defmodule Regex do
|
||||
in the [`:re` module documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using the sigils
|
||||
[`~r`](Kernel.html#sigil_r/2) or [`~R`](Kernel.html#sigil_R/2):
|
||||
[`~r`](`Kernel.sigil_r/2`) or [`~R`](`Kernel.sigil_R/2`):
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
# A simple regular expression that matches foo anywhere in the string
|
||||
~r/foo/
|
||||
|
||||
# A regular expression with case insensitive and Unicode options
|
||||
@@ -41,10 +41,10 @@ defmodule Regex do
|
||||
expression engine at any time.
|
||||
|
||||
For such reasons, we always recommend precompiling Elixir projects using
|
||||
the OTP version meant to run in production. In case cross-compilation is
|
||||
really necessary, you can manually invoke `Regex.recompile/1` or `Regex.
|
||||
recompile!/1` to perform a runtime version check and recompile the regex
|
||||
if necessary.
|
||||
the Erlang/OTP version meant to run in production. In case cross-compilation is
|
||||
really necessary, you can manually invoke `Regex.recompile/1` or
|
||||
`Regex.recompile!/1` to perform a runtime version check and recompile the
|
||||
regex if necessary.
|
||||
|
||||
## Modifiers
|
||||
|
||||
@@ -93,7 +93,7 @@ defmodule Regex do
|
||||
complete matching part of the string; all explicitly captured subpatterns
|
||||
are discarded
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all
|
||||
* `:all_but_first` - all but the first matching subpattern, i.e. all
|
||||
explicitly captured subpatterns, but not the complete matching part of
|
||||
the string
|
||||
|
||||
@@ -117,8 +117,9 @@ defmodule Regex do
|
||||
Compiles the regular expression.
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `~r` sigil,
|
||||
or a list of options, as expected by the Erlang's `:re` module.
|
||||
representing the same regex options given to the
|
||||
[`~r`](`Kernel.sigil_r/2`) sigil, or a list of options, as
|
||||
expected by the Erlang's `:re` module.
|
||||
|
||||
It returns `{:ok, regex}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
@@ -126,7 +127,7 @@ defmodule Regex do
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, ~r"foo"}
|
||||
{:ok, ~r/foo/}
|
||||
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
@@ -178,13 +179,13 @@ defmodule Regex do
|
||||
This checks the version stored in the regular expression
|
||||
and recompiles the regex in case of version mismatch.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec recompile(t) :: t
|
||||
def recompile(%Regex{} = regex) do
|
||||
version = version()
|
||||
|
||||
# We use Map.get/3 by choice to support old regexes versions.
|
||||
case Map.get(regex, :re_version, :error) do
|
||||
^version ->
|
||||
case regex do
|
||||
%{re_version: ^version} ->
|
||||
{:ok, regex}
|
||||
|
||||
_ ->
|
||||
@@ -196,6 +197,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec recompile!(t) :: t
|
||||
def recompile!(regex) do
|
||||
case recompile(regex) do
|
||||
@@ -207,12 +209,14 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the version of the underlying Regex engine.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec version :: term()
|
||||
# TODO: No longer check for function_exported? on OTP 20+.
|
||||
def version do
|
||||
if function_exported?(:re, :version, 0) do
|
||||
:re.version()
|
||||
{:re.version(), :erlang.system_info(:endian)}
|
||||
else
|
||||
"8.33 2013-05-29"
|
||||
{"8.33 2013-05-29", :erlang.system_info(:endian)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -257,7 +261,8 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - sets to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:return` - set to `:index` to return byte index and match length.
|
||||
Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
@@ -288,9 +293,12 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a map or `nil` if no captures are
|
||||
found. The option `:return` can be set to `:index` to get indexes
|
||||
back.
|
||||
Returns the given captures as a map or `nil` if no captures are found.
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - set to `:index` to return byte index and match length.
|
||||
Defaults to `:binary`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -372,7 +380,8 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - sets to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:return` - set to `:index` to return byte index and match length.
|
||||
Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
@@ -390,6 +399,9 @@ defmodule Regex do
|
||||
iex> Regex.scan(~r/\p{Sc}/u, "$, £, and €")
|
||||
[["$"], ["£"], ["€"]]
|
||||
|
||||
iex> Regex.scan(~r/=+/, "=ü†ƒ8===", return: :index)
|
||||
[[{0, 1}], [{9, 3}]]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t(), [term]) :: [[String.t()]]
|
||||
def scan(regex, string, options \\ [])
|
||||
@@ -432,7 +444,7 @@ defmodule Regex do
|
||||
iex> Regex.split(~r{-}, "a-b-c")
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> Regex.split(~r{-}, "a-b-c", [parts: 2])
|
||||
iex> Regex.split(~r{-}, "a-b-c", parts: 2)
|
||||
["a", "b-c"]
|
||||
|
||||
iex> Regex.split(~r{-}, "abc")
|
||||
@@ -465,7 +477,8 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) and is_list(opts) do
|
||||
def split(%Regex{re_pattern: compiled}, string, opts)
|
||||
when is_binary(string) and is_list(opts) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
@@ -586,7 +599,7 @@ defmodule Regex do
|
||||
|
||||
def replace(regex, string, replacement, options)
|
||||
when is_binary(string) and is_function(replacement) and is_list(options) do
|
||||
{:arity, arity} = :erlang.fun_info(replacement, :arity)
|
||||
{:arity, arity} = Function.info(replacement, :arity)
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
|
||||
@@ -653,7 +666,8 @@ defmodule Regex do
|
||||
string
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list)
|
||||
when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched::binary-size(length), rest::binary>> = string
|
||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||
|
||||
+174
-38
@@ -11,7 +11,7 @@ defmodule Registry do
|
||||
Each entry in the registry is associated to the process that has
|
||||
registered the key. If the process crashes, the keys associated to that
|
||||
process are automatically removed. All key comparisons in the registry
|
||||
are done using the match operation (`===`).
|
||||
are done using the match operation (`===/2`).
|
||||
|
||||
The registry can be used for different purposes, such as name lookups (using
|
||||
the `:via` option), storing properties, custom dispatching rules, or a pubsub
|
||||
@@ -89,7 +89,7 @@ defmodule Registry do
|
||||
apply(module, function, [pid])
|
||||
catch
|
||||
kind, reason ->
|
||||
formatted = Exception.format(kind, reason, System.stacktrace)
|
||||
formatted = Exception.format(kind, reason, __STACKTRACE__)
|
||||
Logger.error "Registry.dispatch/3 failed with #{formatted}"
|
||||
end
|
||||
end
|
||||
@@ -171,9 +171,19 @@ defmodule Registry do
|
||||
@typedoc "The type of registry metadata values"
|
||||
@type meta_value :: term
|
||||
|
||||
@typedoc "A pattern to match on objects in a registry"
|
||||
@type match_pattern :: atom | term
|
||||
|
||||
@typedoc "A guard to be evaluated when matching on objects in a registry"
|
||||
@type guard :: {atom | term}
|
||||
|
||||
@typedoc "A list of guards to be evaluated when matching on objects in a registry"
|
||||
@type guards :: [guard] | []
|
||||
|
||||
## Via callbacks
|
||||
|
||||
@doc false
|
||||
@doc since: "1.4.0"
|
||||
def whereis_name({registry, key}) do
|
||||
case key_info!(registry) do
|
||||
{:unique, partitions, key_ets} ->
|
||||
@@ -193,6 +203,7 @@ defmodule Registry do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc since: "1.4.0"
|
||||
def register_name({registry, key}, pid) when pid == self() do
|
||||
case register(registry, key, nil) do
|
||||
{:ok, _} -> :yes
|
||||
@@ -201,6 +212,7 @@ defmodule Registry do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc since: "1.4.0"
|
||||
def send({registry, key}, msg) do
|
||||
case lookup(registry, key) do
|
||||
[{pid, _}] -> Kernel.send(pid, msg)
|
||||
@@ -209,6 +221,7 @@ defmodule Registry do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc since: "1.4.0"
|
||||
def unregister_name({registry, key}) do
|
||||
unregister(registry, key)
|
||||
end
|
||||
@@ -259,6 +272,7 @@ defmodule Registry do
|
||||
* `:meta` - a keyword list of metadata to be attached to the registry.
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec start_link(
|
||||
keys: keys,
|
||||
name: registry,
|
||||
@@ -310,18 +324,18 @@ defmodule Registry do
|
||||
Registry.Supervisor.start_link(keys, name, partitions, listeners, entries)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts the registry as a supervisor process.
|
||||
|
||||
Similar to `start_link/1` except the required options,
|
||||
`keys` and `name` are given as arguments.
|
||||
"""
|
||||
@spec start_link(keys, registry, keyword) :: {:ok, pid} | {:error, term}
|
||||
@doc false
|
||||
@deprecated "Use Registry.start_link/1 instead"
|
||||
def start_link(keys, name, options \\ []) when keys in @keys and is_atom(name) do
|
||||
start_link([keys: keys, name: name] ++ options)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start a registry under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: Keyword.get(opts, :name, Registry),
|
||||
@@ -340,16 +354,17 @@ defmodule Registry do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UpdateTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UpdateTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.UpdateTest, "hello", 1)
|
||||
iex> Registry.lookup(Registry.UpdateTest, "hello")
|
||||
[{self(), 1}]
|
||||
iex> Registry.update_value(Registry.UpdateTest, "hello", & &1 + 1)
|
||||
iex> Registry.update_value(Registry.UpdateTest, "hello", &(&1 + 1))
|
||||
{2, 1}
|
||||
iex> Registry.lookup(Registry.UpdateTest, "hello")
|
||||
[{self(), 2}]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec update_value(registry, key, (value -> value)) ::
|
||||
{new_value :: term, old_value :: term} | :error
|
||||
def update_value(registry, key, callback) when is_atom(registry) and is_function(callback, 1) do
|
||||
@@ -393,7 +408,9 @@ defmodule Registry do
|
||||
See the module documentation for examples of using the `dispatch/3`
|
||||
function for building custom dispatching or a pubsub system.
|
||||
"""
|
||||
@spec dispatch(registry, key, (entries :: [{pid, value}] -> term), keyword) :: :ok
|
||||
@doc since: "1.4.0"
|
||||
@spec dispatch(registry, key, dispatcher, keyword) :: :ok
|
||||
when dispatcher: (entries :: [{pid, value}] -> term) | {module(), atom(), [any()]}
|
||||
def dispatch(registry, key, mfa_or_fun, opts \\ [])
|
||||
when is_atom(registry) and is_function(mfa_or_fun, 1)
|
||||
when is_atom(registry) and tuple_size(mfa_or_fun) == 3 do
|
||||
@@ -484,18 +501,18 @@ defmodule Registry do
|
||||
In the example below we register the current process and look it up
|
||||
both from itself and other processes:
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UniqueLookupTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueLookupTest)
|
||||
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
|
||||
[]
|
||||
iex> {:ok, _} = Registry.register(Registry.UniqueLookupTest, "hello", :world)
|
||||
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
|
||||
[{self(), :world}]
|
||||
iex> Task.async(fn -> Registry.lookup(Registry.UniqueLookupTest, "hello") end) |> Task.await
|
||||
iex> Task.async(fn -> Registry.lookup(Registry.UniqueLookupTest, "hello") end) |> Task.await()
|
||||
[{self(), :world}]
|
||||
|
||||
The same applies to duplicate registries:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.DuplicateLookupTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateLookupTest)
|
||||
iex> Registry.lookup(Registry.DuplicateLookupTest, "hello")
|
||||
[]
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :world)
|
||||
@@ -506,6 +523,7 @@ defmodule Registry do
|
||||
[{self(), :another}, {self(), :world}]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec lookup(registry, key) :: [{pid, value}]
|
||||
def lookup(registry, key) when is_atom(registry) do
|
||||
case key_info!(registry) do
|
||||
@@ -535,14 +553,14 @@ defmodule Registry do
|
||||
|
||||
Pattern must be an atom or a tuple that will match the structure of the
|
||||
value stored in the registry. The atom `:_` can be used to ignore a given
|
||||
value or tuple element, while :"$1" can be used to temporarily assign part
|
||||
value or tuple element, while the atom `:"$1"` can be used to temporarily assign part
|
||||
of pattern to a variable for a subsequent comparison.
|
||||
|
||||
It is possible to pass list of guard conditions for more precise matching.
|
||||
Each guard is a tuple, which describes check that should be passed by assigned part of pattern.
|
||||
For example :"$1" > 1 guard condition would be expressed as {:>, :"$1", 1} tuple.
|
||||
Please note that guard conditions will work only for assigned variables like :"$1", :"$2", etc.
|
||||
Avoid usage of special match variables :"$_" and :"$$", because it might not work as expected.
|
||||
Optionally, it is possible to pass a list of guard conditions for more precise matching.
|
||||
Each guard is a tuple, which describes checks that should be passed by assigned part of pattern.
|
||||
For example the `$1 > 1` guard condition would be expressed as the `{:>, :"$1", 1}` tuple.
|
||||
Please note that guard conditions will work only for assigned variables like `:"$1"`, `:"$2"`, etc.
|
||||
Avoid usage of special match variables `:"$_"` and `:"$$"`, because it might not work as expected.
|
||||
|
||||
An empty list will be returned if there is no match.
|
||||
|
||||
@@ -554,7 +572,7 @@ defmodule Registry do
|
||||
In the example below we register the current process under the same
|
||||
key in a duplicate registry but with different values:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.MatchTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.MatchTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {1, :atom, 1})
|
||||
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {2, :atom, 2})
|
||||
iex> Registry.match(Registry.MatchTest, "hello", {1, :_, :_})
|
||||
@@ -571,7 +589,8 @@ defmodule Registry do
|
||||
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
|
||||
|
||||
"""
|
||||
@spec match(registry, key, match_pattern :: term, guards :: list()) :: [{pid, term}]
|
||||
@doc since: "1.4.0"
|
||||
@spec match(registry, key, match_pattern, guards) :: [{pid, term}]
|
||||
def match(registry, key, pattern, guards \\ []) when is_atom(registry) and is_list(guards) do
|
||||
guards = [{:"=:=", {:element, 1, :"$_"}, {:const, key}} | guards]
|
||||
spec = [{{:_, {:_, pattern}}, guards, [{:element, 2, :"$_"}]}]
|
||||
@@ -603,7 +622,7 @@ defmodule Registry do
|
||||
|
||||
Registering under a unique registry does not allow multiple entries:
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UniqueKeysTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueKeysTest)
|
||||
iex> Registry.keys(Registry.UniqueKeysTest, self())
|
||||
[]
|
||||
iex> {:ok, _} = Registry.register(Registry.UniqueKeysTest, "hello", :world)
|
||||
@@ -614,7 +633,7 @@ defmodule Registry do
|
||||
|
||||
Such is possible for duplicate registries though:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.DuplicateKeysTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateKeysTest)
|
||||
iex> Registry.keys(Registry.DuplicateKeysTest, self())
|
||||
[]
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateKeysTest, "hello", :world)
|
||||
@@ -623,6 +642,7 @@ defmodule Registry do
|
||||
["hello", "hello"]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec keys(registry, pid) :: [key]
|
||||
def keys(registry, pid) when is_atom(registry) and is_pid(pid) do
|
||||
{kind, partitions, _, pid_ets, _} = info!(registry)
|
||||
@@ -673,7 +693,7 @@ defmodule Registry do
|
||||
|
||||
For unique registries:
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UniqueUnregisterTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueUnregisterTest)
|
||||
iex> Registry.register(Registry.UniqueUnregisterTest, "hello", :world)
|
||||
iex> Registry.keys(Registry.UniqueUnregisterTest, self())
|
||||
["hello"]
|
||||
@@ -684,7 +704,7 @@ defmodule Registry do
|
||||
|
||||
For duplicate registries:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.DuplicateUnregisterTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateUnregisterTest)
|
||||
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
|
||||
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
|
||||
iex> Registry.keys(Registry.DuplicateUnregisterTest, self())
|
||||
@@ -695,6 +715,7 @@ defmodule Registry do
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec unregister(registry, key) :: :ok
|
||||
def unregister(registry, key) when is_atom(registry) do
|
||||
self = self()
|
||||
@@ -726,7 +747,7 @@ defmodule Registry do
|
||||
For unique registries it can be used to conditionally unregister a key on
|
||||
the basis of whether or not it matches a particular value.
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UniqueUnregisterMatchTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueUnregisterMatchTest)
|
||||
iex> Registry.register(Registry.UniqueUnregisterMatchTest, "hello", :world)
|
||||
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
|
||||
["hello"]
|
||||
@@ -741,7 +762,7 @@ defmodule Registry do
|
||||
|
||||
For duplicate registries:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.DuplicateUnregisterMatchTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateUnregisterMatchTest)
|
||||
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
|
||||
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_b)
|
||||
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_c)
|
||||
@@ -753,7 +774,9 @@ defmodule Registry do
|
||||
["hello", "hello"]
|
||||
iex> Registry.lookup(Registry.DuplicateUnregisterMatchTest, "hello")
|
||||
[{self(), :world_b}, {self(), :world_c}]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def unregister_match(registry, key, pattern, guards \\ []) when is_list(guards) do
|
||||
self = self()
|
||||
|
||||
@@ -830,7 +853,7 @@ defmodule Registry do
|
||||
|
||||
Registering under a unique registry does not allow multiple entries:
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.UniqueRegisterTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueRegisterTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.UniqueRegisterTest, "hello", :world)
|
||||
iex> Registry.register(Registry.UniqueRegisterTest, "hello", :later)
|
||||
{:error, {:already_registered, self()}}
|
||||
@@ -839,13 +862,14 @@ defmodule Registry do
|
||||
|
||||
Such is possible for duplicate registries though:
|
||||
|
||||
iex> Registry.start_link(:duplicate, Registry.DuplicateRegisterTest)
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateRegisterTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
|
||||
iex> Registry.keys(Registry.DuplicateRegisterTest, self())
|
||||
["hello", "hello"]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec register(registry, key, value) :: {:ok, pid} | {:error, {:already_registered, pid}}
|
||||
def register(registry, key, value) when is_atom(registry) do
|
||||
self = self()
|
||||
@@ -911,13 +935,14 @@ defmodule Registry do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.MetaTest, meta: [custom_key: "custom_value"])
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.MetaTest, meta: [custom_key: "custom_value"])
|
||||
iex> Registry.meta(Registry.MetaTest, :custom_key)
|
||||
{:ok, "custom_value"}
|
||||
iex> Registry.meta(Registry.MetaTest, :unknown_key)
|
||||
:error
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec meta(registry, meta_key) :: {:ok, meta_value} | :error
|
||||
def meta(registry, key) when is_atom(registry) and (is_atom(key) or is_tuple(key)) do
|
||||
try do
|
||||
@@ -938,7 +963,7 @@ defmodule Registry do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Registry.start_link(:unique, Registry.PutMetaTest)
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.PutMetaTest)
|
||||
iex> Registry.put_meta(Registry.PutMetaTest, :custom_key, "custom_value")
|
||||
:ok
|
||||
iex> Registry.meta(Registry.PutMetaTest, :custom_key)
|
||||
@@ -949,6 +974,7 @@ defmodule Registry do
|
||||
{:ok, "tuple_value"}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec put_meta(registry, meta_key, meta_value) :: :ok
|
||||
def put_meta(registry, key, value) when is_atom(registry) and (is_atom(key) or is_tuple(key)) do
|
||||
try do
|
||||
@@ -960,6 +986,120 @@ defmodule Registry do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of registered keys in a registry.
|
||||
It runs in constant time.
|
||||
|
||||
## Examples
|
||||
In the example below we register the current process and ask for the
|
||||
number of keys in the registry:
|
||||
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.UniqueCountTest)
|
||||
iex> Registry.count(Registry.UniqueCountTest)
|
||||
0
|
||||
iex> {:ok, _} = Registry.register(Registry.UniqueCountTest, "hello", :world)
|
||||
iex> {:ok, _} = Registry.register(Registry.UniqueCountTest, "world", :world)
|
||||
iex> Registry.count(Registry.UniqueCountTest)
|
||||
2
|
||||
|
||||
The same applies to duplicate registries:
|
||||
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateCountTest)
|
||||
iex> Registry.count(Registry.DuplicateCountTest)
|
||||
0
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateCountTest, "hello", :world)
|
||||
iex> {:ok, _} = Registry.register(Registry.DuplicateCountTest, "hello", :world)
|
||||
iex> Registry.count(Registry.DuplicateCountTest)
|
||||
2
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec count(registry) :: non_neg_integer()
|
||||
def count(registry) when is_atom(registry) do
|
||||
case key_info!(registry) do
|
||||
{_kind, partitions, nil} ->
|
||||
Enum.reduce(0..(partitions - 1), 0, fn partition_index, acc ->
|
||||
acc + safe_size(key_ets!(registry, partition_index))
|
||||
end)
|
||||
|
||||
{_kind, 1, key_ets} ->
|
||||
safe_size(key_ets)
|
||||
end
|
||||
end
|
||||
|
||||
defp safe_size(ets) do
|
||||
try do
|
||||
:ets.info(ets, :size)
|
||||
catch
|
||||
:error, :badarg -> 0
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of `{pid, value}` pairs under the given `key` in `registry`
|
||||
that match `pattern`.
|
||||
|
||||
Pattern must be an atom or a tuple that will match the structure of the
|
||||
value stored in the registry. The atom `:_` can be used to ignore a given
|
||||
value or tuple element, while the atom `:"$1"` can be used to temporarily assign part
|
||||
of pattern to a variable for a subsequent comparison.
|
||||
|
||||
Optionally, it is possible to pass a list of guard conditions for more precise matching.
|
||||
Each guard is a tuple, which describes checks that should be passed by assigned part of pattern.
|
||||
For example the `$1 > 1` guard condition would be expressed as the `{:>, :"$1", 1}` tuple.
|
||||
Please note that guard conditions will work only for assigned variables like `:"$1"`, `:"$2"`, etc.
|
||||
Avoid usage of special match variables `:"$_"` and `:"$$"`, because it might not work as expected.
|
||||
|
||||
Zero will be returned if there is no match.
|
||||
|
||||
For unique registries, a single partition lookup is necessary. For
|
||||
duplicate registries, all partitions must be looked up.
|
||||
|
||||
## Examples
|
||||
|
||||
In the example below we register the current process under the same
|
||||
key in a duplicate registry but with different values:
|
||||
|
||||
iex> Registry.start_link(keys: :duplicate, name: Registry.MatchTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {1, :atom, 1})
|
||||
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {2, :atom, 2})
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {1, :_, :_})
|
||||
1
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {2, :_, :_})
|
||||
1
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {:_, :atom, :_})
|
||||
2
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {:"$1", :_, :"$1"})
|
||||
2
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}])
|
||||
1
|
||||
iex> Registry.count_match(Registry.MatchTest, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}])
|
||||
2
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec count_match(registry, key, match_pattern, guards) :: non_neg_integer()
|
||||
def count_match(registry, key, pattern, guards \\ [])
|
||||
when is_atom(registry) and is_list(guards) do
|
||||
guards = [{:"=:=", {:element, 1, :"$_"}, {:const, key}} | guards]
|
||||
spec = [{{:_, {:_, pattern}}, guards, [true]}]
|
||||
|
||||
case key_info!(registry) do
|
||||
{:unique, partitions, key_ets} ->
|
||||
key_ets = key_ets || key_ets!(registry, key, partitions)
|
||||
:ets.select_count(key_ets, spec)
|
||||
|
||||
{:duplicate, 1, key_ets} ->
|
||||
:ets.select_count(key_ets, spec)
|
||||
|
||||
{:duplicate, partitions, _key_ets} ->
|
||||
Enum.reduce(0..(partitions - 1), 0, fn partition_index, acc ->
|
||||
count = :ets.select_count(key_ets!(registry, partition_index), spec)
|
||||
acc + count
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, hash: 2}
|
||||
@@ -1173,8 +1313,4 @@ defmodule Registry.Partition do
|
||||
|
||||
{:noreply, ets}
|
||||
end
|
||||
|
||||
def handle_info(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
end
|
||||
|
||||
+17
-4
@@ -1,16 +1,18 @@
|
||||
defmodule Set do
|
||||
@moduledoc ~S"""
|
||||
WARNING: this module is deprecated.
|
||||
Generic API for sets.
|
||||
|
||||
Use the `MapSet` module instead.
|
||||
This module is deprecated, use the `MapSet` module instead.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use MapSet instead"
|
||||
|
||||
@type value :: any
|
||||
@type values :: [value]
|
||||
@type t :: map
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
message = "Use the MapSet module for working with sets"
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
@@ -21,10 +23,12 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def delete(set, value) do
|
||||
target(set).delete(set, value)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def difference(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -39,6 +43,7 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def disjoint?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -56,11 +61,12 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated message
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def equal?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -77,6 +83,7 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def intersection(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -91,18 +98,22 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def member?(set, value) do
|
||||
target(set).member?(set, value)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def put(set, value) do
|
||||
target(set).put(set, value)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def size(set) do
|
||||
target(set).size(set)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def subset?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -114,10 +125,12 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def to_list(set) do
|
||||
target(set).to_list(set)
|
||||
end
|
||||
|
||||
@deprecated message
|
||||
def union(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
+109
-83
@@ -1,14 +1,15 @@
|
||||
defmodule Stream do
|
||||
@moduledoc """
|
||||
Module for creating and composing streams.
|
||||
Functions for creating and composing streams.
|
||||
|
||||
Streams are composable, lazy enumerables. Any enumerable that generates
|
||||
Streams are composable, lazy enumerables (for an introduction on
|
||||
enumerables, see the `Enum` module). Any enumerable that generates
|
||||
items one by one during enumeration is called a stream. For example,
|
||||
Elixir's `Range` is a stream:
|
||||
|
||||
iex> range = 1..5
|
||||
1..5
|
||||
iex> Enum.map range, &(&1 * 2)
|
||||
iex> Enum.map(range, &(&1 * 2))
|
||||
[2, 4, 6, 8, 10]
|
||||
|
||||
In the example above, as we mapped over the range, the elements being
|
||||
@@ -121,13 +122,22 @@ defmodule Stream do
|
||||
|
||||
## Transformers
|
||||
|
||||
# Deprecate on v1.7
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Stream.chunk_every/2 instead"
|
||||
def chunk(enum, n), do: chunk(enum, n, n, nil)
|
||||
|
||||
# Deprecate on v1.7
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
def chunk(enum, n, step, leftover \\ nil)
|
||||
@deprecated "Use Stream.chunk_every/3 instead"
|
||||
def chunk(enum, n, step) do
|
||||
chunk_every(enum, n, step, nil)
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Stream.chunk_every/4 instead"
|
||||
def chunk(enum, n, step, leftover)
|
||||
when is_integer(n) and n > 0 and is_integer(step) and step > 0 do
|
||||
chunk_every(enum, n, step, leftover || :discard)
|
||||
end
|
||||
@@ -135,6 +145,7 @@ defmodule Stream do
|
||||
@doc """
|
||||
Shortcut to `chunk_every(enum, count, count)`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_every(Enumerable.t(), pos_integer) :: Enumerable.t()
|
||||
def chunk_every(enum, count), do: chunk_every(enum, count, count, [])
|
||||
|
||||
@@ -155,19 +166,20 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list()
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard) |> Enum.to_list
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard) |> Enum.to_list()
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list()
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
|
||||
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list()
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_every(Enumerable.t(), pos_integer, pos_integer, Enumerable.t() | :discard) ::
|
||||
Enumerable.t()
|
||||
def chunk_every(enum, count, step, leftover \\ [])
|
||||
@@ -205,11 +217,11 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> chunk_fun = fn i, acc ->
|
||||
...> if rem(i, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([i | acc]), []}
|
||||
iex> chunk_fun = fn item, acc ->
|
||||
...> if rem(item, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([item | acc]), []}
|
||||
...> else
|
||||
...> {:cont, [i | acc]}
|
||||
...> {:cont, [item | acc]}
|
||||
...> end
|
||||
...> end
|
||||
iex> after_fun = fn
|
||||
@@ -221,6 +233,7 @@ defmodule Stream do
|
||||
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec chunk_while(
|
||||
Enumerable.t(),
|
||||
acc,
|
||||
@@ -270,11 +283,11 @@ defmodule Stream do
|
||||
|
||||
This function only ever needs to store the last emitted element.
|
||||
|
||||
Elements are compared using `===`.
|
||||
Elements are compared using `===/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.dedup([1, 2, 3, 3, 2, 1]) |> Enum.to_list
|
||||
iex> Stream.dedup([1, 2, 3, 3, 2, 1]) |> Enum.to_list()
|
||||
[1, 2, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@@ -289,7 +302,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.dedup_by([{1, :x}, {2, :y}, {2, :z}, {1, :x}], fn {x, _} -> x end) |> Enum.to_list
|
||||
iex> Stream.dedup_by([{1, :x}, {2, :y}, {2, :z}, {1, :x}], fn {x, _} -> x end) |> Enum.to_list()
|
||||
[{1, :x}, {2, :y}, {1, :x}]
|
||||
|
||||
"""
|
||||
@@ -318,11 +331,11 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec drop(Enumerable.t(), non_neg_integer) :: Enumerable.t()
|
||||
def drop(enum, n) when n >= 0 do
|
||||
def drop(enum, n) when is_integer(n) and n >= 0 do
|
||||
lazy(enum, n, fn f1 -> R.drop(f1) end)
|
||||
end
|
||||
|
||||
def drop(enum, n) when n < 0 do
|
||||
def drop(enum, n) when is_integer(n) and n < 0 do
|
||||
n = abs(n)
|
||||
|
||||
lazy(enum, {0, [], []}, fn f1 ->
|
||||
@@ -402,7 +415,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.each([1, 2, 3], fn(x) -> send self(), x end)
|
||||
iex> stream = Stream.each([1, 2, 3], fn x -> send(self(), x) end)
|
||||
iex> Enum.to_list(stream)
|
||||
iex> receive do: (x when is_integer(x) -> x)
|
||||
1
|
||||
@@ -430,11 +443,11 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn x -> [x, x * 2] end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 2, 2, 4, 3, 6]
|
||||
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [[x]] end)
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn x -> [[x]] end)
|
||||
iex> Enum.to_list(stream)
|
||||
[[1], [2], [3]]
|
||||
|
||||
@@ -450,7 +463,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> stream = Stream.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2]
|
||||
|
||||
@@ -462,7 +475,7 @@ defmodule Stream do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Stream.filter/2 + Stream.map/2 instead"
|
||||
def filter_map(enum, filter, mapper) do
|
||||
lazy(enum, fn f1 -> R.filter_map(filter, mapper, f1) end)
|
||||
end
|
||||
@@ -521,9 +534,8 @@ defmodule Stream do
|
||||
reduce.({command, [acc | collectable]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
into.(collectable, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:suspended, [acc | collectable], continuation} ->
|
||||
{:suspended, acc, &do_into(continuation, collectable, into, &1)}
|
||||
@@ -540,7 +552,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
iex> stream = Stream.map([1, 2, 3], fn x -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2, 4, 6]
|
||||
|
||||
@@ -560,19 +572,20 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.map_every(1..10, 2, fn(x) -> x * 2 end)
|
||||
iex> stream = Stream.map_every(1..10, 2, fn x -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2, 2, 6, 4, 10, 6, 14, 8, 18, 10]
|
||||
|
||||
iex> stream = Stream.map_every([1, 2, 3, 4, 5], 1, fn(x) -> x * 2 end)
|
||||
iex> stream = Stream.map_every([1, 2, 3, 4, 5], 1, fn x -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2, 4, 6, 8, 10]
|
||||
|
||||
iex> stream = Stream.map_every(1..5, 0, fn(x) -> x * 2 end)
|
||||
iex> stream = Stream.map_every(1..5, 0, fn x -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 2, 3, 4, 5]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec map_every(Enumerable.t(), non_neg_integer, (element -> any)) :: Enumerable.t()
|
||||
def map_every(enum, nth, fun)
|
||||
|
||||
@@ -590,7 +603,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> stream = Stream.reject([1, 2, 3], fn x -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 3]
|
||||
|
||||
@@ -611,13 +624,13 @@ defmodule Stream do
|
||||
Open up a file, replace all `#` by `%` and stream to another file
|
||||
without loading the whole file in memory:
|
||||
|
||||
stream = File.stream!("code")
|
||||
File.stream!("/path/to/file")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|> Stream.run
|
||||
|> Stream.into(File.stream!("/path/to/other/file"))
|
||||
|> Stream.run()
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
No computation will be done until we call one of the `Enum` functions
|
||||
or `run/1`.
|
||||
"""
|
||||
@spec run(Enumerable.t()) :: :ok
|
||||
def run(stream) do
|
||||
@@ -752,7 +765,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
iex> Stream.timer(10) |> Enum.to_list()
|
||||
[0]
|
||||
|
||||
"""
|
||||
@@ -844,9 +857,8 @@ defmodule Stream do
|
||||
next.({:cont, []})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
do_after(after_fun, user_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:suspended, vals, next} ->
|
||||
do_transform_user(:lists.reverse(vals), user_acc, :cont, next, inner_acc, funs)
|
||||
@@ -867,10 +879,9 @@ defmodule Stream do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{[], user_acc} ->
|
||||
do_transform_user(vals, user_acc, next_op, next, inner_acc, funs)
|
||||
@@ -897,10 +908,9 @@ defmodule Stream do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_transform_user(vals, user_acc, next_op, next, {:cont, acc}, funs)
|
||||
@@ -923,15 +933,13 @@ defmodule Stream do
|
||||
reduce.({op, [:outer | inner_acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
# Only take into account outer halts when the op is not halt itself.
|
||||
# Otherwise, we were the ones wishing to halt, so we should just stop.
|
||||
{:halted, [:outer | acc]}
|
||||
when op != :halt ->
|
||||
{:halted, [:outer | acc]} when op != :halt ->
|
||||
do_transform_user(vals, user_acc, next_op, next, {:cont, acc}, funs)
|
||||
|
||||
{:halted, [_ | acc]} ->
|
||||
@@ -968,11 +976,11 @@ defmodule Stream do
|
||||
Keep in mind that, in order to know if an element is unique
|
||||
or not, this function needs to store all unique values emitted
|
||||
by the stream. Therefore, if the stream is infinite, the number
|
||||
of items stored will grow infinitely, never being garbage collected.
|
||||
of items stored will grow infinitely, never being garbage-collected.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.uniq([1, 2, 3, 3, 2, 1]) |> Enum.to_list
|
||||
iex> Stream.uniq([1, 2, 3, 3, 2, 1]) |> Enum.to_list()
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@@ -983,7 +991,7 @@ defmodule Stream do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use Stream.uniq_by/2 instead"
|
||||
def uniq(enum, fun) do
|
||||
uniq_by(enum, fun)
|
||||
end
|
||||
@@ -998,14 +1006,14 @@ defmodule Stream do
|
||||
Keep in mind that, in order to know if an element is unique
|
||||
or not, this function needs to store all unique values emitted
|
||||
by the stream. Therefore, if the stream is infinite, the number
|
||||
of items stored will grow infinitely, never being garbage collected.
|
||||
of items stored will grow infinitely, never being garbage-collected.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Stream.uniq_by([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end) |> Enum.to_list
|
||||
iex> Stream.uniq_by([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end) |> Enum.to_list()
|
||||
[{1, :x}, {2, :y}]
|
||||
|
||||
iex> Stream.uniq_by([a: {:tea, 2}, b: {:tea, 2}, c: {:coffee, 1}], fn {_, y} -> y end) |> Enum.to_list
|
||||
iex> Stream.uniq_by([a: {:tea, 2}, b: {:tea, 2}, c: {:coffee, 1}], fn {_, y} -> y end) |> Enum.to_list()
|
||||
[a: {:tea, 2}, c: {:coffee, 1}]
|
||||
|
||||
"""
|
||||
@@ -1082,8 +1090,8 @@ defmodule Stream do
|
||||
## Examples
|
||||
|
||||
iex> concat = Stream.concat(1..3, 4..6)
|
||||
iex> cycle = Stream.cycle([:a, :b, :c])
|
||||
iex> Stream.zip(concat, cycle) |> Enum.to_list
|
||||
iex> cycle = Stream.cycle([:a, :b, :c])
|
||||
iex> Stream.zip(concat, cycle) |> Enum.to_list()
|
||||
[{1, :a}, {2, :b}, {3, :c}, {4, :a}, {5, :b}, {6, :c}]
|
||||
|
||||
"""
|
||||
@@ -1091,29 +1099,35 @@ defmodule Stream do
|
||||
def zip(left, right), do: zip([left, right])
|
||||
|
||||
@doc """
|
||||
Zips corresponding elements from a list of enumerables
|
||||
Zips corresponding elements from a finite collection of enumerables
|
||||
into one stream of tuples.
|
||||
|
||||
The zipping finishes as soon as any enumerable in the given list completes.
|
||||
The zipping finishes as soon as any enumerable in the given collection completes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> concat = Stream.concat(1..3, 4..6)
|
||||
iex> cycle = Stream.cycle(["foo", "bar", "baz"])
|
||||
iex> Stream.zip([concat, [:a, :b, :c], cycle]) |> Enum.to_list
|
||||
iex> Stream.zip([concat, [:a, :b, :c], cycle]) |> Enum.to_list()
|
||||
[{1, :a, "foo"}, {2, :b, "bar"}, {3, :c, "baz"}]
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec zip([Enumerable.t()]) :: Enumerable.t()
|
||||
def zip(enumerables) when is_list(enumerables) do
|
||||
@spec zip(Enumerable.t()) :: Enumerable.t()
|
||||
def zip(enumerables) do
|
||||
&prepare_zip(enumerables, &1, &2)
|
||||
end
|
||||
|
||||
defp prepare_zip(enumerables, acc, fun) do
|
||||
step = &do_zip_step(&1, &2)
|
||||
|
||||
enum_funs =
|
||||
Enum.map(enumerables, fn enum ->
|
||||
{&Enumerable.reduce(enum, &1, step), :cont}
|
||||
{&Enumerable.reduce(enum, &1, step), [], :cont}
|
||||
end)
|
||||
|
||||
&do_zip(enum_funs, &1, &2)
|
||||
do_zip(enum_funs, acc, fun)
|
||||
end
|
||||
|
||||
# This implementation of do_zip/3 works for any number of
|
||||
@@ -1128,14 +1142,17 @@ defmodule Stream do
|
||||
{:suspended, acc, &do_zip(zips, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_zip([], {:cont, acc}, _callback) do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
defp do_zip(zips, {:cont, acc}, callback) do
|
||||
try do
|
||||
do_zip_next_tuple(zips, acc, callback, [], [])
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
do_zip_close(zips)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:next, buffer, acc} ->
|
||||
do_zip(buffer, acc, callback)
|
||||
@@ -1148,18 +1165,20 @@ defmodule Stream do
|
||||
# do_zip_next_tuple/5 computes the next tuple formed by
|
||||
# the next element of each zipped stream.
|
||||
|
||||
defp do_zip_next_tuple([{_, :halt} | zips], acc, _callback, _yielded_elems, buffer) do
|
||||
defp do_zip_next_tuple([{_, [], :halt} | zips], acc, _callback, _yielded_elems, buffer) do
|
||||
do_zip_close(:lists.reverse(buffer, zips))
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
defp do_zip_next_tuple([{fun, :cont} | zips], acc, callback, yielded_elems, buffer) do
|
||||
defp do_zip_next_tuple([{fun, [], :cont} | zips], acc, callback, yielded_elems, buffer) do
|
||||
case fun.({:cont, []}) do
|
||||
{:suspended, [elem], fun} ->
|
||||
do_zip_next_tuple(zips, acc, callback, [elem | yielded_elems], [{fun, :cont} | buffer])
|
||||
{:suspended, [elem | next_acc], fun} ->
|
||||
next_buffer = [{fun, next_acc, :cont} | buffer]
|
||||
do_zip_next_tuple(zips, acc, callback, [elem | yielded_elems], next_buffer)
|
||||
|
||||
{_, [elem]} ->
|
||||
do_zip_next_tuple(zips, acc, callback, [elem | yielded_elems], [{fun, :halt} | buffer])
|
||||
{_, [elem | next_acc]} ->
|
||||
next_buffer = [{fun, next_acc, :halt} | buffer]
|
||||
do_zip_next_tuple(zips, acc, callback, [elem | yielded_elems], next_buffer)
|
||||
|
||||
{_, []} ->
|
||||
# The current zipped stream terminated, so we close all the streams
|
||||
@@ -1169,6 +1188,12 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_zip_next_tuple([{fun, zip_acc, zip_op} | zips], acc, callback, yielded_elems, buffer) do
|
||||
[elem | rest] = zip_acc
|
||||
next_buffer = [{fun, rest, zip_op} | buffer]
|
||||
do_zip_next_tuple(zips, acc, callback, [elem | yielded_elems], next_buffer)
|
||||
end
|
||||
|
||||
defp do_zip_next_tuple([] = _zips, acc, callback, yielded_elems, buffer) do
|
||||
# "yielded_elems" is a reversed list of results for the current iteration of
|
||||
# zipping: it needs to be reversed and converted to a tuple to have the next
|
||||
@@ -1178,11 +1203,11 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_zip_close(zips) do
|
||||
:lists.foreach(fn {fun, _} -> fun.({:halt, []}) end, zips)
|
||||
:lists.foreach(fn {fun, _, _} -> fun.({:halt, []}) end, zips)
|
||||
end
|
||||
|
||||
defp do_zip_step(x, []) do
|
||||
{:suspend, [x]}
|
||||
defp do_zip_step(x, acc) do
|
||||
{:suspend, :lists.reverse([x | acc])}
|
||||
end
|
||||
|
||||
## Sources
|
||||
@@ -1259,7 +1284,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
|
||||
iex> Stream.iterate(0, &(&1 + 1)) |> Enum.take(5)
|
||||
[0, 1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -1356,9 +1381,8 @@ defmodule Stream do
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
@@ -1382,9 +1406,8 @@ defmodule Stream do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
@@ -1402,9 +1425,8 @@ defmodule Stream do
|
||||
reduce.({op, [:outer | acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:halted, [:outer | acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
@@ -1436,7 +1458,10 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
iex> Stream.unfold(5, fn
|
||||
...> 0 -> nil
|
||||
...> n -> {n, n - 1}
|
||||
...> end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@@ -1465,16 +1490,17 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.intersperse([1, 2, 3], 0) |> Enum.to_list
|
||||
iex> Stream.intersperse([1, 2, 3], 0) |> Enum.to_list()
|
||||
[1, 0, 2, 0, 3]
|
||||
|
||||
iex> Stream.intersperse([1], 0) |> Enum.to_list
|
||||
iex> Stream.intersperse([1], 0) |> Enum.to_list()
|
||||
[1]
|
||||
|
||||
iex> Stream.intersperse([], 0) |> Enum.to_list
|
||||
iex> Stream.intersperse([], 0) |> Enum.to_list()
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec intersperse(Enumerable.t(), any) :: Enumerable.t()
|
||||
def intersperse(enumerable, intersperse_element) do
|
||||
Stream.transform(enumerable, false, fn
|
||||
|
||||
+204
-114
@@ -7,7 +7,7 @@ defmodule String do
|
||||
## Codepoints and grapheme cluster
|
||||
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 10.0.0.
|
||||
Standard, version 11.0.0.
|
||||
|
||||
As per the standard, a codepoint is a single Unicode Character,
|
||||
which may be represented by one or more bytes.
|
||||
@@ -203,18 +203,31 @@ defmodule String do
|
||||
is generated at runtime and does not survive compile term.
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
A UTF-8 encoded binary.
|
||||
|
||||
Note `String.t()` and `binary()` are equivalent to analysis tools.
|
||||
Although, for those reading the documentation, `String.t()` implies
|
||||
it is a UTF-8 encoded binary.
|
||||
"""
|
||||
@type t :: binary
|
||||
|
||||
@typedoc "A UTF-8 codepoint. It may be one or more bytes."
|
||||
@type codepoint :: t
|
||||
|
||||
@typedoc "Multiple codepoints that may be perceived as a single character by readers"
|
||||
@type grapheme :: t
|
||||
|
||||
@typedoc "Pattern used in functions like `replace/3` and `split/2`"
|
||||
@type pattern :: t | [t] | :binary.cp()
|
||||
|
||||
@conditional_mappings [:greek]
|
||||
|
||||
@doc """
|
||||
Checks if a string contains only printable characters.
|
||||
Checks if a string contains only printable characters up to `character_limit`.
|
||||
|
||||
Takes an optional `limit` as a second argument. `printable?/2` only
|
||||
checks the printability of the string up to the `limit`.
|
||||
Takes an optional `character_limit` as a second argument. If `character_limit` is `0`, this
|
||||
function will return `true`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -227,37 +240,47 @@ defmodule String do
|
||||
iex> String.printable?("abc" <> <<0>>, 2)
|
||||
true
|
||||
|
||||
"""
|
||||
@spec printable?(t) :: boolean
|
||||
@spec printable?(t, non_neg_integer | :infinity) :: boolean
|
||||
def printable?(string, counter \\ :infinity)
|
||||
iex> String.printable?("abc" <> <<0>>, 0)
|
||||
true
|
||||
|
||||
def printable?(<<>>, _), do: true
|
||||
def printable?(_, 0), do: true
|
||||
"""
|
||||
@spec printable?(t, 0) :: true
|
||||
@spec printable?(t, pos_integer | :infinity) :: boolean
|
||||
def printable?(string, character_limit \\ :infinity)
|
||||
when is_binary(string) and
|
||||
(character_limit == :infinity or
|
||||
(is_integer(character_limit) and character_limit >= 0)) do
|
||||
recur_printable?(string, character_limit)
|
||||
end
|
||||
|
||||
defp recur_printable?(_string, 0), do: true
|
||||
defp recur_printable?(<<>>, _character_limit), do: true
|
||||
|
||||
for char <- 0x20..0x7E do
|
||||
def printable?(<<unquote(char), rest::binary>>, counter) do
|
||||
printable?(rest, decrement(counter))
|
||||
defp recur_printable?(<<unquote(char), rest::binary>>, character_limit) do
|
||||
recur_printable?(rest, decrement(character_limit))
|
||||
end
|
||||
end
|
||||
|
||||
for char <- '\n\r\t\v\b\f\e\d\a' do
|
||||
def printable?(<<unquote(char), rest::binary>>, counter) do
|
||||
printable?(rest, decrement(counter))
|
||||
defp recur_printable?(<<unquote(char), rest::binary>>, character_limit) do
|
||||
recur_printable?(rest, decrement(character_limit))
|
||||
end
|
||||
end
|
||||
|
||||
def printable?(<<char::utf8, rest::binary>>, counter)
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
when char in 0x10000..0x10FFFF do
|
||||
printable?(rest, decrement(counter))
|
||||
defp recur_printable?(<<char::utf8, rest::binary>>, character_limit)
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
when char in 0x10000..0x10FFFF do
|
||||
recur_printable?(rest, decrement(character_limit))
|
||||
end
|
||||
|
||||
def printable?(binary, _) when is_binary(binary), do: false
|
||||
defp recur_printable?(_string, _character_limit) do
|
||||
false
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
defp decrement(character_limit), do: character_limit - 1
|
||||
|
||||
@doc ~S"""
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
@@ -287,7 +310,8 @@ defmodule String do
|
||||
Divides a string into substrings based on a pattern.
|
||||
|
||||
Returns a list of these substrings. The pattern can
|
||||
be a string, a list of strings, or a regular expression.
|
||||
be a string, a list of strings, a regular expression,
|
||||
or a compiled pattern.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, but can be controlled via the `:parts` option.
|
||||
@@ -343,6 +367,12 @@ defmodule String do
|
||||
iex> String.split("abc", ~r{b}, include_captures: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
A compiled pattern:
|
||||
|
||||
iex> pattern = :binary.compile_pattern([" ", ","])
|
||||
iex> String.split("1,2 3,4", pattern)
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
Splitting on empty string returns graphemes:
|
||||
|
||||
iex> String.split("abc", "")
|
||||
@@ -357,21 +387,15 @@ defmodule String do
|
||||
iex> String.split("abc", "", parts: 3)
|
||||
["", "a", "bc"]
|
||||
|
||||
A precompiled pattern can also be given:
|
||||
|
||||
iex> pattern = :binary.compile_pattern([" ", ","])
|
||||
iex> String.split("1,2 3,4", pattern)
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
Note this function can split within or across grapheme boundaries.
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns true:
|
||||
"e" and the acute accent. The following returns `true`:
|
||||
|
||||
iex> String.split(String.normalize("é", :nfd), "e")
|
||||
["", "́"]
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return false:
|
||||
accent, then it will return `false`:
|
||||
|
||||
iex> String.split(String.normalize("é", :nfc), "e")
|
||||
["é"]
|
||||
@@ -433,8 +457,8 @@ defmodule String do
|
||||
@doc """
|
||||
Returns an enumerable that splits a string on demand.
|
||||
|
||||
This is in contrast to `split/3` which splits all
|
||||
the string upfront.
|
||||
This is in contrast to `split/3` which splits the
|
||||
entire string upfront.
|
||||
|
||||
Note splitter does not support regular expressions
|
||||
(as it is often more efficient to have the regular
|
||||
@@ -456,6 +480,12 @@ defmodule String do
|
||||
iex> String.splitter("abcd", "", trim: true) |> Enum.take(10)
|
||||
["a", "b", "c", "d"]
|
||||
|
||||
A compiled pattern can also be given:
|
||||
|
||||
iex> pattern = :binary.compile_pattern([" ", ","])
|
||||
iex> String.splitter("1,2 3,4 5,6 7,8,...,99999", pattern) |> Enum.take(4)
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
"""
|
||||
@spec splitter(t, pattern, keyword) :: Enumerable.t()
|
||||
def splitter(string, pattern, options \\ [])
|
||||
@@ -508,19 +538,19 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split_at "sweetelixir", 5
|
||||
iex> String.split_at("sweetelixir", 5)
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "sweetelixir", -6
|
||||
iex> String.split_at("sweetelixir", -6)
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "abc", 0
|
||||
iex> String.split_at("abc", 0)
|
||||
{"", "abc"}
|
||||
|
||||
iex> String.split_at "abc", 1000
|
||||
iex> String.split_at("abc", 1000)
|
||||
{"abc", ""}
|
||||
|
||||
iex> String.split_at "abc", -1000
|
||||
iex> String.split_at("abc", -1000)
|
||||
{"", "abc"}
|
||||
|
||||
"""
|
||||
@@ -635,20 +665,28 @@ defmodule String do
|
||||
@spec upcase(t, :default | :ascii | :greek) :: t
|
||||
def upcase(string, mode \\ :default)
|
||||
|
||||
def upcase("", _mode) do
|
||||
""
|
||||
end
|
||||
|
||||
def upcase(string, :default) when is_binary(string) do
|
||||
String.Casing.upcase(string, "", :default)
|
||||
String.Casing.upcase(string, [], :default)
|
||||
end
|
||||
|
||||
def upcase(string, :ascii) when is_binary(string) do
|
||||
for <<x <- string>>,
|
||||
do: if(x >= ?a and x <= ?z, do: <<x - 32>>, else: <<x>>),
|
||||
into: ""
|
||||
IO.iodata_to_binary(upcase_ascii(string))
|
||||
end
|
||||
|
||||
def upcase(string, mode) when mode in @conditional_mappings do
|
||||
String.Casing.upcase(string, "", mode)
|
||||
String.Casing.upcase(string, [], mode)
|
||||
end
|
||||
|
||||
defp upcase_ascii(<<char, rest::bits>>) when char >= ?a and char <= ?z,
|
||||
do: [char - 32 | upcase_ascii(rest)]
|
||||
|
||||
defp upcase_ascii(<<char, rest::bits>>), do: [char | upcase_ascii(rest)]
|
||||
defp upcase_ascii(<<>>), do: []
|
||||
|
||||
@doc """
|
||||
Converts all characters in the given string to lowercase according to `mode`.
|
||||
|
||||
@@ -678,7 +716,7 @@ defmodule String do
|
||||
And `:greek` properly handles the context sensitive sigma in Greek:
|
||||
|
||||
iex> String.downcase("ΣΣ")
|
||||
"ςς"
|
||||
"σσ"
|
||||
|
||||
iex> String.downcase("ΣΣ", :greek)
|
||||
"σς"
|
||||
@@ -687,20 +725,28 @@ defmodule String do
|
||||
@spec downcase(t, :default | :ascii | :greek) :: t
|
||||
def downcase(string, mode \\ :default)
|
||||
|
||||
def downcase("", _mode) do
|
||||
""
|
||||
end
|
||||
|
||||
def downcase(string, :default) when is_binary(string) do
|
||||
String.Casing.downcase(string, "", :default)
|
||||
String.Casing.downcase(string, [], :default)
|
||||
end
|
||||
|
||||
def downcase(string, :ascii) when is_binary(string) do
|
||||
for <<x <- string>>,
|
||||
do: if(x >= ?A and x <= ?Z, do: <<x + 32>>, else: <<x>>),
|
||||
into: ""
|
||||
IO.iodata_to_binary(downcase_ascii(string))
|
||||
end
|
||||
|
||||
def downcase(string, mode) when mode in @conditional_mappings do
|
||||
String.Casing.downcase(string, "", mode)
|
||||
String.Casing.downcase(string, [], mode)
|
||||
end
|
||||
|
||||
defp downcase_ascii(<<char, rest::bits>>) when char >= ?A and char <= ?Z,
|
||||
do: [char + 32 | downcase_ascii(rest)]
|
||||
|
||||
defp downcase_ascii(<<char, rest::bits>>), do: [char | downcase_ascii(rest)]
|
||||
defp downcase_ascii(<<>>), do: []
|
||||
|
||||
@doc """
|
||||
Converts the first character in the given string to
|
||||
uppercase and the remainder to lowercase according to `mode`.
|
||||
@@ -737,12 +783,12 @@ defmodule String do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim_trailing/1 instead"
|
||||
defdelegate rstrip(binary), to: String.Break, as: :trim_trailing
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim_trailing/2 with a binary as second argument instead"
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
replace_trailing(string, <<char::utf8>>, "")
|
||||
end
|
||||
@@ -952,26 +998,26 @@ defmodule String do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim_leading/1 instead"
|
||||
defdelegate lstrip(binary), to: String.Break, as: :trim_leading
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim_leading/2 with a binary as second argument instead"
|
||||
def lstrip(string, char) when is_integer(char) do
|
||||
replace_leading(string, <<char::utf8>>, "")
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim/1 instead"
|
||||
def strip(string) do
|
||||
trim(string)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.trim/2 with a binary second argument instead"
|
||||
def strip(string, char) do
|
||||
trim(string, <<char::utf8>>)
|
||||
end
|
||||
@@ -1196,15 +1242,29 @@ defmodule String do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
def rjust(subject, len, pad \\ ?\s) when is_integer(pad) and is_integer(len) and len >= 0 do
|
||||
@deprecated "Use String.pad_leading/2 instead"
|
||||
def rjust(subject, len) do
|
||||
rjust(subject, len, ?\s)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.pad_leading/3 with a binary padding instead"
|
||||
def rjust(subject, len, pad) when is_integer(pad) and is_integer(len) and len >= 0 do
|
||||
pad(:leading, subject, len, [<<pad::utf8>>])
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
def ljust(subject, len, pad \\ ?\s) when is_integer(pad) and is_integer(len) and len >= 0 do
|
||||
@deprecated "Use String.pad_trailing/2 instead"
|
||||
def ljust(subject, len) do
|
||||
ljust(subject, len, ?\s)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.pad_trailing/3 with a binary padding instead"
|
||||
def ljust(subject, len, pad) when is_integer(pad) and is_integer(len) and len >= 0 do
|
||||
pad(:trailing, subject, len, [<<pad::utf8>>])
|
||||
end
|
||||
|
||||
@@ -1212,7 +1272,7 @@ defmodule String do
|
||||
Returns a new string created by replacing occurrences of `pattern` in
|
||||
`subject` with `replacement`.
|
||||
|
||||
The `pattern` may be a string or a regular expression.
|
||||
The `pattern` may be a string, a regular expression, or a compiled pattern.
|
||||
|
||||
By default it replaces all occurrences but this behaviour can be controlled
|
||||
through the `:global` option; see the "Options" section below.
|
||||
@@ -1263,6 +1323,12 @@ defmodule String do
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
A compiled pattern can also be given:
|
||||
|
||||
iex> pattern = :binary.compile_pattern(",")
|
||||
iex> String.replace("a,b,c", pattern, "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
|
||||
When an empty string is provided as a `pattern`, the function will treat it as
|
||||
an implicit empty string between each grapheme and the string will be
|
||||
interspersed. If an empty string is provided as `replacement` the `subject`
|
||||
@@ -1335,7 +1401,7 @@ defmodule String do
|
||||
"̀e"
|
||||
iex> String.reverse("̀e")
|
||||
"è"
|
||||
iex> String.reverse String.reverse("̀e")
|
||||
iex> String.reverse(String.reverse("̀e"))
|
||||
"è"
|
||||
|
||||
In the first example the accent is before the vowel, so
|
||||
@@ -1438,13 +1504,13 @@ defmodule String do
|
||||
iex> String.valid?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid?(<<0xFFFF :: 16>>)
|
||||
iex> String.valid?(<<0xFFFF::16>>)
|
||||
false
|
||||
|
||||
iex> String.valid?(<<0xEF, 0xB7, 0x90>>)
|
||||
true
|
||||
|
||||
iex> String.valid?("asd" <> <<0xFFFF :: 16>>)
|
||||
iex> String.valid?("asd" <> <<0xFFFF::16>>)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -1457,7 +1523,7 @@ defmodule String do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Use String.valid?/1 instead"
|
||||
def valid_character?(string) do
|
||||
case string do
|
||||
<<_::utf8>> -> valid?(string)
|
||||
@@ -1566,7 +1632,7 @@ defmodule String do
|
||||
@spec next_grapheme(t) :: {grapheme, t} | nil
|
||||
def next_grapheme(binary) do
|
||||
case next_grapheme_size(binary) do
|
||||
{size, rest} -> {:binary.part(binary, 0, size), rest}
|
||||
{size, rest} -> {binary_part(binary, 0, size), rest}
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
@@ -1728,7 +1794,7 @@ defmodule String do
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme
|
||||
@spec slice(t, integer, non_neg_integer) :: grapheme
|
||||
|
||||
def slice(_, _, 0) do
|
||||
""
|
||||
@@ -1857,36 +1923,55 @@ defmodule String do
|
||||
@doc """
|
||||
Returns `true` if `string` starts with any of the prefixes given.
|
||||
|
||||
`prefix` can be either a single prefix or a list of prefixes.
|
||||
`prefix` can be either a string, a list of strings, or a compiled
|
||||
pattern.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
iex> String.starts_with?("elixir", "eli")
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
iex> String.starts_with?("elixir", ["erlang", "elixir"])
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
iex> String.starts_with?("elixir", ["erlang", "ruby"])
|
||||
false
|
||||
|
||||
A compiled pattern can also be given:
|
||||
|
||||
iex> pattern = :binary.compile_pattern(["erlang", "elixir"])
|
||||
iex> String.starts_with?("elixir", pattern)
|
||||
true
|
||||
|
||||
An empty string will always match:
|
||||
|
||||
iex> String.starts_with? "elixir", ""
|
||||
iex> String.starts_with?("elixir", "")
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["", "other"]
|
||||
iex> String.starts_with?("elixir", ["", "other"])
|
||||
true
|
||||
|
||||
"""
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
def starts_with?(string, []) when is_binary(string) do
|
||||
false
|
||||
@spec starts_with?(t, pattern) :: boolean
|
||||
def starts_with?(string, prefix) when is_binary(string) and is_binary(prefix) do
|
||||
starts_with_string?(string, byte_size(string), prefix)
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) when is_binary(string) and is_list(prefix) do
|
||||
"" in prefix or Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
string_size = byte_size(string)
|
||||
Enum.any?(prefix, &starts_with_string?(string, string_size, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) when is_binary(string) do
|
||||
"" == prefix or Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
@compile {:inline, starts_with_string?: 3}
|
||||
defp starts_with_string?(string, string_size, prefix) when is_binary(prefix) do
|
||||
prefix_size = byte_size(prefix)
|
||||
|
||||
if prefix_size <= string_size do
|
||||
prefix == binary_part(string, 0, prefix_size)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1896,39 +1981,40 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
iex> String.ends_with?("language", "age")
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
iex> String.ends_with?("language", ["youth", "age"])
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
iex> String.ends_with?("language", ["youth", "elixir"])
|
||||
false
|
||||
|
||||
An empty suffix will always match:
|
||||
|
||||
iex> String.ends_with? "language", ""
|
||||
iex> String.ends_with?("language", "")
|
||||
true
|
||||
iex> String.ends_with? "language", ["", "other"]
|
||||
iex> String.ends_with?("language", ["", "other"])
|
||||
true
|
||||
|
||||
"""
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
def ends_with?(string, suffixes) when is_binary(string) and is_list(suffixes) do
|
||||
Enum.any?(suffixes, &do_ends_with(string, &1))
|
||||
def ends_with?(string, suffix) when is_binary(string) and is_binary(suffix) do
|
||||
ends_with_string?(string, byte_size(string), suffix)
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) when is_binary(string) do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_string, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
def ends_with?(string, suffix) when is_binary(string) and is_list(suffix) do
|
||||
string_size = byte_size(string)
|
||||
Enum.any?(suffix, &ends_with_string?(string, string_size, &1))
|
||||
end
|
||||
|
||||
@compile {:inline, ends_with_string?: 3}
|
||||
defp ends_with_string?(string, string_size, suffix) when is_binary(suffix) do
|
||||
suffix_size = byte_size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
suffix_size <= string_size and :nomatch != :binary.match(string, suffix, scope: scope)
|
||||
|
||||
if suffix_size <= string_size do
|
||||
suffix == binary_part(string, string_size - suffix_size, suffix_size)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1951,39 +2037,40 @@ defmodule String do
|
||||
@doc """
|
||||
Checks if `string` contains any of the given `contents`.
|
||||
|
||||
`contents` can be either a single string or a list of strings.
|
||||
`contents` can be either a string, a list of strings,
|
||||
or a compiled pattern.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.contains? "elixir of life", "of"
|
||||
iex> String.contains?("elixir of life", "of")
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["life", "death"]
|
||||
iex> String.contains?("elixir of life", ["life", "death"])
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["death", "mercury"]
|
||||
iex> String.contains?("elixir of life", ["death", "mercury"])
|
||||
false
|
||||
|
||||
The argument can also be a compiled pattern:
|
||||
|
||||
iex> pattern = :binary.compile_pattern(["life", "death"])
|
||||
iex> String.contains?("elixir of life", pattern)
|
||||
true
|
||||
|
||||
An empty string will always match:
|
||||
|
||||
iex> String.contains? "elixir of life", ""
|
||||
iex> String.contains?("elixir of life", "")
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["", "other"]
|
||||
true
|
||||
|
||||
The argument can also be a precompiled pattern:
|
||||
|
||||
iex> pattern = :binary.compile_pattern(["life", "death"])
|
||||
iex> String.contains? "elixir of life", pattern
|
||||
iex> String.contains?("elixir of life", ["", "other"])
|
||||
true
|
||||
|
||||
Note this function can match within or across grapheme boundaries.
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns true:
|
||||
"e" and the acute accent. The following returns `true`:
|
||||
|
||||
iex> String.contains?(String.normalize("é", :nfd), "e")
|
||||
true
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return false:
|
||||
accent, then it will return `false`:
|
||||
|
||||
iex> String.contains?(String.normalize("é", :nfc), "e")
|
||||
false
|
||||
@@ -2016,6 +2103,7 @@ defmodule String do
|
||||
|
||||
iex> String.to_charlist("æß")
|
||||
'æß'
|
||||
|
||||
"""
|
||||
@spec to_charlist(t) :: charlist
|
||||
def to_charlist(string) when is_binary(string) do
|
||||
@@ -2035,14 +2123,14 @@ defmodule String do
|
||||
Converts a string to an atom.
|
||||
|
||||
Warning: this function creates atoms dynamically and atoms are
|
||||
not garbage collected. Therefore, `string` should not be an
|
||||
not garbage-collected. Therefore, `string` should not be an
|
||||
untrusted value, such as input received from a socket or during
|
||||
a web request. Consider using `to_existing_atom/1` instead.
|
||||
|
||||
By default, the maximum number of atoms is `1_048_576`. This limit
|
||||
can be raised or lowered using the VM option `+t`.
|
||||
|
||||
The maximum atom size is of 255 characters. Prior to OTP 20,
|
||||
The maximum atom size is of 255 characters. Prior to Erlang/OTP 20,
|
||||
only latin1 characters are allowed.
|
||||
|
||||
Inlined by the compiler.
|
||||
@@ -2061,7 +2149,7 @@ defmodule String do
|
||||
@doc """
|
||||
Converts a string to an existing atom.
|
||||
|
||||
The maximum atom size is of 255 characters. Prior to OTP 20,
|
||||
The maximum atom size is of 255 characters. Prior to Erlang/OTP 20,
|
||||
only latin1 characters are allowed.
|
||||
|
||||
Inlined by the compiler.
|
||||
@@ -2152,6 +2240,8 @@ defmodule String do
|
||||
0.8222222222222223
|
||||
iex> String.jaro_distance("even", "odd")
|
||||
0.0
|
||||
iex> String.jaro_distance("same", "same")
|
||||
1.0
|
||||
|
||||
"""
|
||||
@spec jaro_distance(t, t) :: float
|
||||
@@ -2246,16 +2336,16 @@ defmodule String do
|
||||
[eq: "fox ", del: "ho", ins: "jum", eq: "ps over the ", del: "dog", ins: "lazy cat"]
|
||||
|
||||
"""
|
||||
@spec myers_difference(t, t) :: [{:eq | :ins | :del, t}] | nil
|
||||
@spec myers_difference(t, t) :: [{:eq | :ins | :del, t}]
|
||||
def myers_difference(string1, string2) do
|
||||
graphemes(string1)
|
||||
|> List.myers_difference(graphemes(string2))
|
||||
|> Enum.map(fn {kind, chars} -> {kind, IO.iodata_to_binary(chars)} end)
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.to_charlist/1 instead"
|
||||
@spec to_char_list(t) :: charlist
|
||||
def to_char_list(string), do: String.to_charlist(string)
|
||||
end
|
||||
|
||||
+76
-10
@@ -15,7 +15,7 @@ defmodule StringIO do
|
||||
|
||||
use GenServer
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Creates an IO device.
|
||||
|
||||
`string` will be the initial input of the newly created
|
||||
@@ -23,7 +23,59 @@ defmodule StringIO do
|
||||
|
||||
If the `:capture_prompt` option is set to `true`,
|
||||
prompts (specified as arguments to `IO.get*` functions)
|
||||
are captured.
|
||||
are captured in the output.
|
||||
|
||||
The device will be created and sent to the function given.
|
||||
When the function returns, the device will be closed. The final
|
||||
result will be a tuple with `:ok` and the result of the function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> StringIO.open("foo", [], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", "The input was foo"}}
|
||||
|
||||
iex> StringIO.open("foo", [capture_prompt: true], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", ">The input was foo"}}
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec open(binary, keyword, (pid -> res)) :: {:ok, res} when res: var
|
||||
def open(string, options, function)
|
||||
when is_binary(string) and is_list(options) and is_function(function, 1) do
|
||||
{:ok, pid} = GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
|
||||
try do
|
||||
{:ok, function.(pid)}
|
||||
after
|
||||
{:ok, {_input, _output}} = close(pid)
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Creates an IO device.
|
||||
|
||||
`string` will be the initial input of the newly created
|
||||
device.
|
||||
|
||||
`options_or_function` can be a keyword list of options or
|
||||
a function.
|
||||
|
||||
If options are provided, the result will be `{:ok, pid}`, returning the
|
||||
IO device created. The option `:capture_prompt`, when set to `true`, causes
|
||||
prompts (which are specified as arguments to `IO.get*` functions) to be
|
||||
included in the device's output.
|
||||
|
||||
If a function is provided, the device will be created and sent to the
|
||||
function. When the function returns, the device will be closed. The final
|
||||
result will be a tuple with `:ok` and the result of the function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -39,10 +91,25 @@ defmodule StringIO do
|
||||
iex> StringIO.contents(pid)
|
||||
{"", ">"}
|
||||
|
||||
iex> StringIO.open("foo", fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", "The input was foo"}}
|
||||
|
||||
"""
|
||||
@spec open(binary, keyword) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
@spec open(binary, (pid -> res)) :: {:ok, res} when res: var
|
||||
def open(path, options_or_function \\ [])
|
||||
|
||||
def open(string, options_or_function) when is_binary(string) and is_list(options_or_function) do
|
||||
GenServer.start_link(__MODULE__, {string, options_or_function}, [])
|
||||
end
|
||||
|
||||
def open(string, options_or_function)
|
||||
when is_binary(string) and is_function(options_or_function, 1) do
|
||||
open(string, [], options_or_function)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -99,20 +166,23 @@ defmodule StringIO do
|
||||
|
||||
## callbacks
|
||||
|
||||
@impl true
|
||||
def init({string, options}) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:io_request, from, reply_as, req}, state) do
|
||||
state = io_request(from, reply_as, req, state)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(message, state) do
|
||||
super(message, state)
|
||||
def handle_info(_message, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:contents, _from, %{input: input, output: output} = state) do
|
||||
{:reply, {input, output}, state}
|
||||
end
|
||||
@@ -125,10 +195,6 @@ defmodule StringIO do
|
||||
{:stop, :normal, {:ok, {input, output}}, state}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
super(request, from, state)
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, state) do
|
||||
{reply, state} = io_request(req, state)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
|
||||
+105
-102
@@ -28,16 +28,19 @@ defmodule Supervisor do
|
||||
|
||||
## Callbacks
|
||||
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [head | tail]) do
|
||||
{:reply, head, tail}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, t) do
|
||||
{:noreply, [h | t]}
|
||||
@impl true
|
||||
def handle_cast({:push, head}, tail) do
|
||||
{:noreply, [head | tail]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,21 +96,18 @@ defmodule Supervisor do
|
||||
|
||||
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.
|
||||
workers and/or supervisors as children, with each one having its own
|
||||
configuration (as outlined in the “Child specification” section).
|
||||
|
||||
The rest of this document will cover how child processes are started,
|
||||
how they can be specified, different supervision strategies and more.
|
||||
|
||||
## Start and shutdown
|
||||
|
||||
When the supervisor starts, it traverses all children and retrieves
|
||||
each child specification. It is at this moment `{Stack, [:hello]}`
|
||||
becomes a child specification by calling `Stack.child_spec([:hello])`.
|
||||
|
||||
Then the supervisor starts each child in the order they are defined.
|
||||
This is done by calling the function defined under the `:start` key
|
||||
in the child specification and typically defaults to `start_link/1`.
|
||||
When the supervisor starts, it traverses all child specifications and
|
||||
then starts each child in the order they are defined. This is done by
|
||||
calling the function defined under the `:start` key in the child
|
||||
specification and typically defaults to `start_link/1`.
|
||||
|
||||
The `start_link/1` (or a custom) is then called for each child process.
|
||||
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
|
||||
@@ -124,8 +124,8 @@ defmodule Supervisor do
|
||||
then awaiting for a time interval for the child process to terminate. This
|
||||
interval defaults to 5000 milliseconds. If the child process does not
|
||||
terminate in this interval, the supervisor abruptly terminates the child
|
||||
with reason `:brutal_kill`. The shutdown time can be configured in the
|
||||
child specification which is fully detailed in the next section.
|
||||
with reason `:kill`. The shutdown time can be configured in the child
|
||||
specification which is fully detailed in the next section.
|
||||
|
||||
If the child process is not trapping exits, it will shutdown immediately
|
||||
when it receives the first exit signal. If the child process is trapping
|
||||
@@ -144,15 +144,15 @@ defmodule Supervisor do
|
||||
|
||||
## Child specification
|
||||
|
||||
The child specification describes how the supervisor start, shutdown and
|
||||
restart child processes.
|
||||
The child specification describes how the supervisor starts, shuts down,
|
||||
and restarts child processes.
|
||||
|
||||
The child specification contains 5 keys. The first two are required
|
||||
The child specification contains 6 keys. The first two are required,
|
||||
and the remaining ones are optional:
|
||||
|
||||
* `:id` - a value used to identify the child specification
|
||||
* `:id` - any term used to identify the child specification
|
||||
internally by the supervisor; defaults to the given module.
|
||||
In case of conflicting `:id`, the supervisor will refuse
|
||||
In the case of conflicting `:id` values, the supervisor will refuse
|
||||
to initialize and require explicit IDs. This key is required.
|
||||
|
||||
* `:start` - a tuple with the module-function-args to be invoked
|
||||
@@ -167,11 +167,11 @@ defmodule Supervisor do
|
||||
is optional and defaults to `5000` if the type is `:worker` or
|
||||
`:infinity` if the type is `:supervisor`.
|
||||
|
||||
* `:type` - if the child process is a `:worker` or a `:supervisor`.
|
||||
This key is optional and defaults to `:worker`.
|
||||
* `:type` - specifies that the child process is a `:worker` or a
|
||||
`:supervisor`. This key is optional and defaults to `:worker`.
|
||||
|
||||
There is a sixth key, called `:modules`, which is rarely changed and
|
||||
it is set automatically based on the value in `:start`.
|
||||
There is a sixth key, `:modules`, that is rarely changed. It is set
|
||||
automatically based on the value in `:start`.
|
||||
|
||||
Let's understand what the `:shutdown` and `:restart` options control.
|
||||
|
||||
@@ -197,8 +197,8 @@ defmodule Supervisor do
|
||||
supervisor, the recommended value is `:infinity` to give the supervisor
|
||||
and its children enough time to shutdown. This option can be used with
|
||||
regular workers but doing so is discouraged and requires extreme care.
|
||||
If not used carefully and the child process does not terminate, it means
|
||||
your application will never terminate as well.
|
||||
If not used carefully, the child process will never terminate,
|
||||
preventing your application from terminating as well.
|
||||
|
||||
### Restart values (:restart)
|
||||
|
||||
@@ -212,11 +212,12 @@ defmodule Supervisor do
|
||||
* `:permanent` - the child process is always restarted.
|
||||
|
||||
* `:temporary` - the child process is never restarted, regardless
|
||||
of the supervision strategy.
|
||||
of the supervision strategy: any termination (even abnormal) is
|
||||
considered successful.
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e., with an exit reason other than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`.
|
||||
`:normal`, `:shutdown`, or `{:shutdown, term}`.
|
||||
|
||||
For a more complete understanding of the exit reasons and their
|
||||
impact, see the "Exit reasons and restarts" section.
|
||||
@@ -274,9 +275,20 @@ defmodule Supervisor do
|
||||
with other developers and they can add it directly to their supervision tree
|
||||
without worrying about the low-level details of the worker.
|
||||
|
||||
If you need to access or modify how a worker or a supervisor runs, you can use
|
||||
the `Supervisor.child_spec/2` function. For example, to run the stack with a
|
||||
different `:id` and a `:shutdown` value of 10 seconds (10_000 milliseconds):
|
||||
Overall, the child specification can be one of the following:
|
||||
|
||||
* a map representing the child specification itself - as outlined in the
|
||||
"Child specification" section
|
||||
* a tuple with a module as first element and the start argument as second -
|
||||
such as `{Stack, [:hello]}`. In this case, `Stack.child_spec([:hello])`
|
||||
is called to retrieve the child specification
|
||||
* a module - such as `Stack`. In this case, `Stack.child_spec([])`
|
||||
is called to retrieve the child specification
|
||||
|
||||
If you need to convert how a tuple or module child specification to a map or
|
||||
modify a child specification, you can use the `Supervisor.child_spec/2` function.
|
||||
For example, to run the stack with a different `:id` and a `:shutdown` value of
|
||||
10 seconds (10_000 milliseconds):
|
||||
|
||||
children = [
|
||||
Supervisor.child_spec({Stack, [:hello]}, id: MyStack, shutdown: 10_000)
|
||||
@@ -301,7 +313,9 @@ defmodule Supervisor do
|
||||
function.
|
||||
|
||||
You may also completely override the `child_spec/1` function in the Stack module
|
||||
and return your own child specification.
|
||||
and return your own child specification. Note there is no guarantee the `child_spec/1`
|
||||
function will be called by the Supervisor process, as other processes may invoke
|
||||
it to retrieve the child specification before reaching the supervisor.
|
||||
|
||||
## Exit reasons and restarts
|
||||
|
||||
@@ -324,7 +338,7 @@ defmodule Supervisor do
|
||||
restarts in transient mode, and linked processes exit with the same
|
||||
reason unless they're trapping exits
|
||||
|
||||
Notice that supervisor that reached maximum restart intensity will exit with
|
||||
Notice that the supervisor that reaches maximum restart intensity will exit with
|
||||
`:shutdown` reason. In this case the supervisor will only be restarted if its
|
||||
child specification was defined with the `:restart` option set to `:permanent`
|
||||
(the default).
|
||||
@@ -343,6 +357,7 @@ defmodule Supervisor do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
children = [
|
||||
{Stack, [:hello]}
|
||||
@@ -355,26 +370,27 @@ defmodule Supervisor do
|
||||
The difference between the two approaches is that a module-based
|
||||
supervisor gives you more direct control over how the supervisor
|
||||
is initialized. Instead of calling `Supervisor.start_link/2` with
|
||||
a list of children that are automatically initialized, we have
|
||||
defined a supervisor alongside its `c:init/1` callback and manually
|
||||
initialized the children by calling `Supervisor.init/2`, passing
|
||||
the same arguments we would have given to `start_link/2`.
|
||||
a list of children that are automatically initialized, we manually
|
||||
initialized the children by calling `Supervisor.init/2` inside its
|
||||
`c:init/1` callback.
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
`use Supervisor` also defines a `child_spec/1` function which allows
|
||||
us to run `MyApp.Supervisor` as a child of another supervisor:
|
||||
|
||||
* You need to perform some particular action on supervisor
|
||||
initialization, like setting up an ETS table.
|
||||
children = [
|
||||
MyApp.Supervisor
|
||||
]
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. The module-based approach allow you to add and remove
|
||||
children on a case-by-case basis.
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Note `use Supervisor` defines a `child_spec/1` function, allowing
|
||||
the defined module itself to be put under a supervision tree.
|
||||
The generated `child_spec/1` can be customized with the following
|
||||
options:
|
||||
A general guideline is to use the supervisor without a callback
|
||||
module only at the top of your supervision tree, generally in the
|
||||
`c:Application.start/2` callback. We recommend using module-based
|
||||
supervisors for any other supervisor in your application, so they
|
||||
can run as a child of another supervision in the tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the supervisor should be restarted, defaults to `:permanent`
|
||||
|
||||
@@ -393,28 +409,14 @@ defmodule Supervisor do
|
||||
{Stack, [:hello]}
|
||||
], strategy: :one_for_one)
|
||||
|
||||
Although we have mentioned that the supervisor automatically expands
|
||||
`{Stack, [:hello]}` to a child specification by calling
|
||||
`Stack.child_spec([:hello])`, we haven't formally defined all of the
|
||||
arguments accepted by `start_link/2` and `init/2`. Let's rectify that
|
||||
now.
|
||||
|
||||
The first argument given to `start_link/2` is a list of children which may
|
||||
be either:
|
||||
|
||||
* a map representing the child specification itself - as outlined in the
|
||||
"Child specification" section
|
||||
* a tuple with a module as first element and the start argument as second -
|
||||
such as `{Stack, [:hello]}`. In this case, `Stack.child_spec([:hello])`
|
||||
is called to retrieve the child specification
|
||||
* a module - such as `Stack`. In this case, `Stack.child_spec([])`
|
||||
is called to retrieve the child specification
|
||||
The first argument given to `start_link/2` and `init/2` is a list of child
|
||||
specifications as defined in the "child_spec/1" section above.
|
||||
|
||||
The second argument is a keyword list of options:
|
||||
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one` or `:one_for_all`. See the
|
||||
"Strategies" section.
|
||||
* `:strategy` - the supervision strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one` or `:one_for_all`. Required.
|
||||
See the "Strategies" section.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
a time frame. Defaults to `3`.
|
||||
@@ -422,8 +424,9 @@ defmodule Supervisor do
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to `5`.
|
||||
|
||||
The `:strategy` option is required and by default a maximum of 3 restarts
|
||||
is allowed within 5 seconds.
|
||||
* `:name` - a name to register the supervisor process. Supported values are
|
||||
explained in the "Name registration" section in the documentation for
|
||||
`GenServer`. Optional.
|
||||
|
||||
### Strategies
|
||||
|
||||
@@ -437,16 +440,19 @@ defmodule Supervisor do
|
||||
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
|
||||
one 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 terminated child
|
||||
process and the rest of the children started after it, are terminated and
|
||||
restarted.
|
||||
|
||||
In the above, process termination refers to unsuccessful termination, which
|
||||
is determined by the `:restart` option.
|
||||
|
||||
There is also a deprecated strategy called `:simple_one_for_one` which
|
||||
has been replaced by the `DynamicSupervisor`. The `:simple_one_for_one`
|
||||
supervisor was similar to `:one_for_one` but suits better when dynamically
|
||||
attaching children. Many functions in this module behaved slightly
|
||||
differently when this strategy is used.
|
||||
differently when this strategy was used. See the `DynamicSupervisor` module
|
||||
for more information and migration strategies.
|
||||
|
||||
## Name registration
|
||||
|
||||
@@ -456,12 +462,15 @@ defmodule Supervisor do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
import Supervisor.Spec
|
||||
@behaviour Supervisor
|
||||
@opts unquote(opts)
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
@@ -469,13 +478,10 @@ defmodule Supervisor do
|
||||
type: :supervisor
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
Supervisor.child_spec(default, unquote(Macro.escape(opts)))
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@doc false
|
||||
def init(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -527,10 +533,10 @@ defmodule Supervisor do
|
||||
# Note we have inlined all types for readability
|
||||
@typedoc "The supervisor specification"
|
||||
@type child_spec :: %{
|
||||
required(:id) => term(),
|
||||
required(:start) => {module(), function(), [term()]},
|
||||
required(:id) => atom() | term(),
|
||||
required(:start) => {module(), atom(), [term()]},
|
||||
optional(:restart) => :permanent | :transient | :temporary,
|
||||
optional(:shutdown) => :brutal_kill | non_neg_integer() | :infinity,
|
||||
optional(:shutdown) => timeout() | :brutal_kill,
|
||||
optional(:type) => :worker | :supervisor,
|
||||
optional(:modules) => [module()] | :dynamic
|
||||
}
|
||||
@@ -591,8 +597,8 @@ defmodule Supervisor do
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
* `:strategy` - the supervision strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or the deprecated
|
||||
`:simple_one_for_one`.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
@@ -605,6 +611,7 @@ defmodule Supervisor do
|
||||
is allowed within 5 seconds. Check the `Supervisor` module for a detailed
|
||||
description of the available strategies.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.8.
|
||||
@spec init([:supervisor.child_spec() | {module, term} | module], [init_option]) :: {:ok, tuple}
|
||||
def init(children, options) when is_list(children) and is_list(options) do
|
||||
@@ -627,7 +634,7 @@ defmodule Supervisor do
|
||||
module.child_spec(arg)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
case System.stacktrace() do
|
||||
case __STACKTRACE__ do
|
||||
[{^module, :child_spec, [^arg], _} | _] ->
|
||||
raise ArgumentError, child_spec_error(module)
|
||||
|
||||
@@ -647,7 +654,7 @@ defmodule Supervisor do
|
||||
|
||||
defp init_child(other) do
|
||||
raise ArgumentError, """
|
||||
supervisors expect each child to be one of:
|
||||
supervisors expect each child to be one of the following:
|
||||
|
||||
* a module
|
||||
* a {module, arg} tuple
|
||||
@@ -705,8 +712,8 @@ defmodule Supervisor do
|
||||
If a module is given, the specification is retrieved by calling
|
||||
`module.child_spec(arg)`.
|
||||
|
||||
After the child specification is retrieved, the fields on `config`
|
||||
are directly applied on the child spec. If `config` has keys that
|
||||
After the child specification is retrieved, the fields on `overrides`
|
||||
are directly applied on the child spec. If `overrides` has keys that
|
||||
do not map to any child specification field, an error is raised.
|
||||
|
||||
See the "Child specification" section in the module documentation
|
||||
@@ -722,14 +729,6 @@ defmodule Supervisor do
|
||||
#=> %{id: {Agent, 1},
|
||||
#=> start: {Agent, :start_link, [fn -> :ok end]}}
|
||||
|
||||
It may also be used when there is a need to change the number
|
||||
of arguments when starting a module under a `:simple_one_for_one`
|
||||
strategy, since most args may be given dynamically:
|
||||
|
||||
Supervisor.child_spec(Agent, start: {Agent, :start_link, []})
|
||||
#=> %{id: Agent,
|
||||
#=> start: {Agent, :start_link, []}}
|
||||
|
||||
"""
|
||||
@spec child_spec(child_spec() | {module, arg :: term} | module, keyword) :: child_spec()
|
||||
def child_spec(module_or_map, overrides)
|
||||
@@ -768,6 +767,10 @@ defmodule Supervisor do
|
||||
name, the supported values are described in the "Name registration"
|
||||
section in the `GenServer` module docs.
|
||||
"""
|
||||
|
||||
# It is important to keep the 2-arity spec because it is a catch
|
||||
# all to start_link(children, options).
|
||||
@spec start_link(module, term) :: on_start
|
||||
@spec start_link(module, term, GenServer.options()) :: on_start
|
||||
def start_link(module, arg, options \\ []) when is_list(options) do
|
||||
case Keyword.get(options, :name) do
|
||||
@@ -785,7 +788,7 @@ defmodule Supervisor do
|
||||
|
||||
other ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
expected :name option to be one of the following:
|
||||
|
||||
* nil
|
||||
* atom
|
||||
@@ -821,13 +824,13 @@ defmodule Supervisor do
|
||||
returns `{:error, error}` where `error` is a term containing information about
|
||||
the error and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, :supervisor.child_spec() | {module, term} | module) ::
|
||||
@spec start_child(supervisor, :supervisor.child_spec() | {module, term} | module | [term]) ::
|
||||
on_start_child
|
||||
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
|
||||
call(supervisor, {:start_child, child_spec})
|
||||
end
|
||||
|
||||
# TODO: Deprecate this on Elixir v1.8
|
||||
# TODO: Deprecate this on Elixir v1.8. Remove and update typespec on v2.0.
|
||||
def start_child(supervisor, args) when is_list(args) do
|
||||
call(supervisor, {:start_child, args})
|
||||
end
|
||||
@@ -852,9 +855,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec terminate_child(supervisor, term()) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
# TODO: Deprecate this on Elixir v1.8
|
||||
def terminate_child(supervisor, child_id)
|
||||
|
||||
# TODO: Deprecate this clause on Elixir v1.8
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
call(supervisor, {:terminate_child, pid})
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Supervisor.Spec do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
Outdated functions for building child specifications.
|
||||
|
||||
The functions in this module are deprecated and they do not work
|
||||
with the module-based child specs introduced in Elixir v1.5.
|
||||
@@ -106,6 +106,9 @@ defmodule Supervisor.Spec do
|
||||
|
||||
"""
|
||||
|
||||
@moduledoc deprecated:
|
||||
"Use the new child specifications outlined in the Supervisor module instead"
|
||||
|
||||
# TODO: Deprecate all functions in this module on Elixir v1.8.
|
||||
# Also deprecate entry in Supervisor.Default.
|
||||
|
||||
|
||||
+26
-17
@@ -78,7 +78,7 @@ defmodule System do
|
||||
`:micro_seconds` and `:nano_seconds` as time units although Elixir normalizes
|
||||
their spelling to match the SI convention.
|
||||
"""
|
||||
# TODO: Warn all old mappings once Elixir requires Erlang/OTP 19.1+
|
||||
# TODO: Warn all old mappings once Elixir requires Erlang/OTP 19.1+ (on v1.8)
|
||||
@type time_unit ::
|
||||
:second
|
||||
| :millisecond
|
||||
@@ -139,7 +139,11 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
IO.iodata_to_binary(:httpd_util.rfc1123_date())
|
||||
{{year, month, day}, {hour, minute, second}} = :calendar.universal_time()
|
||||
|
||||
"~4..0b-~2..0b-~2..0bT~2..0b:~2..0b:~2..0bZ"
|
||||
|> :io_lib.format([year, month, day, hour, minute, second])
|
||||
|> :erlang.iolist_to_binary()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -186,7 +190,7 @@ defmodule System do
|
||||
{:ok, v} = Version.parse(version())
|
||||
|
||||
revision_string = if v.pre != [] and revision() != "", do: " (#{revision()})", else: ""
|
||||
otp_version_string = " (compiled with OTP #{get_otp_release()})"
|
||||
otp_version_string = " (compiled with Erlang/OTP #{get_otp_release()})"
|
||||
|
||||
version() <> revision_string <> otp_version_string
|
||||
end
|
||||
@@ -441,14 +445,19 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Last exception stacktrace.
|
||||
Deprecated mechanism to retrieve the last exception stacktrace.
|
||||
|
||||
Accessing the stacktrace outside of a rescue/catch is deprecated.
|
||||
If you want to support only Elixir v1.7+, you must access
|
||||
`__STACKTRACE__/0` inside a rescue/catch. If you want to support
|
||||
earlier Elixir versions, move `System.stacktrace/0` inside a rescue/catch.
|
||||
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
|
||||
Inlined by the compiler into `:erlang.get_stacktrace/0`.
|
||||
"""
|
||||
# TODO: Fully deprecate it on Elixir v1.9.
|
||||
# It is currently partially deprecated in elixir_dispatch.erl
|
||||
def stacktrace do
|
||||
:erlang.get_stacktrace()
|
||||
end
|
||||
@@ -514,6 +523,7 @@ defmodule System do
|
||||
System.stop(1)
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec stop(non_neg_integer | binary) :: no_return
|
||||
def stop(status \\ 0)
|
||||
|
||||
@@ -551,13 +561,13 @@ defmodule System do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> System.cmd "echo", ["hello"]
|
||||
iex> System.cmd("echo", ["hello"])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd "echo", ["hello"], env: [{"MIX_ENV", "test"}]
|
||||
iex> System.cmd("echo", ["hello"], env: [{"MIX_ENV", "test"}])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd "echo", ["hello"], into: IO.stream(:stdio, :line)
|
||||
iex> System.cmd("echo", ["hello"], into: IO.stream(:stdio, :line))
|
||||
hello
|
||||
{%IO.Stream{}, 0}
|
||||
|
||||
@@ -629,9 +639,8 @@ defmodule System do
|
||||
do_cmd(Port.open({:spawn_executable, cmd}, opts), initial, fun)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{acc, status} -> {fun.(acc, :done), status}
|
||||
end
|
||||
@@ -690,7 +699,7 @@ defmodule System do
|
||||
This time is monotonically increasing and starts in an unspecified
|
||||
point in time.
|
||||
|
||||
Inlined by the compiler into `:erlang.monotonic_time/0`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monotonic_time() :: integer
|
||||
def monotonic_time do
|
||||
@@ -715,7 +724,7 @@ defmodule System do
|
||||
case of time warps although the VM works towards aligning
|
||||
them. This time is not monotonic.
|
||||
|
||||
Inlined by the compiler into `:erlang.system_time/0`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec system_time() :: integer
|
||||
def system_time do
|
||||
@@ -760,7 +769,7 @@ defmodule System do
|
||||
|
||||
See `time_offset/1` for more information.
|
||||
|
||||
Inlined by the compiler into `:erlang.time_offset/0`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec time_offset() :: integer
|
||||
def time_offset do
|
||||
@@ -789,7 +798,7 @@ defmodule System do
|
||||
This time may be adjusted forwards or backwards in time
|
||||
with no limitation and is not monotonic.
|
||||
|
||||
Inlined by the compiler into `:os.system_time/0`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec os_time() :: integer
|
||||
def os_time do
|
||||
@@ -808,7 +817,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the OTP release number.
|
||||
Returns the Erlang/OTP release number.
|
||||
"""
|
||||
@spec otp_release :: String.t()
|
||||
def otp_release do
|
||||
@@ -852,7 +861,7 @@ defmodule System do
|
||||
All modifiers listed above can be combined; repeated modifiers in `modifiers`
|
||||
will be ignored.
|
||||
|
||||
Inlined by the compiler into `:erlang.unique_integer/1`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec unique_integer([:positive | :monotonic]) :: integer
|
||||
def unique_integer(modifiers \\ []) do
|
||||
|
||||
+42
-16
@@ -56,8 +56,18 @@ defmodule Task do
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
It is also possible to spawn a task under a supervisor.
|
||||
It is often done by defining the task in its own module:
|
||||
It is also possible to spawn a task under a supervisor. The `Task`
|
||||
module implements the `child_spec/1` function, which allows it to
|
||||
be started directly under a supervisor by passing a tuple with
|
||||
a function to run:
|
||||
|
||||
Supervisor.start_link([
|
||||
{Task, fn -> ... some function ... end}
|
||||
])
|
||||
|
||||
However, if you want to invoke a specific module, function and
|
||||
arguments, or give the task process a name, you need to define
|
||||
the task in its own module:
|
||||
|
||||
defmodule MyTask do
|
||||
use Task
|
||||
@@ -73,7 +83,9 @@ defmodule Task do
|
||||
|
||||
And then passing it to the supervisor:
|
||||
|
||||
Supervisor.start_link([MyTask])
|
||||
Supervisor.start_link([
|
||||
{MyTask, arg}
|
||||
])
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
@@ -84,7 +96,7 @@ defmodule Task do
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:temporary`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -173,7 +185,12 @@ defmodule Task do
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns a specification to start a task under a supervisor.
|
||||
|
||||
See `Supervisor`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def child_spec(arg) do
|
||||
%{
|
||||
id: Task,
|
||||
@@ -184,10 +201,13 @@ defmodule Task do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
@opts unquote(opts)
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
@doc false
|
||||
See `Supervisor`.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
@@ -195,7 +215,7 @@ defmodule Task do
|
||||
restart: :temporary
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
Supervisor.child_spec(default, unquote(Macro.escape(opts)))
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
@@ -368,7 +388,7 @@ defmodule Task do
|
||||
Defaults to `true`.
|
||||
* `:timeout` - the maximum amount of time (in milliseconds) each
|
||||
task is allowed to execute for. Defaults to `5000`.
|
||||
* `:on_timeout` - what do to when a task times out. The possible
|
||||
* `:on_timeout` - what to do when a task times out. The possible
|
||||
values are:
|
||||
* `:exit` (default) - the process that spawned the tasks exits.
|
||||
* `:kill_task` - the task that timed out is killed. The value
|
||||
@@ -389,6 +409,7 @@ defmodule Task do
|
||||
Enum.to_list(stream)
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream(Enumerable.t(), module, atom, [term], keyword) :: Enumerable.t()
|
||||
def async_stream(enumerable, module, function, args, options \\ [])
|
||||
when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
@@ -408,12 +429,13 @@ defmodule Task do
|
||||
Count the codepoints in each string asynchronously, then add the counts together using reduce.
|
||||
|
||||
iex> strings = ["long string", "longer string", "there are many of these"]
|
||||
iex> stream = Task.async_stream(strings, fn text -> text |> String.codepoints |> Enum.count end)
|
||||
iex> stream = Task.async_stream(strings, fn text -> text |> String.codepoints() |> Enum.count() end)
|
||||
iex> Enum.reduce(stream, 0, fn {:ok, num}, acc -> num + acc end)
|
||||
47
|
||||
|
||||
See `async_stream/5` for discussion, options, and more examples.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream(Enumerable.t(), (term -> term), keyword) :: Enumerable.t()
|
||||
def async_stream(enumerable, fun, options \\ []) when is_function(fun, 1) do
|
||||
build_stream(enumerable, fun, options)
|
||||
@@ -431,8 +453,8 @@ defmodule Task do
|
||||
defp get_info(pid) do
|
||||
self_or_name =
|
||||
case Process.info(pid, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
{:registered_name, name} when is_atom(name) -> name
|
||||
_ -> pid
|
||||
end
|
||||
|
||||
{node(), self_or_name}
|
||||
@@ -496,7 +518,7 @@ defmodule Task do
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
@deprecated "Pattern match on the message directly instead"
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value(tasks, fn
|
||||
%Task{ref: ^ref} = task ->
|
||||
@@ -640,7 +662,11 @@ defmodule Task do
|
||||
@spec yield_many([t], timeout) :: [{t, {:ok, term} | {:exit, term} | nil}]
|
||||
def yield_many(tasks, timeout \\ 5000) do
|
||||
timeout_ref = make_ref()
|
||||
timer_ref = Process.send_after(self(), timeout_ref, timeout)
|
||||
|
||||
timer_ref =
|
||||
if timeout != :infinity do
|
||||
Process.send_after(self(), timeout_ref, timeout)
|
||||
end
|
||||
|
||||
try do
|
||||
yield_many(tasks, timeout_ref, :infinity)
|
||||
@@ -648,7 +674,7 @@ defmodule Task do
|
||||
{:noconnection, reason} ->
|
||||
exit({reason, {__MODULE__, :yield_many, [tasks, timeout]}})
|
||||
after
|
||||
Process.cancel_timer(timer_ref)
|
||||
timer_ref && Process.cancel_timer(timer_ref)
|
||||
receive do: (^timeout_ref -> :ok), after: (0 -> :ok)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,8 +74,8 @@ defmodule Task.Supervised do
|
||||
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, module} = Function.info(fun, :module)
|
||||
{:name, name} = Function.info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
@@ -83,31 +83,44 @@ defmodule Task.Supervised do
|
||||
{mod, fun, length(args)}
|
||||
end
|
||||
|
||||
# TODO: Remove conditionals once we depend on Erlang/OTP 20+
|
||||
defp do_apply(info, {module, fun, args} = mfa) do
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
catch
|
||||
:error, value ->
|
||||
reason = {value, System.stacktrace()}
|
||||
exit(info, mfa, reason, reason)
|
||||
reason = {value, __STACKTRACE__}
|
||||
log(info, mfa, reason)
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '20' do
|
||||
:erlang.raise(:error, value, __STACKTRACE__)
|
||||
else
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
:throw, value ->
|
||||
reason = {{:nocatch, value}, System.stacktrace()}
|
||||
exit(info, mfa, reason, reason)
|
||||
reason = {{:nocatch, value}, __STACKTRACE__}
|
||||
log(info, mfa, reason)
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '20' do
|
||||
:erlang.raise(:throw, value, __STACKTRACE__)
|
||||
else
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
:exit, value
|
||||
when value == :normal
|
||||
when value == :shutdown
|
||||
when tuple_size(value) == 2 and elem(value, 0) == :shutdown ->
|
||||
:erlang.raise(:exit, value, __STACKTRACE__)
|
||||
|
||||
:exit, value ->
|
||||
exit(info, mfa, {value, System.stacktrace()}, value)
|
||||
log(info, mfa, {value, __STACKTRACE__})
|
||||
:erlang.raise(:exit, value, __STACKTRACE__)
|
||||
end
|
||||
end
|
||||
|
||||
defp exit(_info, _mfa, _log_reason, 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, log_reason, reason) do
|
||||
defp log(info, mfa, reason) do
|
||||
{fun, args} = get_running(mfa)
|
||||
|
||||
message =
|
||||
@@ -116,16 +129,14 @@ defmodule Task.Supervised do
|
||||
'** When function == ~p~n' ++
|
||||
'** arguments == ~p~n' ++ '** Reason for termination == ~n' ++ '** ~p~n'
|
||||
|
||||
:error_logger.format(message, [self(), get_from(info), fun, args, get_reason(log_reason)])
|
||||
|
||||
exit(reason)
|
||||
:error_logger.format(message, [self(), get_from(info), fun, args, get_reason(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}
|
||||
defp get_running({mod, fun, args}), do: {Function.capture(mod, fun, length(args)), args}
|
||||
|
||||
defp get_reason({:undef, [{mod, fun, args, _info} | _] = stacktrace} = reason)
|
||||
when is_atom(mod) and is_atom(fun) do
|
||||
@@ -295,9 +306,8 @@ defmodule Task.Supervised do
|
||||
next.({:cont, []})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
{:suspended, [value], next} ->
|
||||
waiting = stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout)
|
||||
@@ -324,10 +334,9 @@ defmodule Task.Supervised do
|
||||
reducer.(reply, acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
is_function(next) && next.({:halt, []})
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -354,10 +363,9 @@ defmodule Task.Supervised do
|
||||
reducer.(reply, acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
is_function(next) && next.({:halt, []})
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
else
|
||||
pair ->
|
||||
stream_deliver(
|
||||
|
||||
@@ -5,8 +5,18 @@ defmodule Task.Supervisor do
|
||||
This module defines a supervisor which can be used to dynamically
|
||||
supervise tasks.
|
||||
|
||||
`start_link/1` can be used to start the supervisor. See the `Task`
|
||||
module for more examples.
|
||||
A task supervisor is started with no children, often under a
|
||||
supervisor and a name:
|
||||
|
||||
children = [
|
||||
{Task.Supervisor, name: MyApp.TaskSupervisor}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
The options given in the child specification are documented in `start_link/1`.
|
||||
|
||||
See the `Task` module for more examples.
|
||||
|
||||
## Name registration
|
||||
|
||||
@@ -20,16 +30,18 @@ defmodule Task.Supervisor do
|
||||
| {:restart, :supervisor.restart()}
|
||||
| {:shutdown, :supervisor.shutdown()}
|
||||
|
||||
@typedoc "Supervisor spec used by `async_stream`"
|
||||
@type async_stream_supervisor ::
|
||||
Supervisor.supervisor()
|
||||
| (term -> Supervisor.supervisor())
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
def child_spec(opts) when is_list(opts) do
|
||||
id =
|
||||
case Keyword.get(opts, :name, Task.Supervisor) do
|
||||
name when is_atom(name) -> name
|
||||
{:global, name} -> name
|
||||
{:via, _module, name} -> name
|
||||
end
|
||||
|
||||
%{
|
||||
id: Task.Supervisor,
|
||||
start: {Task.Supervisor, :start_link, [arg]},
|
||||
id: id,
|
||||
start: {Task.Supervisor, :start_link, [opts]},
|
||||
type: :supervisor
|
||||
}
|
||||
end
|
||||
@@ -37,17 +49,33 @@ defmodule Task.Supervisor do
|
||||
@doc """
|
||||
Starts a new supervisor.
|
||||
|
||||
The supported options are:
|
||||
## Examples
|
||||
|
||||
A task supervisor is typically started under a supervision tree using
|
||||
the tuple format:
|
||||
|
||||
{Task.Supervisor, name: MyApp.TaskSupervisor}
|
||||
|
||||
You can also start it by calling `start_link/1` directly:
|
||||
|
||||
Task.Supervisor.start_link(name: MyApp.TaskSupervisor)
|
||||
|
||||
But this is recommended only for scripting and should be avoided in
|
||||
production code. Generally speaking, processes should always be started
|
||||
inside supervision trees.
|
||||
|
||||
## Options
|
||||
|
||||
* `:name` - used to register a supervisor name, the supported values are
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:max_restarts`, `:max_seconds` and `:max_children` - as specified in `DynamicSupervisor`;
|
||||
* `:max_restarts`, `:max_seconds` and `:max_children` - as specified in
|
||||
`DynamicSupervisor`;
|
||||
|
||||
This function could also receive `:restart` and `:shutdown` as options
|
||||
but those two options have been deprecated and it is now preferred to
|
||||
give them directly to `start_child` and `async` when supported.
|
||||
give them directly to `start_child` and `async`.
|
||||
"""
|
||||
@spec start_link([option]) :: Supervisor.on_start()
|
||||
# TODO: Deprecate passing restart and shutdown here on Elixir v1.8.
|
||||
@@ -158,15 +186,6 @@ defmodule Task.Supervisor do
|
||||
own task. The tasks will be spawned under the given `supervisor` and
|
||||
linked to the current process, similarly to `async/4`.
|
||||
|
||||
You may also provide a function as the `supervisor`. Before each task is
|
||||
started, the function will be invoked (in a new process which is linked to
|
||||
the current process) with the stream entry that the to-be-spawned task will
|
||||
process as its argument. The function should return a supervisor pid or name,
|
||||
which will be used to spawn the task. This allows one to dynamically start
|
||||
tasks in different locations in the supervision tree(s) on the local (or
|
||||
another) node. Notably, this enables the distribution of concurrent stream
|
||||
tasks over multiple nodes.
|
||||
|
||||
When streamed, each task will emit `{:ok, value}` upon successful
|
||||
completion or `{:exit, reason}` if the caller is trapping exits.
|
||||
Results are emitted in the same order as the original `enumerable`.
|
||||
@@ -207,7 +226,8 @@ defmodule Task.Supervisor do
|
||||
Enum.to_list(stream)
|
||||
|
||||
"""
|
||||
@spec async_stream(async_stream_supervisor, Enumerable.t(), module, atom, [term], keyword) ::
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream(Supervisor.supervisor(), Enumerable.t(), module, atom, [term], keyword) ::
|
||||
Enumerable.t()
|
||||
def async_stream(supervisor, enumerable, module, function, args, options \\ [])
|
||||
when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
@@ -224,7 +244,8 @@ defmodule Task.Supervisor do
|
||||
|
||||
See `async_stream/6` for discussion, options, and examples.
|
||||
"""
|
||||
@spec async_stream(async_stream_supervisor, Enumerable.t(), (term -> term), keyword) ::
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream(Supervisor.supervisor(), Enumerable.t(), (term -> term), keyword) ::
|
||||
Enumerable.t()
|
||||
def async_stream(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
|
||||
build_stream(supervisor, :link, enumerable, fun, options)
|
||||
@@ -240,8 +261,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
See `async_stream/6` for discussion, options, and examples.
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream_nolink(
|
||||
async_stream_supervisor,
|
||||
Supervisor.supervisor(),
|
||||
Enumerable.t(),
|
||||
module,
|
||||
atom,
|
||||
@@ -263,7 +285,8 @@ defmodule Task.Supervisor do
|
||||
|
||||
See `async_stream/6` for discussion and examples.
|
||||
"""
|
||||
@spec async_stream_nolink(async_stream_supervisor, Enumerable.t(), (term -> term), keyword) ::
|
||||
@doc since: "1.4.0"
|
||||
@spec async_stream_nolink(Supervisor.supervisor(), Enumerable.t(), (term -> term), keyword) ::
|
||||
Enumerable.t()
|
||||
def async_stream_nolink(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
|
||||
build_stream(supervisor, :nolink, enumerable, fun, options)
|
||||
@@ -305,7 +328,8 @@ defmodule Task.Supervisor do
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
||||
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor(), (() -> any)) :: {:ok, pid}
|
||||
@spec start_child(Supervisor.supervisor(), (() -> any), keyword) ::
|
||||
DynamicSupervisor.on_start_child()
|
||||
def start_child(supervisor, fun, options \\ []) do
|
||||
restart = options[:restart]
|
||||
shutdown = options[:shutdown]
|
||||
@@ -319,7 +343,8 @@ defmodule Task.Supervisor do
|
||||
Similar to `start_child/2` except the task is specified
|
||||
by the given `module`, `fun` and `args`.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor(), module, atom, [term]) :: {:ok, pid}
|
||||
@spec start_child(Supervisor.supervisor(), module, atom, [term], keyword) ::
|
||||
DynamicSupervisor.on_start_child()
|
||||
def start_child(supervisor, module, fun, args, options \\ [])
|
||||
when is_atom(fun) and is_list(args) do
|
||||
restart = options[:restart]
|
||||
@@ -329,15 +354,17 @@ defmodule Task.Supervisor do
|
||||
end
|
||||
|
||||
defp start_child_with_spec(supervisor, args, restart, shutdown) do
|
||||
# TODO: Remove this on Elixir v2.0 and the associated clause in DynamicSupervisor
|
||||
# TODO: This only exists because we need to support reading restart/shutdown
|
||||
# from two different places. Remove this and the associated clause in DynamicSupervisor
|
||||
# on Elixir v2.0
|
||||
GenServer.call(supervisor, {:start_task, args, restart, shutdown}, :infinity)
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
name =
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self
|
||||
{:registered_name, name} -> name
|
||||
{:registered_name, name} when is_atom(name) -> name
|
||||
_ -> self
|
||||
end
|
||||
|
||||
{node(), name}
|
||||
@@ -354,27 +381,12 @@ defmodule Task.Supervisor do
|
||||
%Task{pid: pid, ref: ref, owner: owner}
|
||||
end
|
||||
|
||||
defp supervisor_fun(supervisor, {_module, _fun, _args})
|
||||
when is_function(supervisor, 1) do
|
||||
fn {_module, _fun, [entry | _rest_args]} -> supervisor.(entry) end
|
||||
end
|
||||
|
||||
defp supervisor_fun(supervisor, fun)
|
||||
when is_function(supervisor, 1) and is_function(fun, 1) do
|
||||
fn {_erlang, _apply, [_fun, [entry]]} -> supervisor.(entry) end
|
||||
end
|
||||
|
||||
defp supervisor_fun(supervisor, _fun) do
|
||||
fn _mfa -> supervisor end
|
||||
end
|
||||
|
||||
defp build_stream(supervisor, link_type, enumerable, fun, options) do
|
||||
supervisor_fun = supervisor_fun(supervisor, fun)
|
||||
shutdown = options[:shutdown]
|
||||
|
||||
&Task.Supervised.stream(enumerable, &1, &2, fun, options, fn owner, mfa ->
|
||||
args = [owner, :monitor, get_info(owner), mfa]
|
||||
{:ok, pid} = start_child_with_spec(supervisor_fun.(mfa), args, :temporary, shutdown)
|
||||
{:ok, pid} = start_child_with_spec(supervisor, args, :temporary, shutdown)
|
||||
if link_type == :link, do: Process.link(pid)
|
||||
{link_type, pid}
|
||||
end)
|
||||
|
||||
+22
-45
@@ -2,59 +2,36 @@ defmodule Tuple do
|
||||
@moduledoc """
|
||||
Functions for working with tuples.
|
||||
|
||||
Tuples are ordered collections of elements. Tuples can contain elements
|
||||
of any type, and a tuple can contain elements of different types. Curly
|
||||
braces can be used to create tuples:
|
||||
Please note the following functions for tuples are found in `Kernel`:
|
||||
|
||||
* `elem/2` - access a tuple by index
|
||||
* `put_elem/3` - insert a value into a tuple by index
|
||||
* `tuple_size/1` - get the number of elements in a tuple
|
||||
|
||||
Tuples are intended as fixed-size containers for multiple elements.
|
||||
To manipulate a collection of elements, use a list instead. `Enum`
|
||||
functions do not work on tuples.
|
||||
|
||||
Tuples are denoted with curly braces:
|
||||
|
||||
iex> {}
|
||||
{}
|
||||
iex> {1, :two, "three"}
|
||||
{1, :two, "three"}
|
||||
|
||||
Tuples store elements contiguously in memory. This means accessing a
|
||||
tuple element by index doesn't depend on the number of elements in the
|
||||
tuple. We say the operation is done in constant-time, via the
|
||||
`Kernel.elem/1` function:
|
||||
A tuple may contain elements of different types, which are stored
|
||||
contiguously in memory. Accessing any element takes constant time,
|
||||
but modifying a tuple, which produces a shallow copy, takes linear time.
|
||||
Tuples are good for reading data while lists are better for traversals.
|
||||
|
||||
iex> tuple = {1, :two, "three"}
|
||||
iex> elem(tuple, 0)
|
||||
1
|
||||
iex> elem(tuple, 2)
|
||||
"three"
|
||||
Tuples are typically used either when a function has multiple return values
|
||||
or for error handling. `File.read/1` returns `{:ok, contents}` if reading
|
||||
the given file is successful, or else `{:error, reason}` such as when
|
||||
the file does not exist.
|
||||
|
||||
Same goes for getting the tuple size with `Kernel.tuple_size/1`:
|
||||
|
||||
iex> tuple_size({})
|
||||
0
|
||||
iex> tuple_size({1, 2, 3})
|
||||
3
|
||||
|
||||
Tuples being stored contiguously in memory also means that updating a tuple
|
||||
(for example replacing an element with `Kernel.put_elem/3`) will make a
|
||||
shallow copy of the whole tuple. The tuple elements are still shared thanks
|
||||
to immutability.
|
||||
|
||||
Tuples are not meant to be used as a "collection" type but rather as a
|
||||
fixed-size container for multiple elements. That's why it is not possible
|
||||
to traverse a tuple dynamically using the functions in the `Enum` module.
|
||||
|
||||
For example, tuples are often used to have functions return "enriched"
|
||||
values: a common pattern is for functions to return `{:ok, value}` for
|
||||
successful cases and `{:error, reason}` for unsuccessful cases. This is
|
||||
exactly what `File.read/1` does: it returns `{:ok, contents}` if reading
|
||||
the given file is successful, or `{:error, reason}` otherwise, such as
|
||||
when the file does not exist.
|
||||
|
||||
The most common operations performed on tuples are available in `Kernel`
|
||||
(`Kernel.tuple_size/1`, `Kernel.elem/2`, `Kernel.put_elem/3`, and others)
|
||||
and are automatically imported into your code. The functions in this module
|
||||
cover other cases, such as dynamic creation of tuples (`Tuple.duplicate/2`)
|
||||
and conversion to list (`Tuple.to_list/1`). The functions that add and remove
|
||||
elements from tuples, changing their size, are rarely used in practice, as
|
||||
they typically imply tuples are being used as collections. Even if you have
|
||||
a tuple `{:ok, atom}` and you want to append another element to it, such as
|
||||
an empty map, it is preferrable to rely on pattern matching and create a new
|
||||
tuple than manipulating it dynamically:
|
||||
The functions in this module that add and remove elements from tuples are
|
||||
rarely used in practice, as they typically imply tuples are being used as
|
||||
collections. To append to a tuple, it is preferrable to use pattern matching:
|
||||
|
||||
tuple = {:ok, :example}
|
||||
|
||||
|
||||
+26
-5
@@ -88,7 +88,7 @@ defmodule URI do
|
||||
iex> URI.encode_query(query)
|
||||
"key=value+with+spaces"
|
||||
|
||||
iex> URI.encode_query %{key: [:a, :list]}
|
||||
iex> URI.encode_query(%{key: [:a, :list]})
|
||||
** (ArgumentError) encode_query/1 values cannot be lists, got: [:a, :list]
|
||||
|
||||
"""
|
||||
@@ -128,7 +128,7 @@ defmodule URI do
|
||||
%{"percent" => "oh yes!", "starting" => "map"}
|
||||
|
||||
"""
|
||||
@spec decode_query(binary, map) :: map
|
||||
@spec decode_query(binary, %{binary => binary}) :: %{binary => binary}
|
||||
def decode_query(query, map \\ %{})
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
@@ -258,7 +258,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent-escapes all characters that require escaped in a string.
|
||||
Percent-escapes all characters that require escaping in a string.
|
||||
|
||||
This means reserved characters, such as `:` and `/`, and the so-
|
||||
called unreserved characters, which have the same meaning both
|
||||
@@ -422,11 +422,13 @@ defmodule URI do
|
||||
|
||||
parts = Regex.run(regex, string)
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
destructure [_, _, scheme, _, authority, path, query_with_question_mark, _, _, fragment],
|
||||
parts
|
||||
|
||||
scheme = nillify(scheme)
|
||||
authority = nillify(authority)
|
||||
path = nillify(path)
|
||||
query = nillify(query)
|
||||
query = nillify_query(query_with_question_mark)
|
||||
{userinfo, host, port} = split_authority(authority)
|
||||
|
||||
scheme = scheme && String.downcase(scheme)
|
||||
@@ -444,6 +446,9 @@ defmodule URI do
|
||||
}
|
||||
end
|
||||
|
||||
defp nillify_query("?" <> query), do: query
|
||||
defp nillify_query(_other), do: nil
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(string) do
|
||||
regex = Regex.recompile!(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/)
|
||||
@@ -465,12 +470,28 @@ defmodule URI do
|
||||
@doc """
|
||||
Returns the string representation of the given `URI` struct.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.to_string(URI.parse("http://google.com"))
|
||||
"http://google.com"
|
||||
|
||||
iex> URI.to_string(%URI{scheme: "foo", host: "bar.baz"})
|
||||
"foo://bar.baz"
|
||||
|
||||
Note that when creating this string representation, the `authority` will be
|
||||
used if the host is `nil`. Otherwise, the `userinfo`, `host`, and `port` will
|
||||
be used.
|
||||
|
||||
iex> URI.to_string(%URI{authority: "foo@example.com:80"})
|
||||
"//foo@example.com:80"
|
||||
|
||||
iex> URI.to_string(%URI{userinfo: "bar", host: "example.org", port: 81})
|
||||
"//bar@example.org:81"
|
||||
|
||||
iex> URI.to_string(%URI{authority: "foo@example.com:80",
|
||||
...> userinfo: "bar", host: "example.org", port: 81})
|
||||
"//bar@example.org:81"
|
||||
|
||||
"""
|
||||
@spec to_string(t) :: binary
|
||||
defdelegate to_string(uri), to: String.Chars.URI
|
||||
|
||||
@@ -106,10 +106,12 @@ defmodule Version do
|
||||
defmodule InvalidRequirementError do
|
||||
defexception [:requirement]
|
||||
|
||||
@impl true
|
||||
def exception(requirement) when is_binary(requirement) do
|
||||
%__MODULE__{requirement: requirement}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def message(%{requirement: requirement}) do
|
||||
"invalid requirement: #{inspect(requirement)}"
|
||||
end
|
||||
@@ -118,10 +120,12 @@ defmodule Version do
|
||||
defmodule InvalidVersionError do
|
||||
defexception [:version]
|
||||
|
||||
@impl true
|
||||
def exception(version) when is_binary(version) do
|
||||
%__MODULE__{version: version}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def message(%{version: version}) do
|
||||
"invalid version: #{inspect(version)}"
|
||||
end
|
||||
|
||||
@@ -1,40 +1,55 @@
|
||||
# Compatibility and Deprecations
|
||||
|
||||
Elixir is versioned according to a vMAJOR.MINOR.PATCH schema.
|
||||
|
||||
Elixir is currently at major version v1. A new backwards compatible minor release happens every 6 months. Patch releases are not scheduled and are made whenever there are bug fixes or security patches.
|
||||
|
||||
Elixir applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branch:
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.7 | Bug fixes and security patches
|
||||
1.6 | Security patches only
|
||||
1.5 | Security patches only
|
||||
1.4 | Security patches only
|
||||
1.3 | Security patches only
|
||||
|
||||
Major Elixir releases may contain breaking changes and those will be explicitly outlined in the CHANGELOG. There are currently no plans for a major v2 release.
|
||||
|
||||
## Compatibility between non-major Elixir versions
|
||||
|
||||
Elixir minor and patch releases are backwards compatible: well-defined behaviours and documented APIs in a given version will continue working on future versions.
|
||||
|
||||
Although we expect the vast majority of programs to remain compatible over time, it is impossible to guarantee that no future change will break any program. Under some unlikely circumstances, we may introduce changes that break existing code:
|
||||
|
||||
* Security: a security issue in the implementation may arise whose resolution requires backwards incompatible changes. We reserve the right to address such security issues.
|
||||
|
||||
* Bugs: if an API has undesired behaviour, a program that depends on the buggy behaviour may break if the bug is fixed. We reserve the right to fix such bugs.
|
||||
|
||||
* Compiler front-end: improvements may be done to the compiler, introducing new warnings for ambiguous modes and providing more detailed error messages. Those can lead to compilation errors (when running with `--warning-as-errors`) or tooling failures when asserting on specific error messages (although one should avoid such). We reserve the right to do such improvements.
|
||||
|
||||
* Imports: new functions may be added to the `Kernel` module, which is auto-imported. They may collide with local functions defined in your modules. Collisions can be resolved in a backwards compatible fashion using `import Kernel, except: [...]` with a list of all functions you don't want to be imported from `Kernel`. We reserve the right to do such additions.
|
||||
|
||||
In order to continue evolving the language without introducing breaking changes, Elixir will rely on deprecations to demote certain practices and promote new ones. Our deprecation policy is outlined in the ["Deprecations" section](#deprecations).
|
||||
|
||||
The only exception to the compatibility guarantees above are experimental features, which will be explicitly marked as such, and do not provide any compatibility guarantee until they are stabilized.
|
||||
|
||||
## Compatibility between Elixir and Erlang/OTP
|
||||
|
||||
Erlang/OTP versioning is independent from the versioning of Elixir. Each version of Elixir supports a specific range of Erlang/OTP versions. The compatibility table is shown below.
|
||||
|
||||
Elixir version | Supported Erlang/OTP versions
|
||||
:------------- | :----------------------------
|
||||
v1.0.0 | 17
|
||||
v1.0.1 | 17
|
||||
v1.0.2 | 17
|
||||
v1.0.3 | 17
|
||||
v1.0.4 | 17
|
||||
v1.0.5 | 17-18
|
||||
v1.1.0 | 17-18
|
||||
v1.1.1 | 17-18
|
||||
v1.2.0 | 18
|
||||
v1.2.1 | 18
|
||||
v1.2.2 | 18
|
||||
v1.2.3 | 18
|
||||
v1.2.4 | 18
|
||||
v1.2.5 | 18
|
||||
v1.2.6 | 18-19
|
||||
v1.3.0 | 18-19
|
||||
v1.3.1 | 18-19
|
||||
v1.3.2 | 18-19
|
||||
v1.3.3 | 18-19
|
||||
v1.3.4 | 18-19
|
||||
v1.4.0 | 18-19
|
||||
v1.4.1 | 18-19
|
||||
v1.4.2 | 18-19
|
||||
v1.4.3 | 18-19
|
||||
v1.4.4 | 18-19
|
||||
v1.4.5 | 18-20
|
||||
v1.5.0 | 18-20
|
||||
v1.5.1 | 18-20
|
||||
v1.5.2 | 18-20
|
||||
:------------- | :-------------------------------
|
||||
1.0 | 17 - 17 (and Erlang/OTP 18 from v1.0.5)
|
||||
1.1 | 17 - 18
|
||||
1.2 | 18 - 18 (and Erlang/OTP 19 from v1.2.6)
|
||||
1.3 | 18 - 19
|
||||
1.4 | 18 - 19 (and Erlang/OTP 20 from v1.4.5)
|
||||
1.5 | 18 - 20
|
||||
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
|
||||
1.7 | 19 - 21
|
||||
|
||||
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
|
||||
|
||||
## Deprecations
|
||||
|
||||
@@ -44,53 +59,62 @@ Elixir deprecations happen in 3 steps:
|
||||
|
||||
1. The feature is soft-deprecated. It means both CHANGELOG and documentation must list the feature as deprecated but no warning is effectively emitted by running the code. There is no requirement to soft-deprecate a feature.
|
||||
|
||||
2. The feature is effectively deprecated by emitting warnings on usage. In order to deprecate a feature, the proposed alternative MUST exist for AT LEAST two versions. For example, `Enum.uniq/2` was soft-deprecated in favor of `Enum.uniq_by/2` in Elixir v1.1. This means a deprecation warning may only be emitted by Elixir v1.3 or later.
|
||||
2. The feature is effectively deprecated by emitting warnings on usage. This is also known as hard-deprecation. In order to deprecate a feature, the proposed alternative MUST exist for AT LEAST two minor versions. For example, `Enum.uniq/2` was soft-deprecated in favor of `Enum.uniq_by/2` in Elixir v1.1. This means a deprecation warning may only be emitted by Elixir v1.3 or later.
|
||||
|
||||
3. The feature is removed. This can only happen on major releases. This means deprecated features in Elixir v1.x shall only be removed by Elixir v2.x.
|
||||
|
||||
### Table of deprecations
|
||||
|
||||
Deprecated feature | Deprecated in | Replaced by (available since)
|
||||
:----------------------------------------------- | :------------ | :----------------------------
|
||||
Deprecated feature | Hard-deprecated in | Replaced by (available since)
|
||||
:----------------------------------------------- | :----------------- | :----------------------------
|
||||
`Code.get_docs/2` | [v1.7] | `Code.fetch_docs/1` (v1.7)
|
||||
Calling `super` on GenServer callbacks | [v1.7] | Not calling super (v1.0)
|
||||
`Enum.chunk/2`[`/3/4`](Enum.chunk/4) | [v1.7] | `Enum.chunk_every/2`[`/3/4`](`Enum.chunk_every/4`) (v1.5)
|
||||
`not left in right` | [v1.7] | [`left not in right`](`Kernel.SpecialForms.in/2`) (v1.5)
|
||||
`Registry.start_link/3` | [v1.7] | `Registry.start_link/1` (v1.5)
|
||||
`Stream.chunk/2`[`/3/4`](Stream.chunk/4) | [v1.7] | `Stream.chunk_every/2`[`/3/4`](`Stream.chunk_every/4`) (v1.5)
|
||||
`Enum.partition/2` | [v1.6] | `Enum.split_with/2` (v1.4)
|
||||
`Keyword.replace/3` | [v1.6] | Use `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
|
||||
`Map.replace/3` | [v1.6] | Use `Map.fetch/2` + `Map.put/3` (v1.0)
|
||||
`Keyword.replace/3` | [v1.6] | `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
|
||||
`Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` | [v1.6] | Use `Enum.map/2` to traverse over the arguments (v1.0)
|
||||
`Range.range?/1` | [v1.6] | Pattern match on `left..right` instead (v1.0)
|
||||
`Module.add_doc/6` | [v1.6] | `@doc` module attribute (v1.0)
|
||||
`Map.replace/3` | [v1.6] | `Map.fetch/2` + `Map.put/3` (v1.0)
|
||||
`Range.range?/1` | [v1.6] | Pattern match on `_.._` (v1.0)
|
||||
`Atom.to_char_list/1` | [v1.5] | `Atom.to_charlist/1` (v1.3)
|
||||
`Enum.filter_map/3` | [v1.5] | `Enum.filter/2` + `Enum.map/2` or for comprehensions (v1.0)
|
||||
`Enum.filter_map/3` | [v1.5] | `Enum.filter/2` + `Enum.map/2` or [`for`](`Kernel.SpecialForms.for/1`) comprehensions (v1.0)
|
||||
`Float.to_char_list/1` | [v1.5] | `Float.to_charlist/1` (v1.3)
|
||||
`GenEvent` module | [v1.5] | `Supervisor` and `GenServer` (v1.0);<br/>[`GenStage`](https://hex.pm/packages/gen_stage) (v1.3);<br/>[`:gen_event`](http://www.erlang.org/doc/man/gen_event.html) (OTP 17)
|
||||
`GenEvent` module | [v1.5] | `Supervisor` and `GenServer` (v1.0);<br/>[`GenStage`](https://hex.pm/packages/gen_stage) (v1.3);<br/>[`:gen_event`](http://www.erlang.org/doc/man/gen_event.html) (Erlang/OTP 17)
|
||||
`Integer.to_char_list/1` and `Integer.to_char_list/2` | [v1.5] | `Integer.to_charlist/1` and `Integer.to_charlist/2` (v1.3)
|
||||
`Kernel.to_char_list/1` | [v1.5] | `Kernel.to_charlist/1` (v1.3)
|
||||
`List.Chars.to_char_list/1` | [v1.5] | `List.Chars.to_charlist/1` (v1.3)
|
||||
`Stream.filter_map/3` | [v1.5] | `Stream.filter/2` + `Stream.map/2` (v1.0)
|
||||
`String.ljust/3` and `String.rjust/3` | [v1.5] | `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding (v1.3)
|
||||
`String.ljust/3` and `String.rjust/3` | [v1.5] | Use `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding (v1.3)
|
||||
`String.strip/1` and `String.strip/2` | [v1.5] | `String.trim/1` and `String.trim/2` (v1.3)
|
||||
`String.lstrip/1` and `String.rstrip/1` | [v1.5] | `String.trim_leading/1` and `String.trim_trailing/1` (v1.3)
|
||||
`String.lstrip/2` and `String.rstrip/2` | [v1.5] | `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument (v1.3)
|
||||
`String.lstrip/2` and `String.rstrip/2` | [v1.5] | Use `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument (v1.3)
|
||||
`String.to_char_list/1` | [v1.5] | `String.to_charlist/1` (v1.3)
|
||||
`()` to mean `nil` | [v1.5] | `nil` (v1.0)
|
||||
`:as_char_lists` value in `t:Inspect.Opts.t/0` type | [v1.5] | `:as_charlists` (v1.3)
|
||||
`:char_lists` key in `t:Inspect.Opts.t/0` type | [v1.5] | `:charlists` (v1.3)
|
||||
`char_list/0` type | [v1.5] | `t:charlist/0` type (v1.3)
|
||||
`:char_lists` key in `t:Inspect.Opts.t/0` type | [v1.5] | `:charlists` key (v1.3)
|
||||
`:as_char_lists` value in `t:Inspect.Opts.t/0` type | [v1.5] | `:as_charlists` value (v1.3)
|
||||
`@compile {:parse_transform, _}` in `Module` | [v1.5] | *None*
|
||||
EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (= is allowed only on start expressions) (v1.0)
|
||||
EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (`<%=` is allowed only on start expressions) (v1.0)
|
||||
`Access.key/1` | [v1.4] | `Access.key/2` (v1.3)
|
||||
`Behaviour` module | [v1.4] | `@callback` (v1.0)
|
||||
`Behaviour` module | [v1.4] | `@callback` module attribute (v1.0)
|
||||
`Enum.uniq/2` | [v1.4] | `Enum.uniq_by/2` (v1.2)
|
||||
`Float.to_char_list/2` | [v1.4] | `:erlang.float_to_list/2` (OTP 17)
|
||||
`Float.to_string/2` | [v1.4] | `:erlang.float_to_binary/2` (OTP 17)
|
||||
`Float.to_char_list/2` | [v1.4] | `:erlang.float_to_list/2` (Erlang/OTP 17)
|
||||
`Float.to_string/2` | [v1.4] | `:erlang.float_to_binary/2` (Erlang/OTP 17)
|
||||
`HashDict` module | [v1.4] | `Map` (v1.2)
|
||||
`HashSet` module | [v1.4] | `MapSet` (v1.1)
|
||||
Multi-letter aliases in `OptionParser` | [v1.4] | Use single-letter aliases (v1.0)
|
||||
`Set` module | [v1.4] | `MapSet` (v1.1)
|
||||
`Stream.uniq/2` | [v1.4] | `Stream.uniq_by/2` (v1.2)
|
||||
`IEx.Helpers.import_file/2` | [v1.4] | [`IEx.Helpers.import_file_if_available/1`](https://hexdocs.pm/iex/IEx.Helpers.html#import_file_if_available/1) (v1.3)
|
||||
`IEx.Helpers.import_file/2` | [v1.4] | `IEx.Helpers.import_file_if_available/1` (v1.3)
|
||||
`Mix.Utils.camelize/1` | [v1.4] | `Macro.camelize/1` (v1.2)
|
||||
`Mix.Utils.underscore/1` | [v1.4] | `Macro.underscore/1` (v1.2)
|
||||
Variable used as function call | [v1.4] | Use parentheses (v1.0)
|
||||
Anonymous functions with no expression after `->` | [v1.4] | Use an expression or explicitly return `nil` (v1.0)
|
||||
`Dict` module | [v1.3] | `Keyword` (v1.0);<br/>`Map` (v1.2)
|
||||
Support for making private functions overridable | [v1.4] | Use public functions (v1.0)
|
||||
`Dict` module | [v1.3] | `Keyword` (v1.0) or `Map` (v1.2)
|
||||
`Keyword.size/1` | [v1.3] | `Kernel.length/1` (v1.0)
|
||||
`Map.size/1` | [v1.3] | `Kernel.map_size/1` (v1.0)
|
||||
`Set` behaviour | [v1.3] | `MapSet` data structure (v1.1)
|
||||
@@ -99,17 +123,17 @@ Anonymous functions with no expression after `->` | [v1.4] | Use an expres
|
||||
`:append_first` option in `Kernel.defdelegate/2` | [v1.3] | Define the function explicitly (v1.0)
|
||||
`/r` option in `Regex` | [v1.3] | `/U` (v1.1)
|
||||
`\x{X*}` inside strings/sigils/charlists | [v1.3] | `\uXXXX` or `\u{X*}` (v1.1)
|
||||
Map or dictionary as second argument in `Enum.group_by/3` | [v1.3] | Use `Enum.reduce/3` (v1.0)
|
||||
Map or dictionary as second argument in `Enum.group_by/3` | [v1.3] | `Enum.reduce/3` (v1.0)
|
||||
Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1.0)
|
||||
`Dict` behaviour | [v1.2] | `MapSet` data structure (v1.1)
|
||||
`Access` protocol | [v1.1] | `Access` behaviour (v1.1)
|
||||
`as: true \| false` in `alias/2` and `require/2` | [v1.1] | *None*
|
||||
`as: true \| false` in `alias/2` and `require/2` | [v1.1] | *None*
|
||||
`?\xHEX` | [v1.1] | `0xHEX` (v1.0)
|
||||
Empty string in `String.starts_with?/2`, `String.ends_with?/2`, `String.contains?/2`.<br/>*__NOTE__: Feature made back available in v1.3* | [v1.1] to [v1.2] | Explicitly check for `""` beforehand (v1.0)
|
||||
|
||||
[v1.1]: https://github.com/elixir-lang/elixir/blob/v1.1/CHANGELOG.md#4-deprecations
|
||||
[v1.2]: https://github.com/elixir-lang/elixir/blob/v1.2/CHANGELOG.md#changelog-for-elixir-v12
|
||||
[v1.3]: https://github.com/elixir-lang/elixir/blob/v1.3/CHANGELOG.md#4-deprecations
|
||||
[v1.4]: https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md#4-deprecations
|
||||
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
|
||||
[v1.6]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-deprecations
|
||||
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
+34
-21
@@ -1,17 +1,22 @@
|
||||
# Guards
|
||||
|
||||
Guards are a way to augment pattern matching with more complex checks; they are allowed in a predefined set of constructs where pattern matching is allowed.
|
||||
Guards are a way to augment pattern matching with more complex checks. They are allowed in a predefined set of constructs where pattern matching is allowed.
|
||||
|
||||
Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere. It also allows the compiler to optimize the code related to guards efficiently.
|
||||
|
||||
## List of allowed expressions
|
||||
|
||||
For reference, the following is a comprehensive list of all expressions allowed in guards:
|
||||
|
||||
* comparison operators (`==`, `!=`, `===`, `!==`, `>`, `>=`, `<`, `<=`)
|
||||
* strictly boolean operators (`and`, `or`, `not`) (the `&&`, `||`, and `!` sibling operators are not allowed as they're not *strictly* boolean - meaning they don't require both sides to be booleans)
|
||||
* arithmetic binary operators (`+`, `-`, `*`, `/`)
|
||||
* arithmetic unary operators (`+`, `-`)
|
||||
* binary concatenation operator (`<>`)
|
||||
* `in` and `not in` operators (as long as the right-hand side is a list or a range)
|
||||
* comparison operators ([`==`](`Kernel.==/2`), [`!=`](`Kernel.!=/2`), [`===`](`Kernel.===/2`), [`!==`](`Kernel.!==/2`),
|
||||
[`>`](`Kernel.>/2`), [`>=`](`Kernel.>=/2`), [`<`](`Kernel.</2`), [`<=`](`Kernel.<=/2`))
|
||||
* strictly boolean operators ([`and`](`Kernel.and/2`), [`or`](`Kernel.or/2`), [`not`](`Kernel.not/1`))
|
||||
- __NOTE__: [`&&`](`Kernel.&&/2`), [`||`](`Kernel.||/2`), and [`!`](`Kernel.!/1`) sibling operators are not allowed as they're not
|
||||
*strictly* boolean - meaning they don't require both sides to be booleans
|
||||
* arithmetic binary operators ([`+`](`Kernel.+/2`), [`-`](`Kernel.-/2`), [`*`](`Kernel.*/2`), [`/`](`Kernel.//2`))
|
||||
* arithmetic unary operators ([`+`](`Kernel.+/1`), [`-`](`Kernel.-/1`))
|
||||
* binary concatenation operator ([`<>`](`Kernel.<>/2`))
|
||||
* [`in`](`Kernel.in/2`) and [`not in`](`Kernel.in/2`) operators (as long as the right-hand side is a list or a range)
|
||||
* the following "type-check" functions (all documented in the `Kernel` module):
|
||||
* `is_atom/1`
|
||||
* `is_binary/1`
|
||||
@@ -48,12 +53,12 @@ For reference, the following is a comprehensive list of all expressions allowed
|
||||
* `trunc/1`
|
||||
* `tuple_size/1`
|
||||
* the following handful of Erlang bitwise operations, if imported from the `Bitwise` module:
|
||||
* `band/2` or the `&&&` operator
|
||||
* `bor/2` or the `|||` operator
|
||||
* `bnot/1` or the `~~~` operator
|
||||
* `bsl/1` or the `<<<` operator
|
||||
* `bsr/1` or the `>>>` operator
|
||||
* `bxor/2` or the `^^^` operator
|
||||
* [`band/2`](`Bitwise.band/2`) or the [`&&&`](`Bitwise.&&&/2`) operator
|
||||
* [`bor/2`](`Bitwise.bor/2`) or the [`|||`](`Bitwise.|||/2`) operator
|
||||
* [`bnot/1`](`Bitwise.bnot/1`) or the [`~~~`](`Bitwise.~~~/1`) operator
|
||||
* [`bsl/2`](`Bitwise.bsl/2`) or the [`<<<`](`Bitwise.<<</2`) operator
|
||||
* [`bsr/2`](`Bitwise.bsr/2`) or the [`>>>`](`Bitwise.>>>/2`) operator
|
||||
* [`bxor/2`](`Bitwise.bxor/2`) or the [`^^^`](`Bitwise.^^^/2`) operator
|
||||
|
||||
Macros constructed out of any combination of the above guards are also valid guards - for example, `Integer.is_even/1`. See the section "Defining custom guard expressions" below.
|
||||
|
||||
@@ -81,7 +86,7 @@ In the example above, we show how guards can be used in function clauses. There
|
||||
def foo(term) when is_float(term), do: round(term)
|
||||
```
|
||||
|
||||
* `case` expressions:
|
||||
* [`case`](`Kernel.SpecialForms.case/2`) expressions:
|
||||
|
||||
```elixir
|
||||
case x do
|
||||
@@ -91,7 +96,7 @@ In the example above, we show how guards can be used in function clauses. There
|
||||
end
|
||||
```
|
||||
|
||||
* anonymous functions (`fn`s):
|
||||
* anonymous functions ([`fn`](`Kernel.SpecialForms.fn/1`)s):
|
||||
|
||||
```elixir
|
||||
larger_than_two? = fn
|
||||
@@ -100,11 +105,15 @@ In the example above, we show how guards can be used in function clauses. There
|
||||
end
|
||||
```
|
||||
|
||||
Other constructs are `for`, `with`, `try`/`rescue`/`catch`/`else`/, and the `match?/2` macro in the `Kernel` module.
|
||||
* custom guards can also be defined with `Kernel.defguard/1` and `Kernel.defguardp/1`.
|
||||
A custom guard is always defined based on existing guards.
|
||||
|
||||
Other constructs are [`for`](`Kernel.SpecialForms.for/1`), [`with`](`Kernel.SpecialForms.with/1`), [`try/rescue/catch/else`](`Kernel.SpecialForms.try/1`), and the `Kernel.match?/2`.
|
||||
|
||||
## Failing guards
|
||||
|
||||
Errors in guards do not result in runtime errors, but in guards failing. For example, the `length/1` function only works with lists. If we use it with anything else, a runtime error is raised:
|
||||
In guards, when functions would normally raise exceptions, they cause the guard to fail instead.
|
||||
For example, the `length/1` function only works with lists. If we use it with anything else, a runtime error is raised:
|
||||
|
||||
```elixir
|
||||
iex> length("hello")
|
||||
@@ -125,10 +134,6 @@ iex> case "hello" do
|
||||
|
||||
In many cases, we can take advantage of this. In the code above, we used `length/1` to both check that the given thing is a list *and* check some properties of its length (instead of using `is_list(something) and length(something) > 0`).
|
||||
|
||||
## Expressions in guard clauses
|
||||
|
||||
Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice: only a predefined set of side-effect-free functions are allowed. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere. This behaviour is also coherent with pattern match, which is a naturally a side-effect-free operation. Finally, keeping expressions allowed in clauses to a close set of predefined ones allows the compiler to optimize the code related to choosing the right clause.
|
||||
|
||||
## Defining custom guard expressions
|
||||
|
||||
As mentioned before, only the expressions listed in this page are allowed in guards. However, we can take advantage of macros to write custom guards that can simplify our programs or make them more domain-specific. At the end of the day, what matters is that the *output* of the macros (which is what will be compiled) boils down to a combinations of the allowed expressions.
|
||||
@@ -165,6 +170,14 @@ def my_function(number) when is_even(number) do
|
||||
end
|
||||
```
|
||||
|
||||
While it's possible to create custom guards with macros, it's recommended to define them using `defguard` and `defguardp` which perform additional compile-time checks. Here's an example:
|
||||
|
||||
```elixir
|
||||
defmodule MyInteger do
|
||||
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
|
||||
end
|
||||
```
|
||||
|
||||
## Multiple guards in the same clause
|
||||
|
||||
There exists an additional way to simplify a chain of `or`s in guards: Elixir supports writing "multiple guards" in the same clause. This:
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
# Library Guidelines
|
||||
|
||||
This document outlines general guidelines, anti-patterns, and rules for those writing and publishing Elixir libraries meant to be consumed by other developers.
|
||||
|
||||
## Getting started
|
||||
|
||||
You can create a new Elixir library by running the `mix new` command:
|
||||
|
||||
$ mix new my_library
|
||||
|
||||
The project name is given in the `snake_case` convention where all letters are lowercase and words are separate with underscores. This is the same convention used by variables, function names and atoms in Elixir. See the [Naming Conventions](naming-conventions.html) document for more information.
|
||||
|
||||
Every project has a `mix.exs` file, with instructions on how to build, compile, run tests, and so on. Libraries commonly have a `lib` directory, which includes Elixir source code, and a `test` directory. A `src` directory may also exist for Erlang sources.
|
||||
|
||||
For more information on running your project, see the official [Mix & OTP guide](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) or [Mix documentation](https://hexdocs.pm/mix/Mix.html).
|
||||
|
||||
### Applications with supervision tree
|
||||
|
||||
The `mix new` command also allows the `--sup` flag to scaffold an application with a supervision tree out of the box. We talk about supervision trees later on when discussing one of the common anti-patterns when writing libraries.
|
||||
|
||||
## Publishing
|
||||
|
||||
Writing code is only the first of many steps to publish a package. We strongly recommend developers to:
|
||||
|
||||
* Choose a versioning schema. Elixir requires versions to be in the format `MAJOR.MINOR.PATCH` but the meaning of those numbers is up to you. Most projects choose [Semantic Versioning](https://semver.org/).
|
||||
|
||||
* Choose a [license](https://choosealicense.com/). The most common licenses in the Elixir community are the [MIT License](https://choosealicense.com/licenses/mit/) and the [Apache 2.0 License](https://choosealicense.com/licenses/apache-2.0/). The latter is also the one used by Elixir itself.
|
||||
|
||||
* Run the [code formatter](https://hexdocs.pm/mix/Mix.Tasks.Format.html). The code formatter formats your code according to a consistent style shared by your library and the whole community, making it easier for other developers to understand your code and contribute.
|
||||
|
||||
* Write tests. Elixir ships with a test-framework named [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html). The project generated by `mix new` includes sample tests and doctests.
|
||||
|
||||
* Write documentation. The Elixir community is proud of treating documentation as a first-class citizen and making documentation easily accessible. Libraries contribute to the status quo by providing complete API documentation with examples for their modules, types and functions. See the [Writing Documentation](writing-documentation.html) guide for more information. Projects like [ExDoc](https://github.com/elixir-lang/ex_doc) can be used to generate HTML and EPUB documents from the documentation. ExDoc also supports "extra pages", like this one that you are reading. Such pages augment the documentation with tutorials, guides and references.
|
||||
|
||||
Projects are often made available to other developers [by publishing a Hex package](https://hex.pm/docs/publish). Hex also [supports private packages for organizations](https://hex.pm/pricing). If ExDoc is configured for the Mix project, publishing a package on Hex will also automatically publish the generated documentation to [HexDocs](https://hexdocs.pm).
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
In this section we document common anti-patterns to avoid when writing libraries.
|
||||
|
||||
### Avoid using exceptions for control-flow
|
||||
|
||||
You should avoid using exceptions for control-flow. For example, instead of:
|
||||
|
||||
```elixir
|
||||
try do
|
||||
contents = File.read!("some_path_that_may_or_may_not_exist")
|
||||
{:it_worked, contents}
|
||||
rescue
|
||||
File.Error ->
|
||||
:it_failed
|
||||
end
|
||||
```
|
||||
|
||||
you should prefer:
|
||||
|
||||
```elixir
|
||||
case File.read("some_path_that_may_or_may_not_exist") do
|
||||
{:ok, contents} -> {:it_worked, contents}
|
||||
{:error, _} -> :it_failed
|
||||
end
|
||||
```
|
||||
|
||||
As a library author, it is your responsibility to make sure users are not required to use exceptions for control-flow in their applications. You can follow the same convention as Elixir here, using the name without `!` for returning `:ok`/`:error` tuples and appending `!` for a version of the function which raises an exception.
|
||||
|
||||
It is important to note that a name without `!` does not mean a function will never raise. For example, even `File.read/1` can fail in case of bad arguments:
|
||||
|
||||
```iex
|
||||
iex> File.read(1)
|
||||
** (FunctionClauseError) no function clause matching in IO.chardata_to_string/1
|
||||
```
|
||||
|
||||
The usage of `:ok`/`:error` tuples is about the domain that the function works on, in this case, filesystem access. Bad arguments, logical errors, invalid options should raise regardless of the function name. If in doubt, prefer to return tuples instead of raising, as users of your library can always match on the results and raise if necessary.
|
||||
|
||||
### Avoid working with invalid data
|
||||
|
||||
Elixir programs should prefer to validate data as close to the end user as possible, so the errors are easy to locate and fix. This practice also saves you from writing defensive code in the internals of the library.
|
||||
|
||||
For example, imagine you have an API that receives a filename as a binary. At some point you will want to write to this file. You could have a function like this:
|
||||
|
||||
```elixir
|
||||
def my_fun(some_arg, file_to_write_to, options \\ []) do
|
||||
...some code...
|
||||
AnotherModuleInLib.invoke_something_that_will_eventually_write_to_file(file_to_write_to)
|
||||
...more code...
|
||||
end
|
||||
```
|
||||
|
||||
The problem with the code above is that, if the user supplies an invalid input, the error will be raised deep inside the library, which makes it confusing for users. Furthermore, when you don't validate the values at the boundary, the internals of your library are never quite sure which kind of values they are working with.
|
||||
|
||||
A better function definition would be:
|
||||
|
||||
```elixir
|
||||
def my_fun(some_arg, file_to_write_to, options \\ []) when is_binary(file_to_write_to) do
|
||||
```
|
||||
|
||||
Elixir also leverages pattern matching and guards in function clauses to provide clear error messages in case invalid arguments are given.
|
||||
|
||||
This advice does not only apply to libraries but to any Elixir code. Every time you receive multiple options or work with external data, you should validate the data at the boundary and convert it to structured data. For example, if you provide a `GenServer` that can be started with multiple options, you want to validate those options when the server starts and rely only on structured data throughout the process life cycle. Similarly, if a database or a socket gives you a map of strings, after you receive the data, you should validate it and potentially convert it to a struct or a map of atoms.
|
||||
|
||||
### Avoid application configuration
|
||||
|
||||
You should avoid using [the application environment](https://hexdocs.pm/elixir/Application.html#get_env/2) as the configuration mechanism for libraries. The application environment is **global** which means it becomes impossible for two dependencies to use your library in two different ways.
|
||||
|
||||
Let's see a simple example. Imagine that you implement a library that breaks a string in two parts based on the first occurrence of the dash `-` character:
|
||||
|
||||
```elixir
|
||||
defmodule DashSplitter do
|
||||
def split(string) when is_binary(string) do
|
||||
String.split(string, "-", parts: 2)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now imagine someone wants to split the string in three parts. You decide to make the number of parts configurable via the application environment:
|
||||
|
||||
```elixir
|
||||
def split(string) when is_binary(string) do
|
||||
parts = Application.get_env(:dash_splitter, :parts, 2)
|
||||
String.split(string, "-", parts: parts)
|
||||
end
|
||||
```
|
||||
|
||||
Now users can configure your library in their `config/config.exs` file as follows:
|
||||
|
||||
```elixir
|
||||
config :dash_splitter, :parts, 3
|
||||
```
|
||||
|
||||
Once your library is configured, it will change the behaviour of all users of your library. If a library was expecting it to split the string in 2 parts, since the configuration is global, it will now split it in 3 parts.
|
||||
|
||||
The solution is to provide configuration as close as possible to where it is used and not via the application environment. In case of a function, you could expect keyword lists as a new argument:
|
||||
|
||||
```elixir
|
||||
def split(string, opts \\ []) when is_binary(string) and is_list(opts) do
|
||||
parts = Keyword.get(opts, :parts, 2)
|
||||
String.split(string, "-", parts: parts)
|
||||
end
|
||||
```
|
||||
|
||||
In case you need to configure a process, the options should be passed when starting that process.
|
||||
|
||||
The application environment should be reserved only for configurations that are truly global, for example, to control your application boot process and its supervision tree.
|
||||
|
||||
For all remaining scenarios, libraries should not force their users to use the application environment for configuration. If the user of a library believes that certain parameter should be configured globally, then they can wrap the library functionality with their own application environment configuration.
|
||||
|
||||
### Avoid `use` when an `import` is enough
|
||||
|
||||
A library should not provide `use MyLib` functionality if all `use MyLib` does is to `import`/`alias` the module itself. For example, this is an anti-pattern:
|
||||
|
||||
```elixir
|
||||
defmodule MyLib do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
import MyLib
|
||||
end
|
||||
end
|
||||
|
||||
def some_fun(arg1, arg2) do
|
||||
...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The reason why defining the `__using__` macro above should be avoided is because when a developer writes:
|
||||
|
||||
```elixir
|
||||
defmodule MyApp do
|
||||
use MyLib
|
||||
end
|
||||
```
|
||||
|
||||
it allows `use MyLib` to run *any* code into the `MyApp` module. For someone reading the code, it is impossible to assess the impact that `use MyLib` has in a module without looking at the implementation of `__using__`.
|
||||
|
||||
The following code is much clearer:
|
||||
|
||||
```elixir
|
||||
defmodule MyApp do
|
||||
import MyLib
|
||||
end
|
||||
```
|
||||
|
||||
The code above says we are only bringing in the functions from `MyLib` so we can invoke `some_fun(arg1, arg2)` directly without the `MyLib.` prefix. Even more important, `import MyLib` says that we have an option to not `import MyLib` at all as we can simply invoke the function as `MyLib.some_fun(arg1, arg2)`.
|
||||
|
||||
If the module you want to invoke a function on has a long name, such as `SomeLibrary.Namespace.MyLib`, and you find it verbose, you can leverage the `alias/2` special form and still refer to the module as `MyLib`.
|
||||
|
||||
While there are many situations where using a module is required, `use` should be skipped when all it does is to `import` or `alias` a module. In a nutshell, `alias` is simpler and clearer than `import`, and `import` is simpler and clearer than `use`.
|
||||
|
||||
### Avoid macros
|
||||
|
||||
Although the previous section could be summarized as "avoid macros", both topics are important enough to deserve their own sections.
|
||||
|
||||
To quote [the official guide on Macros](https://elixir-lang.org/getting-started/meta/macros.html):
|
||||
|
||||
> Even though Elixir attempts its best to provide a safe environment for macros, the major responsibility of writing clean code with macros falls on developers. Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.
|
||||
>
|
||||
> Elixir already provides mechanisms to write your everyday code in a simple and readable fashion by using its data structures and functions. Macros should only be used as a last resort. Remember that **explicit is better than implicit**. **Clear code is better than concise code**.
|
||||
|
||||
When you absolutely have to use a macro, make sure that a macro is not the only way the user can interface with your library and keep the amount of code generated by a macro to a minimum. For example, the `Logger` module provides `debug/2`, `info/2` and friends as macros that are capable of extracting environment information, but a low-level mechanism for logging is still available with `Logger.bare_log/3`.
|
||||
|
||||
### Avoid using processes for code organization
|
||||
|
||||
A developer must never use a process for code organization purposes. A process must be used to model runtime properties such as:
|
||||
|
||||
* Mutable state and access to shared resources (such as ets, files, etc)
|
||||
* Concurrency and distribution
|
||||
* Initialization, shutdown and restart logic (as seen in supervisors)
|
||||
* System messages such as timer messages and monitoring events
|
||||
|
||||
In Elixir, code organization is done by modules and functions, processes are not necessary. For example, imagine you are implementing a calculator and you decide to put all the calculator operations behind a `GenServer`:
|
||||
|
||||
```elixir
|
||||
def add(a, b) do
|
||||
GenServer.call(__MODULE__, {:add, a, b})
|
||||
end
|
||||
|
||||
def handle_call({:add, a, b}, _from, state) do
|
||||
{:reply, a + b, state}
|
||||
end
|
||||
|
||||
def handle_call({:subtract, a, b}, _from, state) do
|
||||
{:reply, a - b, state}
|
||||
end
|
||||
```
|
||||
|
||||
This is an anti-pattern not only because it convolutes the calculator logic but also because you put the calculator logic behind a single process that will potentially become a bottleneck in your system, especially as the number of calls grow. Instead just define the functions directly:
|
||||
|
||||
```elixir
|
||||
def add(a, b) do
|
||||
a + b
|
||||
end
|
||||
|
||||
def subtract(a, b) do
|
||||
a - b
|
||||
end
|
||||
```
|
||||
|
||||
Use processes only to model runtime properties, never for code organization. And even when you think something could be done in parallel with processes, often it is best to let the callers of your library decide how to parallelize, rather than impose a certain execution flow in users of your code.
|
||||
|
||||
### Avoid spawning unsupervised processes
|
||||
|
||||
You should avoid spawning processes outside of a supervision tree, especially long-running ones. Instead, processes must be started inside supervision trees. This guarantees developers have full control over the initialization, restarts, and shutdown of the system.
|
||||
|
||||
If your application does not have a supervision tree, one can be added by changing `def application` inside `mix.exs` to include a `:mod` key with the application callback name:
|
||||
|
||||
```elixir
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
mod: {MyApp.Application, []}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
and then defining a `my_app/application.ex` file with the following template:
|
||||
|
||||
```elixir
|
||||
defmodule MyApp.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
# List all child processes to be supervised
|
||||
children = [
|
||||
# Starts a worker by calling: MyApp.Worker.start_link(arg)
|
||||
# {MyApp.Worker, arg},
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This is the same template generated by `mix new --sup`.
|
||||
|
||||
Each process started with the application must be listed as a child under the `Supervisor` above. We call those "static processes" because they are known upfront. For handling dynamic processes, such as the ones started during requests and other user inputs, look at the `DynamicSupervisor` module.
|
||||
|
||||
One of the few times where it is acceptable to start a process outside of a supervision tree is with `Task.async/1` and `Task.await/2`. Opposite to `Task.start_link/1`, the `async/await` mechanism gives you full control over the spawned process life cycle - which is also why you must always call `Task.await/2` after starting a task with `Task.async/1`. Even though, if your application is spawning multiple async processes, you should consider using `Task.Supervisor` for better visibility when instrumenting and monitoring the system.
|
||||
@@ -15,7 +15,7 @@ Atoms can be written either in `:snake_case` or `:CamelCase`, although the conve
|
||||
|
||||
Generally speaking, filenames follow the `snake_case` convention of the module they define. For example, `MyApp` should be defined inside the `my_app.ex` file. However, this is only a convention. At the end of the day, any filename can be used as they do not affect the compiled code in any way.
|
||||
|
||||
## Underscore (_foo)
|
||||
## Underscore (`_foo`)
|
||||
|
||||
Elixir relies on underscores in different situations.
|
||||
|
||||
@@ -40,9 +40,9 @@ Due to this property, Elixir relies on functions starting with underscore to att
|
||||
iex> String.__info__(:functions)
|
||||
[at: 2, capitalize: 1, chunk: 2, ...]
|
||||
|
||||
Elixir also includes four special forms that follow the double underscore format. These forms retrieve compile-time information about the current environment: `__MODULE__/0`, `__DIR__/0`, `__ENV__/0` and `__CALLER__/0`.
|
||||
Elixir also includes five special forms that follow the double underscore format: `__CALLER__/0`, `__DIR__/0`, `__ENV__/0`and `__MODULE__/0` retrieve compile-time information about the current environment, while `__STACKTRACE__/0` retrieves the stacktrace for the current exception.
|
||||
|
||||
## Trailing bang (foo!)
|
||||
## Trailing bang (`foo!`)
|
||||
|
||||
A trailing bang (exclamation mark) signifies a function or macro where failure cases raise an exception.
|
||||
|
||||
@@ -73,7 +73,7 @@ There are also some non-paired functions, with no non-bang variant. The bang sti
|
||||
|
||||
In macro code, the bang on `Kernel.alias!/1` and `Kernel.var!/2` signifies that [macro hygiene](http://elixir-lang.org/getting-started/meta/macros.html#macros-hygiene) is set aside.
|
||||
|
||||
## Trailing question mark (foo?)
|
||||
## Trailing question mark (`foo?`)
|
||||
|
||||
Functions that return a boolean are named with a trailing question mark.
|
||||
|
||||
@@ -81,7 +81,7 @@ Examples: `Keyword.keyword?/1`, `Mix.debug?/0`, `String.contains?/2`
|
||||
|
||||
However, functions that return booleans and are valid in guards follow another convention, described next.
|
||||
|
||||
## is_ prefix (is_foo)
|
||||
## `is_` prefix (`is_foo`)
|
||||
|
||||
Type checks and other boolean checks that are allowed in guard clauses are named with an `is_` prefix.
|
||||
|
||||
|
||||
@@ -26,22 +26,22 @@ Operator
|
||||
`\|` | Right to left
|
||||
`::` | Right to left
|
||||
`when` | Right to left
|
||||
`<-`, `\\` | Left to right
|
||||
`<-` `\\` | Left to right
|
||||
|
||||
## Comparison operators
|
||||
|
||||
Elixir provides the following built-in comparison operators:
|
||||
|
||||
* `==` - equality
|
||||
* `===` - strict equality
|
||||
* `!=` - inequality
|
||||
* `!==` - strict inequality
|
||||
* `>` - greater than
|
||||
* `<` - less than
|
||||
* `>=` - greater than or equal
|
||||
* `<=` - less than or equal
|
||||
* [`==`](`Kernel.==/2`) - equality
|
||||
* [`===`](`Kernel.===/2`) - strict equality
|
||||
* [`!=`](`Kernel.!=/2`) - inequality
|
||||
* [`!==`](`Kernel.!==/2`) - strict inequality
|
||||
* [`>`](`Kernel.>/2`) - greater than
|
||||
* [`<`](`Kernel.</2`) - less than
|
||||
* [`>=`](`Kernel.>=/2`) - greater than or equal
|
||||
* [`<=`](`Kernel.<=/2`) - less than or equal
|
||||
|
||||
The only difference between `==` and `===` is that `===` is stricter when it comes to comparing integers and floats:
|
||||
The only difference between [`==`](`Kernel.==/2`) and [`===`](`Kernel.===/2`) is that [`===`](`Kernel.===/2`) is stricter when it comes to comparing integers and floats:
|
||||
|
||||
```elixir
|
||||
iex> 1 == 1.0
|
||||
@@ -50,7 +50,7 @@ iex> 1 === 1.0
|
||||
false
|
||||
```
|
||||
|
||||
`!=` and `!==` act as the negation of `==` and `===`, respectively.
|
||||
[`!=`](`Kernel.!=/2`) and [`!==`](`Kernel.!==/2`) act as the negation of [`==`](`Kernel.==/2`) and [`===`](`Kernel.===/2`), respectively.
|
||||
|
||||
### Term ordering
|
||||
|
||||
@@ -120,7 +120,7 @@ The following is a table of all the operators that Elixir is capable of parsing,
|
||||
* `^^^`
|
||||
* `~~~`
|
||||
|
||||
The following operators are used by the `Bitwise` module when imported: `&&&`, `^^^`, `<<<`, `>>>`, `|||`, `~~~`. See the documentation for `Bitwise` for more information.
|
||||
The following operators are used by the `Bitwise` module when imported: [`&&&`](`Bitwise.&&&/2`), [`^^^`](`Bitwise.^^^/2`), [`<<<`](`Bitwise.<<</2`), [`>>>`](`Bitwise.>>>/2`), [`|||`](`Bitwise.|||/2`), [`~~~`](`Bitwise.~~~/1`). See the documentation for `Bitwise` for more information.
|
||||
|
||||
### Redefining existing operators
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This document covers all of Elixir syntax constructs as a reference and then dis
|
||||
|
||||
## Reserved words
|
||||
|
||||
Those are the reserved words in the Elixir language. They are detailed throughout this guide but summed up here for convenience:
|
||||
These are the reserved words in the Elixir language. They are detailed throughout this guide but summed up here for convenience:
|
||||
|
||||
* `true`, `false`, `nil` - used as atoms
|
||||
* `when`, `and`, `or`, `not`, `in` - used as operators
|
||||
@@ -21,7 +21,7 @@ Integers (`1234`) and floats (`123.4`) in Elixir are represented as a sequence o
|
||||
|
||||
### Atoms
|
||||
|
||||
Atoms in Elixir start with a colon (`:`) which must be followed by non-combining Unicode characters and underscore. The atom may continue using a sequence of Unicode characters, including numbers, underscore and `@`. Atoms may end in `!` or `?`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require OTP 20.
|
||||
Atoms in Elixir start with a colon (`:`) which must be followed by non-combining Unicode characters and underscore. The atom may continue using a sequence of Unicode characters, including numbers, underscore and `@`. Atoms may end in `!` or `?`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require Erlang/OTP 20.
|
||||
|
||||
All operators in Elixir are also valid atoms. Valid examples are `:foo`, `:FOO`, `:foo_42`, `:foo@bar` and `:++`. Invalid examples are `:@foo` (`@` is not allowed at start), `:123` (numbers are not allowed at start) and `:(*)` (not a valid operator).
|
||||
|
||||
@@ -80,13 +80,13 @@ Structs built on the map syntax by passing the struct name between `%` and `{`.
|
||||
|
||||
### Variables
|
||||
|
||||
Variables in Elixir must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The variable may continue using a sequence of Unicode characters, including numbers and underscore. Variables may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require OTP 20.
|
||||
Variables in Elixir must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The variable may continue using a sequence of Unicode characters, including numbers and underscore. Variables may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require Erlang/OTP 20.
|
||||
|
||||
[Elixir's naming conventions](naming-conventions.html) recommend variables to be in `snake_case` format.
|
||||
|
||||
### Non-qualified calls (local calls)
|
||||
|
||||
Non-qualified calls, such as `add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require OTP 20.
|
||||
Non-qualified calls, such as `add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require Erlang/OTP 20.
|
||||
|
||||
Parentheses for non-qualified calls are optional, except for zero-arity calls, which would then be ambiguous with variables. If parentheses are used, they must immediately follow the function name *without spaces*. For example, `add (1, 2)` is a syntax error, since `(1, 2)` is treated as an invalid block which is attempted to be given as a single argument to `add`.
|
||||
|
||||
@@ -98,7 +98,7 @@ As many programming languages, Elixir also support operators as non-qualified ca
|
||||
|
||||
### Qualified calls (remote calls)
|
||||
|
||||
Qualified calls, such as `Math.add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require OTP 20.
|
||||
Qualified calls, such as `Math.add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Unicode characters require Erlang/OTP 20.
|
||||
|
||||
[Elixir's naming conventions](naming-conventions.html) recommend calls to be in `snake_case` format.
|
||||
|
||||
@@ -118,7 +118,7 @@ Blocks are multiple Elixir expressions separated by newlines or semi-colons. A n
|
||||
|
||||
### Left to right arrow
|
||||
|
||||
The left to right arrow (`->`) is used to establish a relationship between left and right. The left side may have zero, one or more arguments, the right side is zero, one or more expressions separted by new line. The `->` is always between one of the following terminators: `do`/`end`, `fn`/`end` or `(`/`)`.
|
||||
The left to right arrow (`->`) is used to establish a relationship between left and right. The left side may have zero, one or more arguments, the right side is zero, one or more expressions separated by new line. The `->` is always between one of the following terminators: `do`/`end`, `fn`/`end` or `(`/`)`.
|
||||
|
||||
It is seen on `case` and `cond` constructs between `do`/`end`:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user