Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffe7a577cc | ||
|
|
a2f14bd007 | ||
|
|
66ac6a3d8a | ||
|
|
50caa25d41 | ||
|
|
92af3fdf0f | ||
|
|
c443cdee36 | ||
|
|
8ca3876b10 | ||
|
|
c7e822345b | ||
|
|
b43a6a923e | ||
|
|
e60fe36740 | ||
|
|
f5735eb697 | ||
|
|
7002554a47 | ||
|
|
660a09b3af | ||
|
|
79388035f5 | ||
|
|
edc204f0b2 | ||
|
|
7d2cee20f6 | ||
|
|
a58a924e10 | ||
|
|
e0a9b4b476 | ||
|
|
eb8121c790 | ||
|
|
3f0608bdc3 | ||
|
|
9797a466fc | ||
|
|
418c277dfb | ||
|
|
580bd764f7 | ||
|
|
49dec48926 | ||
|
|
c8c7663c83 | ||
|
|
4e6261a392 | ||
|
|
f10cf8bdc8 | ||
|
|
75313ababc | ||
|
|
570d44b502 | ||
|
|
71c335ac26 | ||
|
|
02f5d57871 | ||
|
|
6c16486b4a | ||
|
|
bfa5d6d23c | ||
|
|
c30b6d675b | ||
|
|
b59937b80f | ||
|
|
5b0f17130f | ||
|
|
2548965a1e | ||
|
|
09c01da205 | ||
|
|
9ed78dea24 | ||
|
|
e5888e7b93 | ||
|
|
13af842c66 | ||
|
|
a211223810 | ||
|
|
8bc3c826b1 | ||
|
|
7a3d6ec928 | ||
|
|
9b2e7892ca | ||
|
|
04794d5dfd | ||
|
|
36c2787fc6 | ||
|
|
2bafa0b50b | ||
|
|
c953de0036 | ||
|
|
aad7aa4d22 | ||
|
|
ebe23614f7 | ||
|
|
d8d6ab48c8 | ||
|
|
e1b68261b3 | ||
|
|
462dc57156 | ||
|
|
209b826e51 | ||
|
|
5b100cc1b3 | ||
|
|
4428e56ba2 | ||
|
|
9310da3d2f | ||
|
|
3ecc0b4ddc | ||
|
|
7e39eeaa4c | ||
|
|
9f03c38d24 | ||
|
|
ce41a70b2e | ||
|
|
4ab1d5a6f2 | ||
|
|
78ce6793e3 | ||
|
|
7e1d1650b3 | ||
|
|
bb0e8a8850 | ||
|
|
679f978e35 | ||
|
|
d4f8d419e4 | ||
|
|
c4468a95de | ||
|
|
5aa31f4a0b | ||
|
|
3cdcc78f22 | ||
|
|
b8b7e5af01 | ||
|
|
b65af04517 | ||
|
|
0e85ec4d5e | ||
|
|
786a3d2adf | ||
|
|
a6e7b8a4fd | ||
|
|
cddb455495 | ||
|
|
ecda40872f | ||
|
|
7d0b2a2417 | ||
|
|
c99eb7a7b4 | ||
|
|
dd17800b86 | ||
|
|
064c8ce56d | ||
|
|
43a4c850c4 | ||
|
|
34ce5a5c08 | ||
|
|
291d4ad7cc | ||
|
|
daf47ddbc8 | ||
|
|
b325677a97 | ||
|
|
bf7ace56c9 | ||
|
|
2490cc8e6a | ||
|
|
030e0c7b53 | ||
|
|
5cdb1ea744 | ||
|
|
145b7019ae | ||
|
|
e974743475 | ||
|
|
f778fbfd3b | ||
|
|
1e6d4a8ea7 | ||
|
|
f44adfbeb0 | ||
|
|
76a7aebc8e | ||
|
|
2d1fe10e3d | ||
|
|
c72162ab52 | ||
|
|
852efbaf60 | ||
|
|
d67240a39a | ||
|
|
08b59f510b | ||
|
|
a957faa3c5 | ||
|
|
a697f8fd83 | ||
|
|
b9eed39237 | ||
|
|
39c6eb64fe | ||
|
|
7bdffe148f | ||
|
|
10fd316439 | ||
|
|
d63a7a46a7 | ||
|
|
c8e700bec1 | ||
|
|
341a2d1da8 | ||
|
|
30ab4ad968 | ||
|
|
3038401b09 | ||
|
|
a5b45d6896 | ||
|
|
161f5f9a3b | ||
|
|
921738899b | ||
|
|
7e6bac3450 | ||
|
|
f41d758541 | ||
|
|
0229358f88 | ||
|
|
81e5a57d51 | ||
|
|
dc154f5db7 | ||
|
|
2d3a9f80bd | ||
|
|
0bede3b971 | ||
|
|
2bce89045e | ||
|
|
a9f27336ea | ||
|
|
01175743b7 | ||
|
|
8d2e15c3af | ||
|
|
5f6113ba90 | ||
|
|
7cd6ce4a32 | ||
|
|
6fae20977e | ||
|
|
ad4a11da74 | ||
|
|
e38cd472d9 | ||
|
|
6ac1b99e0c | ||
|
|
d9a64c43c2 | ||
|
|
9a75975769 | ||
|
|
8a7817ecdb | ||
|
|
5b08744a41 | ||
|
|
141915625d | ||
|
|
7f41fa903b | ||
|
|
91f9321fcb | ||
|
|
59fcce0b92 | ||
|
|
bd54f4381a | ||
|
|
a7b19052e8 | ||
|
|
9931dcf9c3 | ||
|
|
fe999119cd | ||
|
|
422614ca57 | ||
|
|
119b03b472 | ||
|
|
89216bbe06 | ||
|
|
1f702b359c | ||
|
|
0933ed55b8 | ||
|
|
4925c210c5 | ||
|
|
b3fb4bd7f7 | ||
|
|
944ddf79f1 | ||
|
|
1cd8c92d3a | ||
|
|
8d079862dc | ||
|
|
e425c8aa65 | ||
|
|
bd76632553 | ||
|
|
e466c357e0 | ||
|
|
8dcc5caf8f | ||
|
|
a27c8da434 | ||
|
|
9a41024e12 | ||
|
|
8b23bbca3a | ||
|
|
321a671589 | ||
|
|
b8a2bcb276 | ||
|
|
f10a6ea618 | ||
|
|
5b2a230bb9 | ||
|
|
ccca6b95f4 | ||
|
|
546814bc48 | ||
|
|
4a6c72b91a | ||
|
|
ae94e3de6e | ||
|
|
27c1e5cc19 | ||
|
|
0abf5b437a | ||
|
|
07937fcbfd | ||
|
|
37f84c936e | ||
|
|
1f5ad68302 | ||
|
|
44fae5cc7a | ||
|
|
c50a8308cf | ||
|
|
cf5d080656 | ||
|
|
8a644181c6 | ||
|
|
649339aa95 | ||
|
|
56538588eb | ||
|
|
b3ac0608aa | ||
|
|
e3242cdb7d | ||
|
|
5f6a1d44a1 | ||
|
|
e261b42413 | ||
|
|
0be3097b22 | ||
|
|
459319fb75 | ||
|
|
59832404af | ||
|
|
3d4f8f9d05 | ||
|
|
6d5e49c120 | ||
|
|
92c76c7e7a | ||
|
|
eedef76f40 | ||
|
|
075339a342 | ||
|
|
8faa40ebff | ||
|
|
b007282566 | ||
|
|
5cedd202e7 | ||
|
|
cef19c9365 | ||
|
|
714eeb4922 | ||
|
|
382d363254 | ||
|
|
3e39053c53 | ||
|
|
93449f1e79 | ||
|
|
391e4f6c56 | ||
|
|
b68ef52b94 | ||
|
|
3cf1c830dc | ||
|
|
551204f2fa | ||
|
|
0602b1f64f | ||
|
|
333ebbe13b | ||
|
|
666a0bca8d | ||
|
|
f3206c03bc | ||
|
|
08829b3709 | ||
|
|
cc57b6c64b | ||
|
|
352ca946cd | ||
|
|
92f71b5825 | ||
|
|
bcac90e3a6 | ||
|
|
d10d4ed86e | ||
|
|
6d58085d77 | ||
|
|
4e229a2004 | ||
|
|
afaf8892ef | ||
|
|
e436fa7c08 | ||
|
|
579c235a6f | ||
|
|
34d7494da0 | ||
|
|
eca7e27ad5 | ||
|
|
e809326d81 | ||
|
|
51d1c4de4d | ||
|
|
0a9fa19b50 | ||
|
|
4286138123 | ||
|
|
22bcddfa1b | ||
|
|
326d02fdc1 | ||
|
|
ae6666f0d6 | ||
|
|
930ecc789b | ||
|
|
3a595ffddf | ||
|
|
3f661b0166 | ||
|
|
5a7e6c0778 | ||
|
|
e4997797f3 | ||
|
|
9a8944f470 | ||
|
|
93fb75d793 | ||
|
|
3673849e36 | ||
|
|
966d2213a9 | ||
|
|
a6da9e59b7 | ||
|
|
8d28d77301 | ||
|
|
861ba87db4 | ||
|
|
7aaf02a28d | ||
|
|
cee233cff6 | ||
|
|
fa64e8c78c | ||
|
|
fb8b8c9c8b | ||
|
|
88944ca89e | ||
|
|
ecf36adfc2 | ||
|
|
94c2ffa3a4 | ||
|
|
eb228896f7 | ||
|
|
a3c5820d12 | ||
|
|
914e32a741 | ||
|
|
fb01dcffd5 | ||
|
|
018df4fbfd | ||
|
|
88338bb99b | ||
|
|
2fa6577eee | ||
|
|
874d022b2b | ||
|
|
b80ee75e2d | ||
|
|
12dbdbec86 | ||
|
|
d48b16cf54 | ||
|
|
ee007da29e | ||
|
|
f401c954e0 | ||
|
|
352673d78b | ||
|
|
a304aac97b | ||
|
|
4cb3c123f3 | ||
|
|
3c001a569d | ||
|
|
84f9128825 | ||
|
|
180bf41c25 | ||
|
|
da3c426478 | ||
|
|
0903ad17bc | ||
|
|
4043bd8de6 | ||
|
|
a7019ac90a | ||
|
|
7cac527c9e | ||
|
|
9cfd989f4b | ||
|
|
d269a7bfcd | ||
|
|
c8e054a7f2 | ||
|
|
b88cd49d8d | ||
|
|
e90ebaecff | ||
|
|
35a7750827 | ||
|
|
cd80616924 | ||
|
|
4c3967339c | ||
|
|
4f645c3289 | ||
|
|
af47fb24a8 | ||
|
|
243cbb5453 | ||
|
|
f036f6592c | ||
|
|
23b2a09079 | ||
|
|
33d484231b | ||
|
|
8d00745217 | ||
|
|
1a9a6bb806 | ||
|
|
ce92d83e3e | ||
|
|
3680e85355 | ||
|
|
fc039770b4 | ||
|
|
954f276a96 | ||
|
|
af9fea4aad | ||
|
|
0558e7c92a | ||
|
|
9e387ce198 | ||
|
|
cae8c328bd | ||
|
|
12953fe8da | ||
|
|
ea445b1300 | ||
|
|
28b5df2be2 | ||
|
|
0a81b27861 | ||
|
|
b819b9f093 | ||
|
|
cb2d914174 | ||
|
|
f93b09629c | ||
|
|
ff25707c73 | ||
|
|
34e0fcd923 | ||
|
|
6c88543e7f | ||
|
|
8c27da88c7 | ||
|
|
74cbc8f0ea | ||
|
|
7afbdae53c | ||
|
|
4f819651eb | ||
|
|
4982cb52ed | ||
|
|
6595283c35 | ||
|
|
8add228591 | ||
|
|
4dda125e78 | ||
|
|
1aeb445b40 | ||
|
|
a14fe3184a | ||
|
|
a190c58680 | ||
|
|
c1bc409cc7 | ||
|
|
819114b850 | ||
|
|
ddaa3f6ccb | ||
|
|
58c86e1fcf | ||
|
|
d4f7f5e5be | ||
|
|
2791c8ccf5 | ||
|
|
bf3f4b5d6d | ||
|
|
6e7e9a994a | ||
|
|
b1c5e250ba | ||
|
|
4b22ead1ca | ||
|
|
17c19eaf89 | ||
|
|
a6120c459c | ||
|
|
e80b06574c | ||
|
|
352dd7f78c | ||
|
|
0432af273c | ||
|
|
bf616f77e9 | ||
|
|
c59b341cf3 | ||
|
|
7432e7c2f7 | ||
|
|
2622fd6b0a | ||
|
|
f41d8104ac | ||
|
|
99d919e0c8 | ||
|
|
bd3a67fd6e | ||
|
|
a5550b8e83 | ||
|
|
c1026f77f7 | ||
|
|
bf4b16d823 | ||
|
|
e0b7efdcd8 | ||
|
|
e18d46f600 | ||
|
|
083974aacf | ||
|
|
cb3b243702 | ||
|
|
53cf664b00 | ||
|
|
3d0208d420 | ||
|
|
4e54c3c35e | ||
|
|
a5395fc374 | ||
|
|
c95506bb44 | ||
|
|
ae23447a6f | ||
|
|
d423deafdb | ||
|
|
b38942a482 | ||
|
|
ceaa0450ce | ||
|
|
4b855ceed5 | ||
|
|
7cfe2bae27 | ||
|
|
3eb370aeb6 | ||
|
|
4f450ab1b1 | ||
|
|
d3aebdb6a5 | ||
|
|
2b9c5266bd | ||
|
|
673ee3e027 | ||
|
|
1d34fb44df | ||
|
|
1cea9fa90c | ||
|
|
5e2087cbb8 | ||
|
|
71b5044987 | ||
|
|
b92c925bb0 | ||
|
|
b194e8b24e | ||
|
|
322ce6d8c2 | ||
|
|
a87d39ed05 | ||
|
|
bacda57b33 | ||
|
|
a444533db1 | ||
|
|
033fad32d5 | ||
|
|
1eb080a1a8 | ||
|
|
be309f1888 | ||
|
|
0136b907c5 | ||
|
|
92f048f7cf | ||
|
|
271e9737a8 | ||
|
|
c6b0db6a4a | ||
|
|
ef624e5613 | ||
|
|
9de51f88ca | ||
|
|
0eff63b349 | ||
|
|
04c431c699 | ||
|
|
47ef3d0abc | ||
|
|
acf2c4e9f2 | ||
|
|
e08a2b2642 | ||
|
|
50cab0b962 | ||
|
|
cccc35de4d | ||
|
|
42e686ee03 | ||
|
|
653bf090a1 | ||
|
|
ca7d95f005 | ||
|
|
3078f1a87b | ||
|
|
a266f78de4 | ||
|
|
5af83fa1b9 | ||
|
|
dc030376d6 | ||
|
|
c9991ecb5c | ||
|
|
4e138aeed4 | ||
|
|
3d5b3417a2 | ||
|
|
774ead32fb | ||
|
|
89ae8631d4 | ||
|
|
e883e32f41 | ||
|
|
ea9b343219 | ||
|
|
bcbdc87e2d | ||
|
|
17918dd5c5 | ||
|
|
dd1e96ea68 | ||
|
|
25c61e39b4 | ||
|
|
2b3227f131 | ||
|
|
4247f8e2f1 | ||
|
|
c6a821791d | ||
|
|
77a8f397a0 | ||
|
|
69ed1d4213 | ||
|
|
533a2bd091 | ||
|
|
678cceb963 | ||
|
|
38e369766d | ||
|
|
3bd3f88d57 | ||
|
|
0c89a94c65 | ||
|
|
dee400cc0f | ||
|
|
eca0389878 | ||
|
|
2859ec9027 | ||
|
|
0ccf798fc2 | ||
|
|
5b3f27c929 | ||
|
|
54d2b58f3e | ||
|
|
a4e41d059c | ||
|
|
22b8238c50 | ||
|
|
d9f0488fc1 | ||
|
|
a128b0ac07 | ||
|
|
346f7240fe | ||
|
|
3fd6cf5e91 | ||
|
|
051d3b40eb | ||
|
|
fee525f65e | ||
|
|
ae67b56bff | ||
|
|
7a1ae92b42 | ||
|
|
5820888bc0 | ||
|
|
3c0794b62e | ||
|
|
1fc6652ed6 | ||
|
|
f0e58a2e87 | ||
|
|
4da4ccdb8c | ||
|
|
2afc16b623 | ||
|
|
4d5f903cc3 | ||
|
|
f865289130 | ||
|
|
e333434019 | ||
|
|
2d0a481557 | ||
|
|
8594944c2e | ||
|
|
5bd907d126 | ||
|
|
2b6b1d51e6 | ||
|
|
3beaf09dfe | ||
|
|
b60c8b82be | ||
|
|
2f943d84cd | ||
|
|
4f9c6e6c01 | ||
|
|
e81f8c065c | ||
|
|
13179d2958 | ||
|
|
054814fd3e | ||
|
|
106f1497ec | ||
|
|
736a1ac906 | ||
|
|
f1eca78ca4 | ||
|
|
985bfe53b4 | ||
|
|
861cf504f0 | ||
|
|
6fceb8caaa | ||
|
|
5a217ee941 | ||
|
|
5cf587f9eb | ||
|
|
9d77f5d8f6 | ||
|
|
209e6cb5a6 | ||
|
|
a3a0e25cf7 | ||
|
|
8d48a4399f | ||
|
|
69cd698535 | ||
|
|
0ce62a2add | ||
|
|
7af2e48156 | ||
|
|
bf6022f225 | ||
|
|
17e86fd7eb | ||
|
|
2d9681cd6b | ||
|
|
b8c68e6df3 | ||
|
|
f5ff567ce5 | ||
|
|
4bdc4e4050 | ||
|
|
0da99435c3 | ||
|
|
2142465fdc | ||
|
|
ac2ee78e91 | ||
|
|
57a15ec541 | ||
|
|
d605600d3f | ||
|
|
8dc218edc3 | ||
|
|
f451f9f63d | ||
|
|
8b1dfe2d50 | ||
|
|
0f1d094bd9 | ||
|
|
786740a790 | ||
|
|
b711d08463 | ||
|
|
0a48866077 | ||
|
|
c5a3943e71 | ||
|
|
d54c3183d2 | ||
|
|
a6e9de56db | ||
|
|
1f2140994c | ||
|
|
085cb22352 | ||
|
|
d7d49e1f58 | ||
|
|
35b29c1644 | ||
|
|
0a058028c2 | ||
|
|
e8acfa6802 | ||
|
|
61a12f4a53 | ||
|
|
a5468c2faa | ||
|
|
427c2aa3ec | ||
|
|
01fd5a53f3 | ||
|
|
3e673ac134 | ||
|
|
b2c698d295 | ||
|
|
5648b4e438 | ||
|
|
aa24627547 | ||
|
|
144e0427c7 | ||
|
|
00e488d282 | ||
|
|
ea4cedb796 | ||
|
|
5c96513dea | ||
|
|
7ad213b289 | ||
|
|
6377b324ee | ||
|
|
a356e60d45 | ||
|
|
9a87eb5f98 | ||
|
|
091886d176 | ||
|
|
52ff4ce544 | ||
|
|
92b41acc88 | ||
|
|
3d5d42afd4 | ||
|
|
74ca2ac5dc | ||
|
|
145412e6c9 | ||
|
|
1d7545a180 | ||
|
|
fe11f867b5 | ||
|
|
ccc3691cb0 | ||
|
|
a14e2eb316 | ||
|
|
c6c788f1e1 | ||
|
|
ca40d1594c | ||
|
|
ae6ae8ccb9 | ||
|
|
8aeeb86dfe | ||
|
|
ffa3c8bc4b | ||
|
|
c1e81f171f | ||
|
|
316b969feb | ||
|
|
05363f77f8 | ||
|
|
963c3eb61d | ||
|
|
759a2ba133 | ||
|
|
e3dacf07b3 | ||
|
|
a0f451bc0e | ||
|
|
61b36e34fc | ||
|
|
53855cec8e | ||
|
|
3d7b40b001 | ||
|
|
1ba9859e9e | ||
|
|
b5f6e8e4b2 | ||
|
|
a15b07a283 | ||
|
|
0e15f809bb | ||
|
|
1995202862 | ||
|
|
6c066f13fd | ||
|
|
1c7ee571c2 | ||
|
|
dd531317c4 | ||
|
|
bfaee8e7b5 | ||
|
|
ce9d7488c8 | ||
|
|
69630a36d4 | ||
|
|
f045475934 | ||
|
|
ad4a363232 | ||
|
|
7123901baa | ||
|
|
dc7a35c3a0 | ||
|
|
579a3e197b | ||
|
|
d19701dc91 | ||
|
|
a81259aba8 | ||
|
|
dfe34380ca | ||
|
|
ec50dcead3 | ||
|
|
784e076a61 | ||
|
|
5b349504f4 | ||
|
|
0c1df34645 | ||
|
|
d85ed42765 | ||
|
|
93cf82f141 | ||
|
|
e15b957a0d | ||
|
|
450261295f |
+18
-12
@@ -5,12 +5,15 @@ env:
|
||||
global:
|
||||
- ELIXIR_ASSERT_TIMEOUT=2000
|
||||
matrix:
|
||||
- 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=OTP-22.0 CHECK_REPRODUCIBLE=true CHECK_POSIX_COMPLIANT=true
|
||||
- OTP_RELEASE=OTP-21.3.8
|
||||
- OTP_RELEASE=OTP-21.2
|
||||
- OTP_RELEASE=OTP-21.1
|
||||
- OTP_RELEASE=OTP-21.0
|
||||
- OTP_RELEASE=OTP-20.3
|
||||
- OTP_RELEASE=OTP-20.2
|
||||
- OTP_RELEASE=OTP-20.1
|
||||
- OTP_RELEASE=OTP-20.0
|
||||
- OTP_RELEASE=maint
|
||||
- OTP_RELEASE=master
|
||||
|
||||
@@ -28,14 +31,17 @@ install:
|
||||
- PATH=$(pwd)/otp/bin:$PATH
|
||||
|
||||
script:
|
||||
- make compile
|
||||
- rm -rf .git
|
||||
- ELIXIRC_OPTS="--warnings-as-errors" ERLC_OPTS="+warning_as_errors" make compile
|
||||
- make test
|
||||
- dialyzer -pa lib/elixir/ebin --build_plt --output_plt elixir.plt --apps lib/elixir/ebin/elixir.beam lib/elixir/ebin/Elixir.Kernel.beam
|
||||
|
||||
notifications:
|
||||
recipients:
|
||||
- jose.valim@gmail.com
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
- lexmag@me.com
|
||||
- an.leopardi@gmail.com
|
||||
# Check for reproducible builds only in the latest OTP release
|
||||
- if [ -n "$CHECK_REPRODUCIBLE" ]; then make check_reproducible; fi
|
||||
|
||||
# Check for POSIX compliant shell scripts
|
||||
- if [ -n "$CHECK_POSIX_COMPLIANT" ]; then
|
||||
shellcheck -e SC2039,2086 bin/elixir && echo "bin/elixir is POSIX compliant";
|
||||
shellcheck bin/elixirc && echo "bin/elixirc is POSIX compliant";
|
||||
shellcheck bin/iex && echo "bin/iex is POSIX compliant";
|
||||
fi
|
||||
|
||||
+179
-126
@@ -1,179 +1,232 @@
|
||||
# Changelog for Elixir v1.8
|
||||
# Changelog for Elixir v1.9
|
||||
|
||||
Elixir v1.8 comes with many improvements at the infrastructure level, improving compilation time, speeding up common patterns, and adding features around introspection of the system.
|
||||
## Releases
|
||||
|
||||
## Custom struct inspections
|
||||
The main feature in Elixir v1.9 is the addition of releases. A release is a self-contained directory that consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine (VM) and runtime. Once a release is assembled, it can be packaged and deployed to a target as long as the target runs on the same operating system (OS) distribution and version as the machine running the `mix release` command.
|
||||
|
||||
Elixir now provides a derivable implementation of the `Inspect` protocol. In a nutshell, this means it is really easy to filter data from your data structures whenever they are inspected. For example, imagine you have a user struct with security and privacy sensitive information:
|
||||
You can start a new project and assemble a release for it in three easy steps:
|
||||
|
||||
```elixir
|
||||
defmodule User do
|
||||
defstruct [:id, :name, :age, :email, :encrypted_password]
|
||||
end
|
||||
```
|
||||
$ mix new my_app
|
||||
$ cd my_app
|
||||
$ MIX_ENV=prod mix release
|
||||
|
||||
By default, if you inspect a user via `inspect(user)`, it will include all fields. This can cause fields such as `:email` and `:encrypted_password` to appear in logs, error reports, etc. You could always define a custom implementation of the `Inspect` protocol for such cases but Elixir v1.8 makes it simpler by allowing you to derive the `Inspect` protocol:
|
||||
A release will be assembled in `_build/prod/rel/my_app`. Inside the release, there will be a `bin/my_app` file which is the entry point to your system. It supports multiple commands, such as:
|
||||
|
||||
```elixir
|
||||
defmodule User do
|
||||
@derive {Inspect, only: [:id, :name, :age]}
|
||||
defstruct [:id, :name, :age, :email, :encrypted_password]
|
||||
end
|
||||
```
|
||||
* `bin/my_app start`, `bin/my_app start_iex`, `bin/my_app restart`, and `bin/my_app stop` - for general management of the release
|
||||
|
||||
Now all user structs will be printed with all remaining fields collapsed:
|
||||
* `bin/my_app rpc COMMAND` and `bin/my_app remote` - for running commands on the running system or to connect to the running system
|
||||
|
||||
#User<id: 1, name: "Jane", age: 33, ...>
|
||||
* `bin/my_app eval COMMAND` - to start a fresh system that runs a single command and then shuts down
|
||||
|
||||
You can also pass `@derive {Inspect, except: [...]}` in case you want to keep all fields by default and exclude only some.
|
||||
* `bin/my_app daemon` and `bin/my_app daemon_iex` - to start the system as a daemon on Unix-like systems
|
||||
|
||||
## Time zone database support
|
||||
* `bin/my_app install` - to install the system as a service on Windows machines
|
||||
|
||||
In Elixir v1.3, Elixir added four types, known as Calendar types, to work with dates and times: `Time`, `Date`, `NaiveDateTime` (without time zone) and `DateTime` (with time zone). Over the last releases we have added many enhancements to the Calendar types but the `DateTime` module always evolved at a slower pace since Elixir did not provide support for a time zone database.
|
||||
### Why releases?
|
||||
|
||||
Elixir v1.8 now defines a `Calendar.TimeZoneDatabase` behaviour, allowing developers to bring in their own time zone databases. By defining an explicit contract for time zone behaviours, Elixir can now extend the `DateTime` API, adding functions such as `DateTime.shift_zone/3`. By default, Elixir ships with a time zone database called `Calendar.UTCOnlyTimeZoneDatabase` that only handles UTC.
|
||||
Releases allow developers to precompile and package all of their code and the runtime into a single unit. The benefits of releases are:
|
||||
|
||||
Other Calendar related improvements include the addition of `Date.day_of_year/1`, `Date.quarter_of_year/1`, `Date.year_of_era/1`, and `Date.day_of_era/1`.
|
||||
* Code preloading. The VM has two mechanisms for loading code: interactive and embedded. By default, it runs in the interactive mode which dynamically loads modules when they are used for the first time. The first time your application calls `Enum.map/2`, the VM will find the `Enum` module and load it. There’s a downside. When you start a new server in production, it may need to load many other modules, causing the first requests to have an unusual spike in response time. Releases run in embedded mode, which loads all available modules upfront, guaranteeing your system is ready to handle requests after booting.
|
||||
|
||||
## Faster compilation and other performance improvements
|
||||
* Configuration and customization. Releases give developers fine grained control over system configuration and the VM flags used to start the system.
|
||||
|
||||
Due to improvements to the compiler made over the last year, Elixir v1.8 should compile code about 5% faster on average. This is yet another release where we have been able to reduce compilation times and provide a more joyful development experience to everyone.
|
||||
* Self-contained. A release does not require the source code to be included in your production artifacts. All of the code is precompiled and packaged. Releases do not even require Erlang or Elixir in your servers, as they include the Erlang VM and its runtime by default. Furthermore, both Erlang and Elixir standard libraries are stripped to bring only the parts you are actually using.
|
||||
|
||||
The compiler also emits more efficient code for range checks in guards (such as `x in y..z`), for charlists with interpolation (such as `'foo #{bar} baz'`), and when working with records via the `Record` module.
|
||||
* Multiple releases. You can assemble different releases with different configuration per application or even with different applications altogether.
|
||||
|
||||
Finally, EEx templates got their own share of optimizations, emitting more compact code that runs faster.
|
||||
### Hooks and Configuration
|
||||
|
||||
## Improved instrumentation and ownership with `$callers`
|
||||
Releases also provide built-in hooks for configuring almost every need of the production system:
|
||||
|
||||
The `Task` module is one of the most common ways to spawn light-weight processes to perform work concurrently. Whenever you spawn a new process, Elixir annotates the parent of that process through the `$ancestors` key. This information can be used by instrumentation tools to track the relationship between events occurring within multiple processes. However, many times, tracking only the `$ancestors` is not enough.
|
||||
* `config/config.exs` (and `config/prod.exs`) - provides build-time application configuration, which is executed when the release is assembled
|
||||
|
||||
For example, we recommend developers to always start tasks under a supervisor. This provides more visibility and allows us to control how those tasks are terminated when a node shuts down. In your code, this can be done by invoking something like: `Task.Supervisor.start_child(MySupervisor, task_specification)`. This means that, although your code is the one who invokes the task, the actual parent of the task would be the supervisor, as the supervisor is the one spawning it. We would list the supervisor as one of the `$ancestors` for the task, but the relationship between your code and the task is lost.
|
||||
* `config/releases.exs` - provides runtime application configuration. It is executed every time the release boots and is further extensible via config providers
|
||||
|
||||
In Elixir v1.8, we now track the relationship between your code and the task via the `$callers` key in the process dictionary, which aligns well with the existing `$ancestors` key. Therefore, assuming the `Task.Supervisor` call above, we have:
|
||||
* `rel/vm.args.eex` - a template file that is copied into every release and provides static configuration of the Erlang Virtual Machine and other runtime flags
|
||||
|
||||
[your code] -- calls --> [supervisor] ---- spawns --> [task]
|
||||
* `rel/env.sh.eex` and `rel/env.bat.eex` - template files that are copied into every release and executed on every command to set up environment variables, including ones specific to the VM, and the general environment
|
||||
|
||||
which means we store the following relationships:
|
||||
We have written extensive documentation on releases, so we recommend checking it out for more information.
|
||||
|
||||
[your code] [supervisor] <-- ancestor -- [task]
|
||||
^ |
|
||||
|--------------------- caller ---------------------|
|
||||
## Configuration overhaul
|
||||
|
||||
When a task is spawned directly from your code, without a supervisor, then the process running your code will be listed under both `$ancestors` and `$callers`.
|
||||
A new `Config` module has been added to Elixir. The previous configuration API, `Mix.Config`, was part of the Mix build tool. But since releases provide runtime configuration and Mix is not included in releases, we ported the `Mix.Config` API to Elixir. In other words, `use Mix.Config` has been soft-deprecated in favor of `import Config`.
|
||||
|
||||
This small feature is very powerful. It allows instrumentation and monitoring tools to better track and relate the events happening in your system. This feature can also be used by tools like the "Ecto Sandbox". The "Ecto Sandbox" allows developers to run tests concurrently against the database, by using transactions and an ownership mechanism where each process explicitly gets a connection assigned to it. Without `$callers`, every time you spawned a task that queries the database, the task would not know its caller, and therefore it would be unable to know which connection was assigned to it. This often meant features that relies on tasks could not be tested concurrently. With `$callers`, figuring out this relationship is trivial and you have more tests using the full power of your machine.
|
||||
Another important change related to configuration is that `mix new` will no longer generate a `config/config.exs` file. [Relying on configuration is undesired for most libraries](https://hexdocs.pm/elixir/library-guidelines.html#avoid-application-configuration) and the generated config files pushed library authors in the wrong direction. Furthermore, `mix new --umbrella` will no longer generate a configuration for each child app, instead all configuration should be declared in the umbrella root. That's how it has always behaved, we are now making it explicit.
|
||||
|
||||
## v1.8.0 (2018-01-14)
|
||||
## Other enhancements
|
||||
|
||||
There are many other enhancements. The Elixir CLI got a handful of new options in order to best support releases. `Logger` now computes its sync/async/discard thresholds in a decentralized fashion, reducing contention. `EEx` templates support more complex expressions than before. Finally, there is a new `~U` sigil for working with UTC DateTimes as well as new functions in the `File`, `Registry`, and `System` modules.
|
||||
|
||||
## v1.9.2 (2019-10-12)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Optimize the default template engine to compile and execute more efficiently
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Add `Calendar.TimeZoneDatabase` and a `Calendar.UTCOnlyTimeZoneDatabase` implementation
|
||||
* [Calendar] Add callbacks `day_of_year/3`, `quarter_of_year/3`, `year_of_era/1`, and `day_of_era/3`
|
||||
* [Code.Formatter] Preserve user's choice of new line after most operators
|
||||
* [Date] Add `Date.day_of_year/1`, `Date.quarter_of_year/1`, `Date.year_of_era/1`, and `Date.day_of_era/1`
|
||||
* [DateTime] Add `DateTime.from_naive/3`, `DateTime.now/1`, and `DateTime.shift_zone/3`
|
||||
* [File] Allow `:raw` option in `File.exists?/2`, `File.regular?/2`, and `File.dir?/2`
|
||||
* [File] Allow POSIX time as an integer in `File.touch/2` and `File.touch!/2`
|
||||
* [Inspect] Allow `Inspect` protocol to be derivable with the `:only`/`:except` options
|
||||
* [Kernel] Do not propagate counters to variables in quote inside another quote
|
||||
* [Kernel] Warn on ambiguous use of `::` and `|` in typespecs
|
||||
* [Kernel] Add `:delegate_to` `@doc` metadata tag when using `defdelegate`
|
||||
* [Kernel] Improve compile-time building of ranges via the `..` operator
|
||||
* [Kernel] Compile charlist interpolation more efficiently
|
||||
* [Kernel] Add `floor/1` and `ceil/1` guards
|
||||
* [Kernel.SpecialForms] Add `:reduce` option to `for` comprehensions
|
||||
* [List] Add `List.myers_difference/3` and `List.improper?/1`
|
||||
* [Macro] Add `Macro.struct!/2` for proper struct resolution during compile time
|
||||
* [Map] Optimize and merge nested maps `put` and `merge` operations
|
||||
* [Range] Add `Range.disjoint?/2`
|
||||
* [Record] Reduce memory allocation when updating multiple fields in a record
|
||||
* [Registry] Allow associating a value on `:via` tuple
|
||||
* [String] Add `String.bag_distance/2`
|
||||
* [Task] Add `$callers` tracking to `Task` - this makes it easier to find which process spawned a task and use it for tracking ownership and monitoring
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Add `ExUnit.after_suite/1` callback
|
||||
* [ExUnit.Assertions] Show last N messages (instead of first N) from mailbox on `assert_receive` fail
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Add `port/1` and `port/2`
|
||||
* [IEx.Server] Expose `IEx.Server.run/1` for custom IEx sessions with the ability to broker pry sessions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Add `Mix.target/0` and `Mix.target/1` to control dependency management per target
|
||||
* [Mix.Project] Add `:depth` and `:parents` options to `deps_paths/1`
|
||||
* [mix archive.install] Add a timeout when installing archives
|
||||
* [mix compile] Include optional dependencies in `:extra_applications`
|
||||
* [mix escript.install] Add a timeout when installing escripts
|
||||
* [mix format] Warn when the same file may be formatted by multiple `.formatter.exs`
|
||||
* [mix test] Allow setting the maximum number of failures via `--max-failures`
|
||||
* [mix test] Print a message instead of raising on unmatched tests inside umbrella projects
|
||||
* [mix release] Allow `{:from_app, app_name}` as a version for releases
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Allow printing dates with more than 9999 years
|
||||
* [Exception] Exclude deprecated functions in "did you mean?" hints
|
||||
* [Float] Handle subnormal floats in `Float.ratio/1`
|
||||
* [Kernel] Remove `Guard test tuple_size(...) can never succeed` Dialyzer warning on `try`
|
||||
* [Kernel] Expand operands in `size*unit` bitstring modifier instead of expecting `size` and `unit` to be literal integers
|
||||
* [Kernel] Do not deadlock on circular struct dependencies in typespecs
|
||||
* [Kernel] Raise proper error message when passing flags to the Erlang compiler that Elixir cannot handle
|
||||
* [Kernel] Do not leak variables in `cond` clauses with a single matching at compile-time clause
|
||||
* [NaiveDateTime] Do not accept leap seconds in builder and parsing functions
|
||||
* [String] Fix ZWJ handling in Unicode grapheme clusters
|
||||
* [StringIO] Handle non-printable args in StringIO gracefully
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Use typespec info (instead of docs chunk) and properly format callbacks in `b/1`
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Allow Logger backends to be dynamically removed when an application is shutting down
|
||||
* [Kernel] Ensure compilation works for a variable named `super`
|
||||
* [Kernel] Ensure capture operator of a local function expands correctly inside a macro
|
||||
* [Regex] Ensure dynamic recompilation of regexes considers options. This fixes an issue where parsing the protocol in `URI.parse/1` seemingly looked case sensitive when running Elixir precompiled on another machine
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure changes in deps propagate to all umbrella children - this fix a long standing issue where updating a dependency would not recompile all projects accordingly, requiring a complete removal of `_build`
|
||||
* [mix compile] Avoid time drift when checking and updating compiler manifest files
|
||||
* [mix compile.app] Respect the `:only` option between umbrella siblings
|
||||
* [mix compile.protocols] Reconsolidate protocols if local dependencies are stale
|
||||
* [mix deps] Properly mark dependencies with different `:system_env` as diverged
|
||||
* [mix new] Use `--module` value when setting up filenames
|
||||
* [mix release] Use `Base.encode32` when generating cookie to avoid unsafe chars
|
||||
* [mix release] Fix `install` command on Windows
|
||||
* [mix release] Quote executable path on Windows to ensure it works on directories with spaces
|
||||
|
||||
## v1.9.1 (2019-07-18)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Print relative paths in `--check-formatted` output
|
||||
* [mix release] Support included applications
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Fix formatter wrongly removing nested parens in nested calls
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Do not crash translator on poorly formatted supervisor names
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Raise readable error for mismatched sources during compilation
|
||||
* [mix release] Preserve UTF8 encoding in release config files
|
||||
|
||||
## v1.9.0 (2019-06-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Allow more complex mixed expressions when tokenizing
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Access] Allow `Access.at/1` to handle negative index
|
||||
* [CLI] Add support for `--boot`, `--boot-var`, `--erl-config`, `--pipe-to`, `--rpc-eval`, and `--vm-args` options
|
||||
* [Code] Add `static_atom_encoder` option to `Code.string_to_quoted/2`
|
||||
* [Code] Support `:force_do_end_blocks` on `Code.format_string!/2` and `Code.format_file!/2`
|
||||
* [Code] Do not raise on deadlocks on `Code.ensure_compiled/1`
|
||||
* [Config] Add `Config`, `Config.Reader`, and `Config.Provider` modules for working with configuration
|
||||
* [File] Add `File.rename!/2`
|
||||
* [Inspect] Add `:inspect_fun` and `:custom_options` to `Inspect.Opts`
|
||||
* [Kernel] Add `~U` sigil for UTC date times
|
||||
* [Kernel] Optimize `&super/arity` and `&super(&1)`
|
||||
* [Kernel] Optimize generated code for `with` with a catch-all clause
|
||||
* [Kernel] Validate `__struct__` key in map returned by `__struct__/0,1`
|
||||
* [Module] Add `Module.get_attribute/3`
|
||||
* [Protocol] Improve `Protocol.UndefinedError` messages to also include the type that was attempted to dispatch on
|
||||
* [Protocol] Optimize performance of dynamic dispatching for non-consolidated protocols
|
||||
* [Record] Include field names in generated type for records
|
||||
* [Regex] Automatically recompile regexes
|
||||
* [Registry] Add `Registry.select/2`
|
||||
* [System] Add `System.restart/0`, `System.pid/0` and `System.no_halt/1`
|
||||
* [System] Add `System.get_env/2`, `System.fetch_env/1`, and `System.fetch_env!/1`
|
||||
* [System] Support `SOURCE_DATE_EPOCH` for reproducible builds
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Allow multiple `:exclude` on configuration/CLI
|
||||
* [ExUnit.DocTest] No longer wrap doctest errors in custom exceptions. They ended-up hiding more information than showing
|
||||
* [ExUnit.DocTest] Display the actual doctest code when doctest fails
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.CLI] Copy ticktime from remote node on IEx `--remsh`
|
||||
* [IEx.CLI] Automatically add a host on node given to `--remsh`
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Use a decentralized mode computation for Logger which allows overloads to be detected more quickly
|
||||
* [Logger] Use `persistent_term` to store configuration whenever available for performance
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Follow XDG base dir specification in Mix for temporary and configuration files
|
||||
* [Mix.Generator] Add `copy_file/3`, `copy_template/4`, and `overwite?/2`
|
||||
* [Mix.Project] Add `preferred_cli_target` that works like `preferred_cli_env`
|
||||
* [mix archive.uninstall] Allow `mix archive.uninstall APP` to uninstall any installed version of APP
|
||||
* [mix new] No longer generate a `config/` directory for mix new
|
||||
* [mix release] Add support for releases
|
||||
* [mix release.init] Add templates for release configuration
|
||||
* [mix test] Allow running tests for a given umbrella app from the umbrella root with `mix test apps/APP/test`. Test failures also include the `apps/APP` prefix in the test location
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Consistently trim newlines when you have a single EEx expression per line on multiple lines
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Quote `::` in `Code.format_string!/1` to avoid ambiguity
|
||||
* [Code] Do not crash formatter on false positive sigils
|
||||
* [Enum] Ensure the first equal entry is returned by `Enum.min/2` and `Enum.max/2`
|
||||
* [Kernel] Improve error message when string interpolation is used in a guard
|
||||
* [Kernel] Properly merge and handle docs for callbacks with multiple clauses
|
||||
* [Kernel] Guarantee reproducible builds on modules with dozens of specs
|
||||
* [Kernel] Resolve `__MODULE__` accordingly in nested `defmodule` to avoid double nesting
|
||||
* [Kernel] Type variables starting with an underscore (`_foo`) should not raise compile error
|
||||
* [Kernel] Keep order of elements when macro `in/2` is expanded with a literal list on the right-hand side
|
||||
* [Kernel] Print proper location on undefined function error from dynamically generated functions
|
||||
* [Kernel] **Potentially breaking** Do not leak aliases when nesting module definitions that are fully namespaced modules. If you defined `defmodule Elixir.Foo.Bar` inside `defmodule Foo`, previous Elixir versions would automatically define an alias, but fully namespaced modules such as `Elixir.Foo.Bar` should never define or require an alias. If you were accidentally relying on this broken behaviour, your code may no longer work
|
||||
* [System] Make sure `:init.get_status/0` is set to `{:started, :started}` once the system starts
|
||||
* [Path] Do not expand `~` in `Path.expand/2` when not followed by a path separator
|
||||
* [Protocol] Ensure `debug_info` is kept in protocols
|
||||
* [Regex] Ensure inspect returns valid `~r//` expressions when they are manually compiled with backslashes
|
||||
* [Registry] Fix ETS leak in `Registry.register/2` for already registered calls in unique registries while the process is still alive
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Raise error if attempting to run single line tests on multiple files
|
||||
* [ExUnit] Return proper error on duplicate child IDs on `start_supervised`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Automatically shut down IEx if we receive EOF
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Don't discard Logger messages from other nodes as to leave a trail on both systems
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure Erlang-based Mix compilers (erlang, leex, yecc) set valid position on diagnostics
|
||||
* [mix compile] Ensure compilation halts in an umbrella project if one of the siblings fail to compile
|
||||
* [mix deps] Raise an error if the umbrella app's dir name and `mix.exs` app name don't match
|
||||
* [mix deps.compile] Fix subcommand splitting bug in rebar3
|
||||
* [mix test] Do not consider modules that are no longer cover compiled when computing coverage report, which could lead to flawed reports
|
||||
|
||||
### 3. Soft-deprecations (no warnings emitted)
|
||||
|
||||
None.
|
||||
#### Mix
|
||||
|
||||
* [Mix.Config] `Mix.Config` has been deprecated in favor of the `Config` module that now ships as part of Elixir itself. Reading configuration files should now be done by the `Config.Reader` module
|
||||
|
||||
### 4. Hard-deprecations
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] Passing a non-empty list to `Enum.into/2` was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
|
||||
* [Inspect.Algebra] `surround/3` is deprecated in favor of `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2`
|
||||
* [Inspect.Algebra] `surround_many/6` is deprecated in favor of `container_doc/6`
|
||||
* [Kernel] Using `@since` will now emit a unused attribute warning. Use `@doc since: "1.7.2"` instead
|
||||
* [Kernel] Passing a non-empty list as `:into` in `for` comprehensions was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
|
||||
* [Kernel.ParallelCompiler] `files/2` is deprecated in favor of `compile/2`
|
||||
* [Kernel.ParallelCompiler] `files_to_path/2` is deprecated in favor of `compile_to_path/2`
|
||||
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
|
||||
* [System] `:seconds`, `:milliseconds`, etc. as time units is deprecated in favor of `:second`, `:millisecond`, etc.
|
||||
* [System] `System.cwd/0` and `System.cwd!/0` are deprecated in favor of `File.cwd/0` and `File.cwd!/0`
|
||||
* [CLI] Deprecate `--detached` option, use `--erl "-detached"` instead
|
||||
* [Map] Deprecate Enumerable keys in `Map.drop/2`, `Map.split/2`, and `Map.take/2`
|
||||
* [String] The `:insert_replaced` option in `String.replace/4` has been deprecated. Instead you may pass a function as a replacement or use `:binary.replace/4` if you need to support earlier Elixir versions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.erlang] Returning `{:ok, contents}` or `:error` as the callback in `Mix.Compilers.Erlang.compile/6` is deprecated in favor of returning `{:ok, contents, warnings}` or `{:error, errors, warnings}`
|
||||
* [Mix.Project] Deprecate `Mix.Project.load_paths/1` in favor of `Mix.Project.compile_path/1`
|
||||
|
||||
## v1.7
|
||||
## v1.8
|
||||
|
||||
The CHANGELOG for v1.7 releases can be found [in the v1.7 branch](https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md).
|
||||
The CHANGELOG for v1.8 releases can be found [in the v1.8 branch](https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md).
|
||||
|
||||
+7
-3
@@ -39,11 +39,11 @@ If you participate in or contribute to the Elixir ecosystem in any way, you are
|
||||
|
||||
Explicit enforcement of the Code of Conduct applies to the official mediums operated by the Elixir project:
|
||||
|
||||
* The official GitHub projects and code reviews.
|
||||
* The [official GitHub projects][1] and code reviews.
|
||||
* The official elixir-lang mailing lists.
|
||||
* The #elixir-lang IRC channel on Freenode.
|
||||
* The **[#elixir-lang][2]** IRC channel on [Freenode][3].
|
||||
|
||||
Other Elixir activities (such as conferences, meetups, and other unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information.
|
||||
Other Elixir activities (such as conferences, meetups, and unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information.
|
||||
|
||||
Project maintainers may remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct.
|
||||
|
||||
@@ -54,3 +54,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
|
||||
## Acknowledgements
|
||||
|
||||
This document was based on the Code of Conduct from the Go project with parts derived from Django's Code of Conduct, Rust's Code of Conduct and the Contributor Covenant.
|
||||
|
||||
[1]: https://github.com/elixir-lang/
|
||||
[2]: https://webchat.freenode.net/?channels=#elixir-lang
|
||||
[3]: https://www.freenode.net
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
### Precheck
|
||||
|
||||
* Do not use the issues tracker for help or support (try Elixir Forum, Stack Overflow, IRC, etc.)
|
||||
* Do not use the issue tracker for help or support (try Elixir Forum, Stack Overflow, IRC, etc.)
|
||||
* For proposing a new feature, please start a discussion on the Elixir Core mailing list: https://groups.google.com/group/elixir-lang-core
|
||||
* For bugs, do a quick search and make sure the bug has not yet been reported
|
||||
* Please disclose security vulnerabilities privately at elixir-security@googlegroups.com
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
CANONICAL := v1.8/ # master/ or vMAJOR.MINOR/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict --warnings-as-errors
|
||||
ERLC := erlc -I lib/elixir/include +warnings_as_errors
|
||||
CANONICAL := v1.9/ # master/ or vMAJOR.MINOR/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include $(ERLC_OPTS)
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
GENERATE_APP := $(CURDIR)/lib/elixir/generate_app.escript
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -16,8 +16,10 @@ INSTALL_DATA = $(INSTALL) -m644
|
||||
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) )
|
||||
SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible
|
||||
SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH
|
||||
|
||||
.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
|
||||
.PHONY: install compile erlang elixir unicode app build_plt clean_plt dialyze test check_reproducible clean clean_residual_files install_man clean_man docs Docs.zip Precompiled.zip zips
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Functions
|
||||
@@ -46,6 +48,18 @@ test_$(1): compile $(1)
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
endef
|
||||
|
||||
define WRITE_SOURCE_DATE_EPOCH
|
||||
$(shell mkdir -p $(SOURCE_DATE_EPOCH_PATH) && bin/elixir -e \
|
||||
'IO.puts System.build_info()[:date] \
|
||||
|> DateTime.from_iso8601() \
|
||||
|> elem(1) \
|
||||
|> DateTime.to_unix()' > $(SOURCE_DATE_EPOCH_FILE))
|
||||
endef
|
||||
|
||||
define READ_SOURCE_DATE_EPOCH
|
||||
$(strip $(shell cat $(SOURCE_DATE_EPOCH_FILE)))
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
APP := lib/elixir/ebin/elixir.app
|
||||
@@ -113,6 +127,29 @@ install: compile
|
||||
done
|
||||
$(MAKE) install_man
|
||||
|
||||
check_reproducible: compile
|
||||
$(Q) echo "==> Checking for reproducible builds..."
|
||||
$(Q) rm -rf lib/*/tmp/ebin_reproducible/
|
||||
$(call WRITE_SOURCE_DATE_EPOCH)
|
||||
$(Q) mkdir -p lib/elixir/tmp/ebin_reproducible/ \
|
||||
lib/eex/tmp/ebin_reproducible/ \
|
||||
lib/iex/tmp/ebin_reproducible/ \
|
||||
lib/logger/tmp/ebin_reproducible/ \
|
||||
lib/mix/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/elixir/ebin/* lib/elixir/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/eex/ebin/* lib/eex/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/iex/ebin/* lib/iex/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/logger/ebin/* lib/logger/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/mix/ebin/* lib/mix/tmp/ebin_reproducible/
|
||||
SOURCE_DATE_EPOCH=$(call READ_SOURCE_DATE_EPOCH) $(MAKE) compile
|
||||
$(Q) echo "Diffing..."
|
||||
$(Q) diff -r lib/elixir/ebin/ lib/elixir/tmp/ebin_reproducible/
|
||||
$(Q) diff -r lib/eex/ebin/ lib/eex/tmp/ebin_reproducible/
|
||||
$(Q) diff -r lib/iex/ebin/ lib/iex/tmp/ebin_reproducible/
|
||||
$(Q) diff -r lib/logger/ebin/ lib/logger/tmp/ebin_reproducible/
|
||||
$(Q) diff -r lib/mix/ebin/ lib/mix/tmp/ebin_reproducible/
|
||||
$(Q) echo "Builds are reproducible"
|
||||
|
||||
clean:
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
@@ -190,7 +227,7 @@ Precompiled.zip: build_man compile
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
@ echo ""
|
||||
@ echo "## Checksums"
|
||||
@ 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:"
|
||||
|
||||
@@ -11,7 +11,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -27,7 +27,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
=========
|
||||
[](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.
|
||||
@@ -9,13 +10,28 @@ and maintainable applications.
|
||||
For more about Elixir, installation and documentation,
|
||||
[check Elixir's website](https://elixir-lang.org/).
|
||||
|
||||
## Announcements
|
||||
## Policies
|
||||
|
||||
New releases are announced in the [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
New releases are announced in the [announcement mailing list][8].
|
||||
You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email.
|
||||
|
||||
All security releases [will be tagged with `[security]`][10]. For more information, please read our [Security Policy][9].
|
||||
|
||||
All interactions in our official communication channels follow our [Code of Conduct][1].
|
||||
|
||||
## Bug reports
|
||||
|
||||
For reporting bugs, [visit our issue tracker][2] and follow the steps
|
||||
for reporting a new issue. **Please disclose security vulnerabilities
|
||||
privately at elixir-security@googlegroups.com**.
|
||||
|
||||
## Compiling from source
|
||||
|
||||
To run Elixir from source, clone this repository to your machine, compile and test it:
|
||||
For the many different ways to install Elixir,
|
||||
[see our installation instructions on the website](https://elixir-lang.org/install.html).
|
||||
To compile from source, you can follow the steps below.
|
||||
|
||||
First, [install Erlang](https://elixir-lang.org/install.html#installing-erlang). Then clone this repository to your machine, compile and test it:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/elixir-lang/elixir.git
|
||||
@@ -31,10 +47,9 @@ If Elixir fails to build (specifically when pulling in a new version via
|
||||
`git`), be sure to remove any previous build artifacts by running
|
||||
`make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1]
|
||||
or to try Interactive Elixir by running `bin/iex` in your terminal.
|
||||
If tests pass, you can use Interactive Elixir by running `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang/OTP version
|
||||
However, if tests fail, it is likely that you have an outdated Erlang/OTP version
|
||||
(Elixir requires Erlang/OTP 20.0 or later). You can check your Erlang/OTP version
|
||||
by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
@@ -43,12 +58,6 @@ by calling `erl` in the command line. You will see some information as follows:
|
||||
If you have properly set up your dependencies and tests still fail,
|
||||
you may want to open up a bug report, as explained next.
|
||||
|
||||
## Bug reports
|
||||
|
||||
For reporting bugs, [visit our issues tracker][2] and follow the steps
|
||||
for reporting a new issue. **Please disclose security vulnerabilities
|
||||
privately at elixir-security@googlegroups.com**.
|
||||
|
||||
## Proposing new features
|
||||
|
||||
For proposing new features, please start a discussion in the
|
||||
@@ -56,17 +65,14 @@ For proposing new features, please start a discussion in the
|
||||
to argue and explain why a feature is useful and how it will impact the
|
||||
codebase and the community.
|
||||
|
||||
Once a proposal is accepted, it will be added to [the issues tracker][2].
|
||||
The issues tracker focuses on *actionable items* and it holds a list of
|
||||
Once a proposal is accepted, it will be added to [the issue tracker][2].
|
||||
The issue tracker focuses on *actionable items* and it holds a list of
|
||||
upcoming enhancements and pending bugs. All entries in the tracker are
|
||||
tagged for clarity and to ease collaboration.
|
||||
|
||||
Features and bug fixes that have already been merged and will be included
|
||||
in the next release are marked as "closed" in the issues tracker and are
|
||||
added to the [CHANGELOG](CHANGELOG.md).
|
||||
|
||||
Finally, remember all interactions in our official spaces follow our
|
||||
[Code of Conduct][7].
|
||||
in the next release are marked as "closed" in the issue tracker and are
|
||||
added to the [changelog][7].
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -74,20 +80,20 @@ We welcome everyone to contribute to Elixir. To do so, there are a few
|
||||
things you need to know about the code. First, Elixir code is divided
|
||||
in applications inside the `lib` folder:
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib
|
||||
* `elixir` - Elixir's kernel and standard library
|
||||
|
||||
* `eex` - Template engine that allows you to embed Elixir
|
||||
* `eex` - EEx is the template engine that allows you to embed Elixir
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir
|
||||
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
|
||||
|
||||
* `iex` - IEx, Elixir's interactive shell
|
||||
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
|
||||
|
||||
* `logger` - The built-in logger
|
||||
* `logger` - Logger is the built-in logger
|
||||
|
||||
* `mix` - Elixir's build tool
|
||||
* `mix` - Mix is Elixir's build tool
|
||||
|
||||
You can run all tests in the root directory with `make test` and you can
|
||||
also run tests for a specific framework `make test_#{NAME}`, for example,
|
||||
also run tests for a specific framework `make test_#{APPLICATION}`, 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`.
|
||||
|
||||
@@ -120,7 +126,7 @@ make clean_elixir compile
|
||||
Similarly, if you can't get Elixir to compile or the tests to pass after
|
||||
updating an existing checkout, run `make clean compile`. You can check
|
||||
[the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
More tasks can be found by reading the [Makefile](./Makefile).
|
||||
More tasks can be found by reading the [Makefile](Makefile).
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
[send a pull request](https://help.github.com/articles/using-pull-requests/).
|
||||
@@ -152,7 +158,8 @@ another team member can merge it.
|
||||
|
||||
When the review finishes, your pull request will be squashed and merged
|
||||
into the repository. If you have carefully organized your commits and
|
||||
believe they should be merged without squashing, leave a comment.
|
||||
believe they should be merged without squashing, please mention it in
|
||||
a comment.
|
||||
|
||||
## Building documentation
|
||||
|
||||
@@ -163,7 +170,13 @@ to be installed and built alongside Elixir:
|
||||
# After cloning and compiling Elixir, in its parent directory:
|
||||
git clone git://github.com/elixir-lang/ex_doc.git
|
||||
cd ex_doc && ../elixir/bin/mix do deps.get, compile
|
||||
cd ../elixir && make docs
|
||||
```
|
||||
|
||||
Now go back to Elixir's root directory and run:
|
||||
|
||||
```sh
|
||||
make docs # to generate HTML pages
|
||||
make docs DOCS_FORMAT=epub # to generate EPUB documents
|
||||
```
|
||||
|
||||
This will produce documentation sets for `elixir`, `mix`, etc. under
|
||||
@@ -172,25 +185,30 @@ the `doc` directory. If you are planning to contribute documentation,
|
||||
|
||||
## Development links
|
||||
|
||||
* [Elixir Getting Started guide][1]
|
||||
* [Elixir Documentation][6]
|
||||
* [Elixir Core Mailing list (development)][3]
|
||||
* [Issues tracker][2]
|
||||
* [Code of Conduct][7]
|
||||
* [Announcement mailing list][8]
|
||||
* [Code of Conduct][1]
|
||||
* [Issue tracker][2]
|
||||
* [Changelog][7]
|
||||
* [Security Policy][9]
|
||||
* **[#elixir-lang][4]** on [Freenode][5] IRC
|
||||
|
||||
[1]: https://elixir-lang.org/getting-started/introduction.html
|
||||
[1]: CODE_OF_CONDUCT.md
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: https://groups.google.com/group/elixir-lang-core
|
||||
[4]: https://webchat.freenode.net/?channels=#elixir-lang
|
||||
[5]: http://www.freenode.net
|
||||
[5]: https://www.freenode.net
|
||||
[6]: https://elixir-lang.org/docs.html
|
||||
[7]: CODE_OF_CONDUCT.md
|
||||
[7]: CHANGELOG.md
|
||||
[8]: https://groups.google.com/group/elixir-lang-ann
|
||||
[9]: SECURITY.md
|
||||
[10]: https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date
|
||||
|
||||
## License
|
||||
|
||||
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
|
||||
|
||||
Elixir source code is released under Apache 2 License.
|
||||
Elixir source code is released under Apache License 2.0.
|
||||
|
||||
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more information.
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
|
||||
1. Set `CANONICAL=` in /Makefile
|
||||
|
||||
2. Update tables in "Compatibility and Deprecations"
|
||||
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
|
||||
|
||||
3. Commit "Prepare vMAJOR.MINOR for release"
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
Elixir applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branches:
|
||||
|
||||
| Elixir version | Support
|
||||
| -------------- | ------------------------------
|
||||
| 1.9 | Bug fixes and security patches
|
||||
| 1.8 | Security patches only
|
||||
| 1.7 | Security patches only
|
||||
| 1.6 | Security patches only
|
||||
| 1.5 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email.
|
||||
|
||||
All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please disclose security vulnerabilities privately at elixir-security@googlegroups.com
|
||||
+169
-63
@@ -1,31 +1,56 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
echo "Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
|
||||
-e COMMAND Evaluates the given command (*)
|
||||
-r FILE Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in PATH
|
||||
-pr FILE Requires the given files/patterns in parallel (*)
|
||||
-pa PATH Prepends the given path to Erlang code path (*)
|
||||
-pz PATH Appends the given path to Erlang code path (*)
|
||||
## General options
|
||||
|
||||
--app APP Starts the given app and its dependencies (*)
|
||||
--cookie COOKIE Sets a cookie for this distributed node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--erl SWITCHES Switches to be passed down to Erlang (*)
|
||||
--help, -h Prints this message and exits
|
||||
--hidden Makes a hidden node
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--name NAME Makes and assigns a name to the distributed node
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--sname NAME Makes and assigns a short name to the distributed node
|
||||
--version, -v Prints Elixir version and exits
|
||||
--werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
-e \"COMMAND\" Evaluates the given command (*)
|
||||
-h, --help Prints this message and exits
|
||||
-r \"FILE\" Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in \$PATH
|
||||
-pr \"FILE\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"PATH\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"PATH\" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Elixir version and exits
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
--app APP Starts the given app and its dependencies (*)
|
||||
--erl \"SWITCHES\" Switches to be passed down to Erlang (*)
|
||||
--eval \"COMMAND\" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
|
||||
Options given after the .exs file or -- are passed down to the executed code.
|
||||
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS or --erl.
|
||||
|
||||
## Distribution options
|
||||
|
||||
The following options are related to node distribution.
|
||||
|
||||
--cookie COOKIE Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--name NAME Makes and assigns a name to the distributed node
|
||||
--rpc-eval NODE \"COMMAND\" Evaluates the given command on the given remote node (*)
|
||||
--sname NAME Makes and assigns a short name to the distributed node
|
||||
|
||||
## Release options
|
||||
|
||||
The following options are generally used under releases.
|
||||
|
||||
--boot \"FILE\" Uses the given FILE.boot to start the system
|
||||
--boot-var VAR \"VALUE\" Makes \$VAR available as VALUE to FILE.boot (*)
|
||||
--erl-config \"FILE\" Loads configuration in FILE.config written in Erlang (*)
|
||||
--pipe-to \"PIPEDIR\" \"LOGDIR\" Starts the Erlang VM as a named PIPEDIR and LOGDIR
|
||||
--vm-args \"FILE\" Passes the contents in file as arguments to the VM
|
||||
|
||||
--pipe-to starts Elixir detached from console (Unix-like only).
|
||||
It will attempt to create PIPEDIR and LOGDIR if they don't exist.
|
||||
See run_erl to learn more. To reattach, run: to_erl PIPEDIR.
|
||||
|
||||
** Options marked with (*) can be given more than once." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -35,70 +60,142 @@ readlink_f () {
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
echo "$(pwd -P)/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
MODE="elixir"
|
||||
ERL_EXEC="erl"
|
||||
# Stores static erlang arguments and --erl (which is passed as is)
|
||||
ERL=""
|
||||
I=1
|
||||
|
||||
while [ $I -le $# ]; do
|
||||
# Stores erl arguments preserving spaces/quotes (mimics an array)
|
||||
erl () {
|
||||
eval "E${E}=\$1"
|
||||
E=$((E + 1))
|
||||
}
|
||||
|
||||
# Checks if a string starts with prefix. Usage: starts_with "$STRING" "$PREFIX"
|
||||
starts_with () {
|
||||
case $1 in
|
||||
"$2"*) true;;
|
||||
*) false;;
|
||||
esac
|
||||
}
|
||||
|
||||
ERL_EXEC="erl"
|
||||
MODE="elixir"
|
||||
I=1
|
||||
E=0
|
||||
LENGTH=$#
|
||||
set -- "$@" -extra
|
||||
|
||||
while [ $I -le $LENGTH ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
case "$1" in
|
||||
+iex)
|
||||
set -- "$@" "$1"
|
||||
MODE="iex"
|
||||
;;
|
||||
+elixirc)
|
||||
set -- "$@" "$1"
|
||||
MODE="elixirc"
|
||||
;;
|
||||
-v|--compile|--no-halt)
|
||||
-v|--no-halt)
|
||||
set -- "$@" "$1"
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
-e|-r|-pr|-pa|-pz|--app|--eval|--remsh|--dot-iex)
|
||||
S=2
|
||||
set -- "$@" "$1" "$2"
|
||||
;;
|
||||
--detached|--hidden)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
--rpc-eval)
|
||||
S=3
|
||||
set -- "$@" "$1" "$2" "$3"
|
||||
;;
|
||||
--cookie)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL -setcookie "$VAL""
|
||||
--detached)
|
||||
echo "warning: the --detached option is deprecated" >&2
|
||||
ERL="$ERL -detached"
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-` "$VAL""
|
||||
--hidden)
|
||||
ERL="$ERL -hidden"
|
||||
;;
|
||||
--logger-otp-reports)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
if [ "$VAL" = 'true' ] || [ "$VAL" = 'false' ]; then
|
||||
ERL="$ERL -logger handle_otp_reports "$VAL""
|
||||
S=2
|
||||
if [ "$2" = 'true' ] || [ "$2" = 'false' ]; then
|
||||
ERL="$ERL -logger handle_otp_reports $2"
|
||||
fi
|
||||
;;
|
||||
--logger-sasl-reports)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
if [ "$VAL" = 'true' ] || [ "$VAL" = 'false' ]; then
|
||||
ERL="$ERL -logger handle_sasl_reports "$VAL""
|
||||
S=2
|
||||
if [ "$2" = 'true' ] || [ "$2" = 'false' ]; then
|
||||
ERL="$ERL -logger handle_sasl_reports $2"
|
||||
fi
|
||||
;;
|
||||
--erl)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
S=2
|
||||
ERL="$ERL $2"
|
||||
;;
|
||||
--cookie)
|
||||
S=2
|
||||
erl "-setcookie"
|
||||
erl "$2"
|
||||
;;
|
||||
--sname|--name)
|
||||
S=2
|
||||
erl "$(echo "$1" | cut -c 2-)"
|
||||
erl "$2"
|
||||
;;
|
||||
--erl-config)
|
||||
S=2
|
||||
erl "-config"
|
||||
erl "$2"
|
||||
;;
|
||||
--vm-args)
|
||||
S=2
|
||||
erl "-args_file"
|
||||
erl "$2"
|
||||
;;
|
||||
--boot)
|
||||
S=2
|
||||
erl "-boot"
|
||||
erl "$2"
|
||||
;;
|
||||
--boot-var)
|
||||
S=3
|
||||
erl "-boot_var"
|
||||
erl "$2"
|
||||
erl "$3"
|
||||
;;
|
||||
--pipe-to)
|
||||
S=3
|
||||
RUN_ERL_PIPE="$2"
|
||||
RUN_ERL_LOG="$3"
|
||||
if [ "$(starts_with "$RUN_ERL_PIPE" "-")" ]; then
|
||||
echo "--pipe-to : PIPEDIR cannot be a switch" >&2 && exit 1
|
||||
elif [ "$(starts_with "$RUN_ERL_LOG" "-")" ]; then
|
||||
echo "--pipe-to : LOGDIR cannot be a switch" >&2 && exit 1
|
||||
fi
|
||||
;;
|
||||
--werl)
|
||||
USE_WERL=true
|
||||
if [ "$OS" = "Windows_NT" ]; then ERL_EXEC="werl"; fi
|
||||
;;
|
||||
*)
|
||||
while [ $I -le $LENGTH ]; do
|
||||
I=$((I + 1))
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
done
|
||||
break
|
||||
;;
|
||||
esac
|
||||
I=$(expr $I + $S)
|
||||
|
||||
I=$((I + S))
|
||||
shift $S
|
||||
done
|
||||
|
||||
I=$((E - 1))
|
||||
while [ $I -ge 0 ]; do
|
||||
eval "VAL=\$E$I"
|
||||
set -- "$VAL" "$@"
|
||||
I=$((I - 1))
|
||||
done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
@@ -107,17 +204,26 @@ SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
if [ "$OS" != "Windows_NT" ]; then
|
||||
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
|
||||
fi
|
||||
|
||||
if [ "$OS" = "Windows_NT" ] && [ $USE_WERL ]; then
|
||||
ERL_EXEC="werl"
|
||||
ERTS_BIN=
|
||||
set -- "$ERTS_BIN$ERL_EXEC" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL "$@"
|
||||
|
||||
if [ -n "$RUN_ERL_PIPE" ]; then
|
||||
ESCAPED=""
|
||||
for PART in "$@"; do
|
||||
ESCAPED="$ESCAPED $(echo "$PART" | sed 's/[^a-zA-Z0-9_\-\/]/\\&/g')"
|
||||
done
|
||||
mkdir -p "$RUN_ERL_PIPE"
|
||||
mkdir -p "$RUN_ERL_LOG"
|
||||
ERL_EXEC="run_erl"
|
||||
set -- "$ERTS_BIN$ERL_EXEC" -daemon "$RUN_ERL_PIPE/" "$RUN_ERL_LOG/" "$ESCAPED"
|
||||
fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
ERL_PATH="$ERL_EXEC"
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
if [ -n "$ELIXIR_CLI_DRY_RUN" ]; then
|
||||
echo "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
+118
-68
@@ -1,5 +1,5 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" goto documentation
|
||||
if /I ""%1""==""-h"" goto documentation
|
||||
@@ -10,105 +10,155 @@ goto parseopts
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -e COMMAND Evaluates the given command (*)
|
||||
echo -r FILE Requires the given files/patterns (*)
|
||||
echo -S SCRIPT Finds and executes the given script in PATH
|
||||
echo -pr FILE Requires the given files/patterns in parallel (*)
|
||||
echo -pa PATH Prepends the given path to Erlang code path (*)
|
||||
echo -pz PATH Appends the given path to Erlang code path (*)
|
||||
echo ## General options
|
||||
echo.
|
||||
echo --app APP Starts the given app and its dependencies (*)
|
||||
echo --cookie COOKIE Sets a cookie for this distributed node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --erl SWITCHES Switches to be passed down to Erlang (*)
|
||||
echo --help, -h Prints this message and exits
|
||||
echo --hidden Makes a hidden node
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
echo --name NAME Makes and assigns a name to the distributed node
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --sname NAME Makes and assigns a short name to the distributed node
|
||||
echo --version, -v Prints Elixir version and exits
|
||||
echo --werl Uses Erlang's Windows shell GUI
|
||||
echo -e "COMMAND" Evaluates the given command (*)
|
||||
echo -h, --help Prints this message and exits
|
||||
echo -r "FILE" Requires the given files/patterns (*)
|
||||
echo -S SCRIPT Finds and executes the given script in $PATH
|
||||
echo -pr "FILE" Requires the given files/patterns in parallel (*)
|
||||
echo -pa "PATH" Prepends the given path to Erlang code path (*)
|
||||
echo -pz "PATH" Appends the given path to Erlang code path (*)
|
||||
echo -v, --version Prints Elixir version and exits
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
echo --app APP Starts the given app and its dependencies (*)
|
||||
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
echo.
|
||||
echo Options given after the .exs file or -- are passed down to the executed code.
|
||||
echo Options can be passed to the Erlang runtime using $ELIXIR_ERL_OPTIONS or --erl.
|
||||
echo.
|
||||
echo ## Distribution options
|
||||
echo.
|
||||
echo The following options are related to node distribution.
|
||||
echo.
|
||||
echo --cookie COOKIE Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --name NAME Makes and assigns a name to the distributed node
|
||||
echo --rpc-eval NODE "COMMAND" Evaluates the given command on the given remote node (*)
|
||||
echo --sname NAME Makes and assigns a short name to the distributed node
|
||||
echo.
|
||||
echo ## Release options
|
||||
echo.
|
||||
echo The following options are generally used under releases.
|
||||
echo.
|
||||
echo --boot "FILE" Uses the given FILE.boot to start the system
|
||||
echo --boot-var VAR "VALUE" Makes $VAR available as VALUE to FILE.boot (*)
|
||||
echo --erl-config "FILE" Loads configuration in FILE.config written in Erlang (*)
|
||||
echo --vm-args "FILE" Passes the contents in file as arguments to the VM
|
||||
echo.
|
||||
echo --pipe-to is not supported on Windows. If set, Elixir won't boot.
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once.
|
||||
goto end
|
||||
|
||||
:parseopts
|
||||
|
||||
rem Parameters for Elixir
|
||||
set parsElixir=
|
||||
|
||||
rem Parameters for Erlang
|
||||
set parsErlang=
|
||||
|
||||
rem Make sure we keep a copy of all parameters
|
||||
set allPars=%*
|
||||
|
||||
rem Get the original path name from the batch file
|
||||
set originPath=%~dp0
|
||||
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Option which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
rem Option which determines whether the loop is over
|
||||
set endLoop=0
|
||||
|
||||
rem Designates which mode / Elixir component to run as
|
||||
set runMode="elixir"
|
||||
|
||||
rem Designates the path to the current script
|
||||
set SCRIPT_PATH=%~dp0
|
||||
|
||||
rem Designates the path to the ERTS system
|
||||
set ERTS_BIN=
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
shift
|
||||
if "%par%"=="" (
|
||||
rem if no parameters defined
|
||||
set "par=%~1"
|
||||
if "!par!"=="" (
|
||||
rem skip if no parameter
|
||||
goto expand_erl_libs
|
||||
)
|
||||
if "%par%"=="""" (
|
||||
rem if no parameters defined - special case for parameter that is already quoted
|
||||
goto expand_erl_libs
|
||||
shift
|
||||
set par="!par:"=\"!"
|
||||
if !endLoop! == 1 (
|
||||
set parsElixir=!parsElixir! !par!
|
||||
goto startloop
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
if "%par%"==""--werl"" (set useWerl=1)
|
||||
if "%par%"==""+iex"" (set runMode="iex")
|
||||
if !par!=="--werl" (set useWerl=1 && goto startloop)
|
||||
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set runMode="iex" && goto startloop)
|
||||
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && set runMode="elixirc" && goto startloop)
|
||||
rem ******* EVAL PARAMETERS ************************
|
||||
if ""==!par:-e=! (
|
||||
set "VAR=%~1"
|
||||
set parsElixir=!parsElixir! -e "!VAR:"=\"!"
|
||||
shift
|
||||
goto startloop
|
||||
)
|
||||
if ""==!par:--eval=! (
|
||||
set "VAR=%~1"
|
||||
set parsElixir=!parsElixir! --eval "!VAR:"=\"!"
|
||||
shift
|
||||
goto startloop
|
||||
)
|
||||
if ""==!par:--rpc-eval=! (
|
||||
set "VAR=%~2"
|
||||
set parsElixir=!parsElixir! --rpc-eval %1 "!VAR:"=\"!"
|
||||
shift
|
||||
shift
|
||||
goto startloop
|
||||
)
|
||||
rem ******* ELIXIR PARAMETERS **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
if """"=="%par:-e=%" (shift)
|
||||
if """"=="%par:-r=%" (shift)
|
||||
if """"=="%par:-pr=%" (shift)
|
||||
if """"=="%par:-pa=%" (shift)
|
||||
if """"=="%par:-pz=%" (shift)
|
||||
if """"=="%par:--app=%" (shift)
|
||||
if """"=="%par:--remsh=%" (shift)
|
||||
if ""==!par:-r=! (set "parsElixir=!parsElixir! -r %1" && shift && goto startloop)
|
||||
if ""==!par:-pr=! (set "parsElixir=!parsElixir! -pr %1" && shift && goto startloop)
|
||||
if ""==!par:-pa=! (set "parsElixir=!parsElixir! -pa %1" && shift && goto startloop)
|
||||
if ""==!par:-pz=! (set "parsElixir=!parsElixir! -pz %1" && shift && goto startloop)
|
||||
if ""==!par:-v=! (set "parsElixir=!parsElixir! -v" && goto startloop)
|
||||
if ""==!par:--app=! (set "parsElixir=!parsElixir! --app %1" && shift && goto startloop)
|
||||
if ""==!par:--no-halt=! (set "parsElixir=!parsElixir! --no-halt" && goto startloop)
|
||||
if ""==!par:--remsh=! (set "parsElixir=!parsElixir! --remsh %1" && shift && goto startloop)
|
||||
if ""==!par:--dot-iex=! (set "parsElixir=!parsElixir! --dot-iex %1" && shift && goto startloop)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
if """"=="%par:--detached=%" (set parsErlang=%parsErlang% -detached)
|
||||
if """"=="%par:--hidden=%" (set parsErlang=%parsErlang% -hidden)
|
||||
if """"=="%par:--cookie=%" (set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||
if """"=="%par:--sname=%" (set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
if """"=="%par:--name=%" (set parsErlang=%parsErlang% -name %1 && shift)
|
||||
if """"=="%par:--logger-otp-reports=%" (set parsErlang=%parsErlang% -logger handle_otp_reports %1 && shift)
|
||||
if """"=="%par:--logger-sasl-reports=%" (set parsErlang=%parsErlang% -logger handle_sasl_reports %1 && shift)
|
||||
if """"=="%par:--erl=%" (set "beforeExtra=%beforeExtra% %~1" && shift)
|
||||
goto:startloop
|
||||
if ""==!par:--boot=! (set "parsErlang=!parsErlang! -boot %1" && shift && goto startloop)
|
||||
if ""==!par:--boot-var=! (set "parsErlang=!parsErlang! -boot_var %1 %2" && shift && shift && goto startloop)
|
||||
if ""==!par:--cookie=! (set "parsErlang=!parsErlang! -setcookie %1" && shift && goto startloop)
|
||||
if ""==!par:--hidden=! (set "parsErlang=!parsErlang! -hidden" && goto startloop)
|
||||
if ""==!par:--detached=! (set "parsErlang=!parsErlang! -detached" && echo warning: the --detached option is deprecated && goto startloop)
|
||||
if ""==!par:--erl-config=! (set "parsErlang=!parsErlang! -config %1" && shift && goto startloop)
|
||||
if ""==!par:--logger-otp-reports=! (set "parsErlang=!parsErlang! -logger handle_otp_reports %1" && shift && goto startloop)
|
||||
if ""==!par:--logger-sasl-reports=! (set "parsErlang=!parsErlang! -logger handle_sasl_reports %1" && shift && goto startloop)
|
||||
if ""==!par:--name=! (set "parsErlang=!parsErlang! -name %1" && shift && goto startloop)
|
||||
if ""==!par:--sname=! (set "parsErlang=!parsErlang! -sname %1" && shift && goto startloop)
|
||||
if ""==!par:--vm-args=! (set "parsErlang=!parsErlang! -args_file %1" && shift && goto startloop)
|
||||
if ""==!par:--erl=! (set "beforeExtra=!beforeExtra! %~1" && shift && goto startloop)
|
||||
if ""==!par:--pipe-to=! (echo --pipe-to : Option is not supported on Windows && goto end)
|
||||
set endLoop=1
|
||||
set parsElixir=!parsElixir! !par!
|
||||
goto startloop
|
||||
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
:expand_erl_libs
|
||||
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
|
||||
setlocal enabledelayedexpansion
|
||||
rem expand all ebin paths as Windows does not support the ..\*\ebin wildcard
|
||||
set ext_libs=
|
||||
for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
for /d %%d in ("!SCRIPT_PATH!..\lib\*.") do (
|
||||
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
|
||||
)
|
||||
setlocal disabledelayedexpansion
|
||||
|
||||
:run
|
||||
if not %runMode% == "iex" (
|
||||
set beforeExtra=-noshell -s elixir start_cli %beforeExtra%
|
||||
if not !runMode! == "iex" (
|
||||
set beforeExtra=-noshell -s elixir start_cli !beforeExtra!
|
||||
)
|
||||
if %useWerl% equ 1 (
|
||||
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
if defined useWerl (
|
||||
start "" "!ERTS_BIN!werl.exe" !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
) else (
|
||||
erl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
"!ERTS_BIN!erl.exe" !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
)
|
||||
:end
|
||||
endlocal
|
||||
endlocal
|
||||
+9
-7
@@ -1,20 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
|
||||
echo "Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-h, --help Prints this message and exits
|
||||
-o The directory to output compiled files
|
||||
-v, --version Prints Elixir version and exits
|
||||
|
||||
--help, -h Prints this message and exits
|
||||
--ignore-module-conflict Does not emit warnings if a module was previously defined
|
||||
--no-debug-info Does not attach debug info to compiled modules
|
||||
--no-docs Does not attach documentation to compiled modules
|
||||
--verbose Prints compilation status
|
||||
--version, -v Prints Elixir version and exits
|
||||
--warnings-as-errors Treats warnings as errors and return non-zero exit code
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the Erlang compiler using ERL_COMPILER_OPTIONS" >&2
|
||||
Options given after -- are passed down to the executed code.
|
||||
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS.
|
||||
Options can be passed to the Erlang compiler using \$ERL_COMPILER_OPTIONS." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -24,7 +26,7 @@ readlink_f () {
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
echo "$(pwd -P)/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
#!/bin/sh
|
||||
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
set -e
|
||||
|
||||
-e COMMAND Evaluates the given command (*)
|
||||
-r FILE Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in PATH
|
||||
-pr FILE Requires the given files/patterns in parallel (*)
|
||||
-pa PATH Prepends the given path to Erlang code path (*)
|
||||
-pz PATH Appends the given path to Erlang code path (*)
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
|
||||
--app APP Starts the given app and its dependencies (*)
|
||||
--cookie COOKIE Sets a cookie for this distributed node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--erl SWITCHES Switches to be passed down to Erlang (*)
|
||||
--help, -h Prints this message and exits
|
||||
--hidden Makes a hidden node
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--name NAME Makes and assigns a name to the distributed node
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--sname NAME Makes and assigns a short name to the distributed node
|
||||
--version, -v Prints IEx version and exits
|
||||
--werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
The following options are exclusive to IEx:
|
||||
|
||||
--dot-iex PATH Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
--remsh NAME Connects to a node using a remote shell
|
||||
--dot-iex \"PATH\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
--remsh NAME Connects to a node using a remote shell
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
It accepts all other options listed by \"elixir --help\"." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -39,7 +20,7 @@ readlink_f () {
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
echo "$(pwd -P)/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
+9
-28
@@ -1,4 +1,4 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
setlocal
|
||||
if /I ""%1""==""--help"" goto documentation
|
||||
if /I ""%1""==""-h"" goto documentation
|
||||
@@ -9,38 +9,19 @@ goto run
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -e COMMAND Evaluates the given command (*)
|
||||
echo -r FILE Requires the given files/patterns (*)
|
||||
echo -S SCRIPT Finds and executes the given script in PATH
|
||||
echo -pr FILE Requires the given files/patterns in parallel (*)
|
||||
echo -pa PATH Prepends the given path to Erlang code path (*)
|
||||
echo -pz PATH Appends the given path to Erlang code path (*)
|
||||
echo The following options are exclusive to IEx:
|
||||
echo.
|
||||
echo --app APP Starts the given app and its dependencies (*)
|
||||
echo --cookie COOKIE Sets a cookie for this distributed node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --erl SWITCHES Switches to be passed down to Erlang (*)
|
||||
echo --help, -h Prints this message and exits
|
||||
echo --hidden Makes a hidden node
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
echo --name NAME Makes and assigns a name to the distributed node
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --sname NAME Makes and assigns a short name to the distributed node
|
||||
echo --version, -v Prints IEx version and exits
|
||||
echo --werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
echo --dot-iex "PATH" Overrides default .iex.exs file and uses path instead;
|
||||
echo path can be empty, then no file will be loaded
|
||||
echo --remsh NAME Connects to a node using a remote shell
|
||||
echo --werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
echo.
|
||||
echo --dot-iex PATH Overrides default .iex.exs file and uses path instead;
|
||||
echo path can be empty, then no file will be loaded
|
||||
echo --remsh NAME Connects to a node using a remote shell
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the Erlang VM using ELIXIR_ERL_OPTIONS or --erl
|
||||
echo Set the IEX_WITH_WERL environment variable to always use werl.
|
||||
echo It accepts all other options listed by "elixir --help".
|
||||
goto end
|
||||
|
||||
:run
|
||||
@if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||
if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||
call "%~dp0\elixir.bat" --no-halt --erl "-noshell -user Elixir.IEx.CLI" +iex %__ELIXIR_IEX_FLAGS% %*
|
||||
:end
|
||||
endlocal
|
||||
|
||||
+8
-5
@@ -43,7 +43,8 @@ defmodule EEx do
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
* `:trim` - trims whitespace left/right of quotation tags
|
||||
* `:trim` - trims whitespace left/right of quotation tags. If a quotation
|
||||
tag appears on its own in a given line, line endings are also removed.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -105,7 +106,7 @@ defmodule EEx do
|
||||
|
||||
iex> defmodule Sample do
|
||||
...> require EEx
|
||||
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
...> EEx.function_from_string(:def, :sample, "<%= a + b %>", [:a, :b])
|
||||
...> end
|
||||
iex> Sample.sample(1, 2)
|
||||
"3"
|
||||
@@ -141,11 +142,12 @@ defmodule EEx do
|
||||
# sample.ex
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_file :def, :sample, "sample.eex", [:a, :b]
|
||||
EEx.function_from_file(:def, :sample, "sample.eex", [:a, :b])
|
||||
end
|
||||
|
||||
# iex
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
Sample.sample(1, 2)
|
||||
#=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
@@ -207,7 +209,8 @@ defmodule EEx do
|
||||
foo <%= bar %>
|
||||
|
||||
# iex
|
||||
EEx.eval_file "sample.eex", [bar: "baz"] #=> "foo baz"
|
||||
EEx.eval_file("sample.eex", bar: "baz")
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
@spec eval_file(String.t(), keyword, keyword) :: any
|
||||
|
||||
+34
-24
@@ -41,35 +41,40 @@ defmodule EEx.Compiler do
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{:expr, line, mark, chars} | rest], buffer, scope, state) do
|
||||
defp generate_buffer([{:expr, line, mark, chars, _} | rest], buffer, scope, state) do
|
||||
expr = Code.string_to_quoted!(chars, line: line, file: state.file)
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), expr)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{:start_expr, start_line, mark, chars} | rest], buffer, scope, state) do
|
||||
{contents, line, rest} = look_ahead_text(rest, start_line, chars)
|
||||
defp generate_buffer([{:start_expr, start_line, mark, chars, _} | rest], buffer, scope, state) do
|
||||
{contents, line, rest} = look_ahead_middle(rest, start_line, chars)
|
||||
|
||||
{contents, rest} =
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [contents | scope], %{
|
||||
state
|
||||
| quoted: [],
|
||||
line: line,
|
||||
start_line: start_line
|
||||
})
|
||||
generate_buffer(
|
||||
rest,
|
||||
state.engine.handle_begin(buffer),
|
||||
[contents | scope],
|
||||
%{state | quoted: [], line: line, start_line: start_line}
|
||||
)
|
||||
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), contents)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{:middle_expr, line, '', chars} | rest], buffer, [current | scope], state) do
|
||||
defp generate_buffer(
|
||||
[{:middle_expr, line, '', chars, _} | rest],
|
||||
buffer,
|
||||
[current | scope],
|
||||
state
|
||||
) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
state = %{state | line: line}
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [wrapped | scope], state)
|
||||
end
|
||||
|
||||
defp generate_buffer(
|
||||
[{:middle_expr, line, modifier, chars} | t],
|
||||
[{:middle_expr, line, modifier, chars, trimmed?} | t],
|
||||
buffer,
|
||||
[_ | _] = scope,
|
||||
state
|
||||
@@ -78,38 +83,43 @@ defmodule EEx.Compiler do
|
||||
"unexpected beginning of EEx tag \"<%#{modifier}\" on \"<%#{modifier}#{chars}%>\", " <>
|
||||
"please remove \"#{modifier}\" accordingly"
|
||||
|
||||
:elixir_errors.warn(line, state.file, message)
|
||||
generate_buffer([{:middle_expr, line, '', chars} | t], buffer, scope, state)
|
||||
:elixir_errors.erl_warn(line, state.file, message)
|
||||
generate_buffer([{:middle_expr, line, '', chars, trimmed?} | t], buffer, scope, state)
|
||||
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
|
||||
# raise EEx.SyntaxError, message: message, file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:middle_expr, line, _, chars} | _], _buffer, [], state) do
|
||||
defp generate_buffer([{:middle_expr, line, _, chars, _} | _], _buffer, [], state) do
|
||||
raise EEx.SyntaxError,
|
||||
message: "unexpected middle of expression <%#{chars}%>",
|
||||
file: state.file,
|
||||
line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, '', chars} | rest], buffer, [current | _], state) do
|
||||
defp generate_buffer([{:end_expr, line, '', chars, _} | rest], buffer, [current | _], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = Code.string_to_quoted!(wrapped, line: state.start_line, file: state.file)
|
||||
buffer = insert_quoted(tuples, state.quoted)
|
||||
{buffer, rest}
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, modifier, chars} | t], buffer, [_ | _] = scope, state) do
|
||||
defp generate_buffer(
|
||||
[{:end_expr, line, modifier, chars, trimmed?} | t],
|
||||
buffer,
|
||||
[_ | _] = scope,
|
||||
state
|
||||
) do
|
||||
message =
|
||||
"unexpected beginning of EEx tag \"<%#{modifier}\" on end of " <>
|
||||
"expression \"<%#{modifier}#{chars}%>\", please remove \"#{modifier}\" accordingly"
|
||||
|
||||
:elixir_errors.warn(line, state.file, message)
|
||||
generate_buffer([{:end_expr, line, '', chars} | t], buffer, scope, state)
|
||||
:elixir_errors.erl_warn(line, state.file, message)
|
||||
generate_buffer([{:end_expr, line, '', chars, trimmed?} | t], buffer, scope, state)
|
||||
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
|
||||
# raise EEx.SyntaxError, message: message, file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, _, chars} | _], _buffer, [], state) do
|
||||
defp generate_buffer([{:end_expr, line, _, chars, _} | _], _buffer, [], state) do
|
||||
raise EEx.SyntaxError,
|
||||
message: "unexpected end of expression <%#{chars}%>",
|
||||
file: state.file,
|
||||
@@ -139,10 +149,10 @@ defmodule EEx.Compiler do
|
||||
{count, new_state}
|
||||
end
|
||||
|
||||
# Look text ahead on expressions
|
||||
# Look middle expressions that immediatelly follow a start_expr
|
||||
|
||||
defp look_ahead_text(
|
||||
[{:text, text}, {:middle_expr, line, _, chars} | rest] = tokens,
|
||||
defp look_ahead_middle(
|
||||
[{:text, text}, {:middle_expr, line, _, chars, _} | rest] = tokens,
|
||||
start,
|
||||
contents
|
||||
) do
|
||||
@@ -153,11 +163,11 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp look_ahead_text([{:middle_expr, line, _, chars} | rest], _start, contents) do
|
||||
defp look_ahead_middle([{:middle_expr, line, _, chars, _} | rest], _start, contents) do
|
||||
{contents ++ chars, line, rest}
|
||||
end
|
||||
|
||||
defp look_ahead_text(tokens, start, contents) do
|
||||
defp look_ahead_middle(tokens, start, contents) do
|
||||
{contents, start, tokens}
|
||||
end
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ defmodule EEx.Engine do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Raise on 2.0
|
||||
# TODO: Raise on v2.0
|
||||
@spec fetch_assign!(Access.t(), Access.key()) :: term | nil
|
||||
def fetch_assign!(assigns, key) do
|
||||
case Access.fetch(assigns, key) do
|
||||
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_begin(state) do
|
||||
check_state!(state)
|
||||
%{state | binary: [], dynamic: []}
|
||||
end
|
||||
|
||||
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_body(state) do
|
||||
check_state!(state)
|
||||
%{binary: binary, dynamic: dynamic} = state
|
||||
binary = {:<<>>, [], Enum.reverse(binary)}
|
||||
dynamic = [binary | dynamic]
|
||||
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
|
||||
raise EEx.SyntaxError,
|
||||
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
|
||||
end
|
||||
|
||||
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
|
||||
|
||||
defp check_state!(state) do
|
||||
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
|
||||
"This typically means a bug or an outdated EEx.Engine or tool"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,11 +24,12 @@ defmodule EEx.SmartEngine do
|
||||
# sample.ex
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
|
||||
EEx.function_from_file(:def, :sample, "sample.eex", [:assigns])
|
||||
end
|
||||
|
||||
# iex
|
||||
Sample.sample(a: 1, b: 2) #=> "3"
|
||||
Sample.sample(a: 1, b: 2)
|
||||
#=> "3"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@ defmodule EEx.Tokenizer do
|
||||
@type content :: IO.chardata()
|
||||
@type line :: non_neg_integer
|
||||
@type marker :: '=' | '/' | '|' | ''
|
||||
@type trimmed? :: boolean
|
||||
@type token ::
|
||||
{:text, content}
|
||||
| {:expr | :start_expr | :middle_expr | :end_expr, line, marker, content}
|
||||
| {:expr | :start_expr | :middle_expr | :end_expr, line, marker, content, trimmed?}
|
||||
|
||||
@spaces [?\s, ?\t]
|
||||
@closing_brackets ')]}'
|
||||
|
||||
@doc """
|
||||
Tokenizes the given charlist or binary.
|
||||
@@ -14,10 +18,10 @@ defmodule EEx.Tokenizer do
|
||||
It returns {:ok, list} with the following tokens:
|
||||
|
||||
* `{:text, content}`
|
||||
* `{:expr, line, marker, content}`
|
||||
* `{:start_expr, line, marker, content}`
|
||||
* `{:middle_expr, line, marker, content}`
|
||||
* `{:end_expr, line, marker, content}`
|
||||
* `{:expr, line, marker, content, trimmed?}`
|
||||
* `{:start_expr, line, marker, content, trimmed?}`
|
||||
* `{:middle_expr, line, marker, content, trimmed?}`
|
||||
* `{:end_expr, line, marker, content, trimmed?}`
|
||||
|
||||
Or `{:error, line, error}` in case of errors.
|
||||
"""
|
||||
@@ -44,7 +48,7 @@ defmodule EEx.Tokenizer do
|
||||
error
|
||||
|
||||
{:ok, _, new_line, rest} ->
|
||||
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
{_, rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
tokenize(rest, new_line, opts, buffer, acc)
|
||||
end
|
||||
end
|
||||
@@ -58,9 +62,9 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
{:ok, expr, new_line, rest} ->
|
||||
token = token_name(expr)
|
||||
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
{trimmed?, rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
final = {token, line, marker, Enum.reverse(expr), trimmed?}
|
||||
tokenize(rest, new_line, opts, [], [final | acc])
|
||||
end
|
||||
end
|
||||
@@ -110,25 +114,28 @@ defmodule EEx.Tokenizer do
|
||||
#
|
||||
# Start tokens finish with "do" and "fn ->"
|
||||
# Middle tokens are marked with "->" or keywords
|
||||
# End tokens contain only the end word and optionally ")"
|
||||
# End tokens contain only the end word and optionally
|
||||
# combinations of ")", "]" and "}".
|
||||
|
||||
defp token_name([h | t]) when h in [?\s, ?\t, ?)] do
|
||||
defp token_name([h | t]) when h in @spaces do
|
||||
token_name(t)
|
||||
end
|
||||
|
||||
defp token_name('od' ++ [h | _]) when h in [?\s, ?\t, ?)] do
|
||||
:start_expr
|
||||
defp token_name('od' ++ [h | rest]) when h in @spaces or h in @closing_brackets do
|
||||
case tokenize_rest(rest) do
|
||||
{:ok, [{:end, _} | _]} -> :middle_expr
|
||||
_ -> :start_expr
|
||||
end
|
||||
end
|
||||
|
||||
defp token_name('>-' ++ rest) do
|
||||
rest = Enum.reverse(rest)
|
||||
case tokenize_rest(rest) do
|
||||
{:ok, [{:end, _} | _]} ->
|
||||
:middle_expr
|
||||
|
||||
# Tokenize the remaining passing check_terminators as
|
||||
# false, which relax the tokenizer to not error on
|
||||
# unmatched pairs. Then, we check if there is a "fn"
|
||||
# token and, if so, it is not followed by an "end"
|
||||
# token. If this is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
|
||||
# Check if there is a "fn" token and, if so, it is not
|
||||
# followed by an "end" token. If this is the case, we
|
||||
# are on a start expr.
|
||||
{:ok, tokens} ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
@@ -148,10 +155,19 @@ defmodule EEx.Tokenizer do
|
||||
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
|
||||
|
||||
defp token_name(_) do
|
||||
:expr
|
||||
defp token_name(rest) do
|
||||
case Enum.drop_while(rest, &(&1 in @spaces or &1 in @closing_brackets)) do
|
||||
'dne' ++ t -> check_spaces(t, :end_expr)
|
||||
_ -> :expr
|
||||
end
|
||||
end
|
||||
|
||||
# Tokenize the remaining passing check_terminators as false,
|
||||
# which relax the tokenizer to not error on unmatched pairs.
|
||||
# If the tokens start with an "end" we have a middle expr.
|
||||
defp tokenize_rest(rest) do
|
||||
:elixir_tokenizer.tokenize(Enum.reverse(rest), 1, file: "eex", check_terminators: false)
|
||||
end
|
||||
|
||||
defp fn_index(tokens) do
|
||||
@@ -167,7 +183,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
|
||||
if Enum.all?(string, &(&1 in @spaces)) do
|
||||
token
|
||||
else
|
||||
:expr
|
||||
@@ -189,24 +205,19 @@ defmodule EEx.Tokenizer do
|
||||
# only itself and whitespace, trim the whitespace around it,
|
||||
# including the line break following it if there is one.
|
||||
defp trim_if_needed(rest, line, opts, buffer, acc) do
|
||||
original = {rest, line, buffer}
|
||||
|
||||
if opts[:trim] do
|
||||
case {trim_left(buffer, acc), trim_right(rest, line)} do
|
||||
{{true, new_buffer}, {true, new_rest, new_line}} ->
|
||||
{new_rest, new_line, new_buffer}
|
||||
|
||||
_ ->
|
||||
original
|
||||
end
|
||||
with true <- opts[:trim],
|
||||
{true, new_buffer} <- trim_left(buffer, acc),
|
||||
{true, new_rest, new_line} <- trim_right(rest, line) do
|
||||
{true, new_rest, new_line, new_buffer}
|
||||
else
|
||||
original
|
||||
_ -> {false, rest, line, buffer}
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_left(buffer, acc) do
|
||||
case {trim_whitespace(buffer), acc} do
|
||||
{[?\n | _] = trimmed_buffer, _} -> {true, trimmed_buffer}
|
||||
{[], [{_, _, _, _, true} | _]} -> {true, []}
|
||||
{[], []} -> {true, []}
|
||||
_ -> {false, buffer}
|
||||
end
|
||||
@@ -221,7 +232,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_whitespace([h | t]) when h == ?\s or h == ?\t do
|
||||
defp trim_whitespace([h | t]) when h in @spaces do
|
||||
trim_whitespace(t)
|
||||
end
|
||||
|
||||
|
||||
@@ -29,16 +29,19 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval("1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>")
|
||||
end
|
||||
|
||||
test "preserves line numbers" do
|
||||
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
|
||||
test "preserves line numbers in assignments" do
|
||||
result = EEx.compile_string("foo\n<%= @hello %>", engine: EEx.SmartEngine)
|
||||
|
||||
Macro.prewalk(result, fn
|
||||
{_left, meta, _right} ->
|
||||
assert Keyword.get(meta, :line, 0) in [0, 1]
|
||||
{_left, meta, [_, :hello]} ->
|
||||
assert Keyword.get(meta, :line) == 2
|
||||
send(self(), :found)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
node ->
|
||||
node
|
||||
end)
|
||||
|
||||
assert_received :found
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
|
||||
@@ -13,23 +13,28 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar '}]}
|
||||
assert T.tokenize('foo <% bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar ', false}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar '}]}
|
||||
assert T.tokenize('foo <%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar ', false}]}
|
||||
end
|
||||
|
||||
test "strings with embedded slash code" do
|
||||
assert T.tokenize('foo <%/ bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '/', ' bar '}]}
|
||||
assert T.tokenize('foo <%/ bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '/', ' bar ', false}]}
|
||||
end
|
||||
|
||||
test "strings with embedded pipe code" do
|
||||
assert T.tokenize('foo <%| bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '|', ' bar '}]}
|
||||
assert T.tokenize('foo <%| bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '|', ' bar ', false}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == {:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar '}]}
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar ', false}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -42,9 +47,9 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, '=', ' bar\n\nbaz '},
|
||||
{:expr, 1, '=', ' bar\n\nbaz ', false},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, '', ' foo '},
|
||||
{:expr, 4, '', ' foo ', false},
|
||||
{:text, '\n'}
|
||||
]
|
||||
|
||||
@@ -63,9 +68,9 @@ defmodule EEx.TokenizerTest do
|
||||
test "quotation with interpolation" do
|
||||
exprs = [
|
||||
{:text, 'a <% b '},
|
||||
{:expr, 1, '=', ' c '},
|
||||
{:expr, 1, '=', ' c ', false},
|
||||
{:text, ' '},
|
||||
{:expr, 1, '=', ' d '},
|
||||
{:expr, 1, '=', ' d ', false},
|
||||
{:text, ' e %> f'}
|
||||
]
|
||||
|
||||
@@ -99,9 +104,9 @@ defmodule EEx.TokenizerTest do
|
||||
test "strings with embedded do end" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' if true do '},
|
||||
{:start_expr, 1, '', ' if true do ', false},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
{:end_expr, 1, '', ' end ', false}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, exprs}
|
||||
@@ -110,26 +115,50 @@ defmodule EEx.TokenizerTest do
|
||||
test "strings with embedded -> end" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' cond do '},
|
||||
{:middle_expr, 1, '', ' false -> '},
|
||||
{:start_expr, 1, '', ' cond do ', false},
|
||||
{:middle_expr, 1, '', ' false -> ', false},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, '', ' true -> '},
|
||||
{:middle_expr, 1, '', ' true -> ', false},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
{:end_expr, 1, '', ' end ', false}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) ==
|
||||
{:ok, exprs}
|
||||
end
|
||||
|
||||
test "strings with multiple callbacks" do
|
||||
exprs = [
|
||||
{:start_expr, 1, '=', ' a fn -> ', false},
|
||||
{:text, 'foo'},
|
||||
{:middle_expr, 1, '', ' end, fn -> ', false},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, '', ' end ', false}
|
||||
]
|
||||
|
||||
assert T.tokenize('<%= a fn -> %>foo<% end, fn -> %>bar<% end %>', 1) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "strings with callback followed by do block" do
|
||||
exprs = [
|
||||
{:start_expr, 1, '=', ' a fn -> ', false},
|
||||
{:text, 'foo'},
|
||||
{:middle_expr, 1, '', ' end do ', false},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, '', ' end ', false}
|
||||
]
|
||||
|
||||
assert T.tokenize('<%= a fn -> %>foo<% end do %>bar<% end %>', 1) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' if true do '},
|
||||
{:start_expr, 1, '', ' if true do ', false},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, '', ' else '},
|
||||
{:middle_expr, 1, '', ' else ', false},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
{:end_expr, 1, '', ' end ', false}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, exprs}
|
||||
@@ -139,11 +168,11 @@ defmodule EEx.TokenizerTest do
|
||||
template = '\t<%= if true do %> \n TRUE \n <% else %>\n FALSE \n <% end %> '
|
||||
|
||||
exprs = [
|
||||
{:start_expr, 1, '=', ' if true do '},
|
||||
{:start_expr, 1, '=', ' if true do ', true},
|
||||
{:text, ' TRUE \n'},
|
||||
{:middle_expr, 3, '', ' else '},
|
||||
{:middle_expr, 3, '', ' else ', true},
|
||||
{:text, ' FALSE \n'},
|
||||
{:end_expr, 5, '', ' end '}
|
||||
{:end_expr, 5, '', ' end ', true}
|
||||
]
|
||||
|
||||
assert T.tokenize(template, 1, trim: true) == {:ok, exprs}
|
||||
@@ -160,7 +189,7 @@ defmodule EEx.TokenizerTest do
|
||||
test "trim mode with CRLF" do
|
||||
exprs = [
|
||||
{:text, '0\r\n'},
|
||||
{:expr, 2, '=', ' 12 '},
|
||||
{:expr, 2, '=', ' 12 ', true},
|
||||
{:text, '34'}
|
||||
]
|
||||
|
||||
@@ -170,7 +199,7 @@ defmodule EEx.TokenizerTest do
|
||||
test "trim mode set to false" do
|
||||
exprs = [
|
||||
{:text, ' '},
|
||||
{:expr, 1, '=', ' 12 '},
|
||||
{:expr, 1, '=', ' 12 ', false},
|
||||
{:text, ' \n'}
|
||||
]
|
||||
|
||||
|
||||
+122
-1
@@ -84,6 +84,18 @@ defmodule EExTest do
|
||||
assert_eval(expected, string, [], trim: true)
|
||||
end
|
||||
|
||||
test "trim mode with multiple lines" do
|
||||
string = """
|
||||
<%= "First line" %>
|
||||
<%= "Second line" %>
|
||||
<%= "Third line" %>
|
||||
<%= "Fourth line" %>
|
||||
"""
|
||||
|
||||
expected = "First lineSecond lineThird lineFourth line"
|
||||
assert_eval(expected, string, [], trim: true)
|
||||
end
|
||||
|
||||
test "embedded code" do
|
||||
assert_eval("foo bar", "foo <%= :bar %>")
|
||||
end
|
||||
@@ -100,6 +112,12 @@ defmodule EExTest do
|
||||
assert_eval("foo ", "foo <%= if false do %>bar<% end %>")
|
||||
end
|
||||
|
||||
test "embedded code with do preceeded by bracket" do
|
||||
assert_eval("foo bar", "foo <%= if {true}do %>bar<% end %>")
|
||||
assert_eval("foo bar", "foo <%= if (true)do %>bar<% end %>")
|
||||
assert_eval("foo bar", "foo <%= if [true]do %>bar<% end %>")
|
||||
end
|
||||
|
||||
test "embedded code with do end and expression" do
|
||||
assert_eval("foo bar", "foo <%= if true do %><%= :bar %><% end %>")
|
||||
end
|
||||
@@ -132,7 +150,27 @@ defmodule EExTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "embedded code with parentheses after end in end token" do
|
||||
test "embedded code with end followed by bracket" do
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= Enum.map([1, 2, 3], fn x -> %> <%= 100 + x %> <% end) %>"
|
||||
)
|
||||
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= apply Enum, :map, [[1, 2, 3], fn x -> %> <%= 100 + x %> <% end] %>"
|
||||
)
|
||||
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= #{__MODULE__}.tuple_map {[1, 2, 3], fn x -> %> <%= 100 + x %> <% end} %>"
|
||||
)
|
||||
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= apply(Enum, :map, [[1, 2, 3], fn x -> %> <%= 100 + x %> <% end]) %>"
|
||||
)
|
||||
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= Enum.map([1, 2, 3], (fn x -> %> <%= 100 + x %> <% end) ) %>"
|
||||
@@ -217,6 +255,20 @@ defmodule EExTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "error messages" do
|
||||
test "honor line numbers" do
|
||||
assert_raise EEx.SyntaxError, "nofile:99: missing token '%>'", fn ->
|
||||
EEx.compile_string("foo <%= bar", line: 99)
|
||||
end
|
||||
end
|
||||
|
||||
test "honor file names" do
|
||||
assert_raise EEx.SyntaxError, "my_file.eex:1: missing token '%>'", fn ->
|
||||
EEx.compile_string("foo <%= bar", file: "my_file.eex")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "environment" do
|
||||
test "respects line numbers" do
|
||||
expected = """
|
||||
@@ -342,6 +394,52 @@ defmodule EExTest do
|
||||
assert_eval(expected, string)
|
||||
end
|
||||
|
||||
test "inside multiple functions" do
|
||||
expected = """
|
||||
|
||||
A 1
|
||||
|
||||
B 2
|
||||
|
||||
A 3
|
||||
|
||||
"""
|
||||
|
||||
string = """
|
||||
<%= #{__MODULE__}.switching_map [1, 2, 3], fn x -> %>
|
||||
A <%= x %>
|
||||
<% end, fn x -> %>
|
||||
B <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
end
|
||||
|
||||
test "inside callback and do block" do
|
||||
expected = """
|
||||
|
||||
|
||||
A 1
|
||||
|
||||
B 2
|
||||
|
||||
A 3
|
||||
|
||||
"""
|
||||
|
||||
string = """
|
||||
<% require #{__MODULE__} %>
|
||||
<%= #{__MODULE__}.switching_macro [1, 2, 3], fn x -> %>
|
||||
A <%= x %>
|
||||
<% end do %>
|
||||
B <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
end
|
||||
|
||||
test "inside cond" do
|
||||
expected = """
|
||||
foo
|
||||
@@ -527,4 +625,27 @@ defmodule EExTest do
|
||||
defp assert_normalized_newline_equal(expected, actual) do
|
||||
assert String.replace(expected, "\r\n", "\n") == String.replace(actual, "\r\n", "\n")
|
||||
end
|
||||
|
||||
def tuple_map({list, callback}) do
|
||||
Enum.map(list, callback)
|
||||
end
|
||||
|
||||
def switching_map(list, a, b) do
|
||||
list
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn
|
||||
{element, index} when rem(index, 2) == 0 -> a.(element)
|
||||
{element, index} when rem(index, 2) == 1 -> b.(element)
|
||||
end)
|
||||
end
|
||||
|
||||
defmacro switching_macro(list, a, do: block) do
|
||||
quote do
|
||||
b = fn var!(x) ->
|
||||
unquote(block)
|
||||
end
|
||||
|
||||
unquote(__MODULE__).switching_map(unquote(list), unquote(a), b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+12
-8
@@ -18,6 +18,7 @@
|
||||
Float,
|
||||
Function,
|
||||
Integer,
|
||||
Module,
|
||||
NaiveDateTime,
|
||||
Record,
|
||||
Regex,
|
||||
@@ -25,7 +26,8 @@
|
||||
Time,
|
||||
Tuple,
|
||||
URI,
|
||||
Version
|
||||
Version,
|
||||
Version.Requirement
|
||||
],
|
||||
"Collections & Enumerables": [
|
||||
Access,
|
||||
@@ -57,16 +59,12 @@
|
||||
Calendar.TimeZoneDatabase,
|
||||
Calendar.UTCOnlyTimeZoneDatabase
|
||||
],
|
||||
"Modules & Code": [
|
||||
Code,
|
||||
Kernel.ParallelCompiler,
|
||||
Macro,
|
||||
Macro.Env,
|
||||
Module
|
||||
],
|
||||
"Processes & Applications": [
|
||||
Agent,
|
||||
Application,
|
||||
Config,
|
||||
Config.Provider,
|
||||
Config.Reader,
|
||||
DynamicSupervisor,
|
||||
GenServer,
|
||||
Node,
|
||||
@@ -86,6 +84,12 @@
|
||||
Protocol,
|
||||
String.Chars
|
||||
],
|
||||
"Code & Macros": [
|
||||
Code,
|
||||
Kernel.ParallelCompiler,
|
||||
Macro,
|
||||
Macro.Env
|
||||
],
|
||||
Deprecated: [
|
||||
Behaviour,
|
||||
Dict,
|
||||
|
||||
+51
-95
@@ -2,46 +2,12 @@ defmodule Access do
|
||||
@moduledoc """
|
||||
Key-based access to data structures.
|
||||
|
||||
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`).
|
||||
The `Access` module defines a behaviour for dynamically accessing
|
||||
keys of any type in a data structure via the `data[key]` syntax.
|
||||
|
||||
In the next section we will briefly recap the key-value constructs and then
|
||||
discuss the access mechanisms.
|
||||
|
||||
## Key-value constructs
|
||||
|
||||
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:
|
||||
`Access` supports keyword lists (`Keyword`) and maps (`Map`) out
|
||||
of the box. The key can be of any type and it returns `nil` if
|
||||
the key does not exist:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
@@ -59,62 +25,41 @@ defmodule Access do
|
||||
|
||||
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)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
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
|
||||
|
||||
This works because accessing anything on a `nil` value, returns
|
||||
`nil` itself:
|
||||
|
||||
iex> nil[:a]
|
||||
nil
|
||||
|
||||
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 can also be used with the `Kernel.put_in/2`,
|
||||
`Kernel.update_in/2` and `Kernel.get_and_update_in/2` macros
|
||||
to allow values to be set in nested data structures:
|
||||
|
||||
### Dot-based syntax
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"][:age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
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)
|
||||
|
||||
Instead we should use the `user.name` syntax to access fields:
|
||||
|
||||
user.name
|
||||
#=> "John"
|
||||
|
||||
Differently from `user[:name]`, `user.name` is not extensible via
|
||||
a behaviour and is restricted only to structs and atom keys in maps.
|
||||
|
||||
### Summing up
|
||||
|
||||
The bracket-based syntax, `user[:name]`, is used by dynamic structures,
|
||||
is extensible and returns nil 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.
|
||||
> Attention! While the access syntax is allowed in maps via
|
||||
> `map[key]`, if your map is made of predefined atom keys,
|
||||
> you should prefer to access those atom keys with `map.key`
|
||||
> instead of `map[key]`, as `map.key` will raise if the key
|
||||
> is missing. This is important because, if a map has a predefined
|
||||
> set of keys and a key is missing, it is most likely a bug
|
||||
> in your software or a typo on the key name. For this reason,
|
||||
> because structs are predefined in nature, they only allow
|
||||
> the `struct.key` syntax and they do not allow the `struct[key]`
|
||||
> access syntax. See the `Map` module for more information.
|
||||
|
||||
## Nested data structures
|
||||
|
||||
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`.
|
||||
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 example, to update a map inside another map:
|
||||
|
||||
@@ -126,9 +71,9 @@ defmodule Access do
|
||||
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:
|
||||
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},
|
||||
@@ -144,8 +89,8 @@ defmodule Access do
|
||||
]
|
||||
}
|
||||
|
||||
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for some of the
|
||||
available accessors.
|
||||
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for
|
||||
some of the available accessors.
|
||||
"""
|
||||
|
||||
@type container :: keyword | struct | map
|
||||
@@ -665,10 +610,16 @@ defmodule Access do
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> get_in(list, [Access.at(1), :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)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||
iex> get_and_update_in(list, [Access.at(-1), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"mary", [%{name: "john"}, %{name: "MARY"}]}
|
||||
|
||||
`at/1` can also be used to pop elements out of a list or
|
||||
a key inside of a list:
|
||||
@@ -689,19 +640,14 @@ defmodule Access do
|
||||
...> end)
|
||||
{nil, [%{name: "john"}, %{name: "mary"}]}
|
||||
|
||||
An error is raised for negative indexes:
|
||||
|
||||
iex> get_in([], [Access.at(-1)])
|
||||
** (FunctionClauseError) no function clause matching in Access.at/1
|
||||
|
||||
An error is raised if the accessed structure is not a list:
|
||||
|
||||
iex> get_in(%{}, [Access.at(1)])
|
||||
** (RuntimeError) Access.at/1 expected a list, got: %{}
|
||||
|
||||
"""
|
||||
@spec at(non_neg_integer) :: access_fun(data :: list, get_value :: term)
|
||||
def at(index) when is_integer(index) and index >= 0 do
|
||||
@spec at(integer) :: access_fun(data :: list, get_value :: term)
|
||||
def at(index) when is_integer(index) do
|
||||
fn op, data, next -> at(op, data, index, next) end
|
||||
end
|
||||
|
||||
@@ -724,7 +670,17 @@ defmodule Access do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_and_update_at([head | rest], index, next, updates) do
|
||||
defp get_and_update_at(list, index, next, updates) when index < 0 do
|
||||
list_length = length(list)
|
||||
|
||||
if list_length + index >= 0 do
|
||||
get_and_update_at(list, list_length + index, next, updates)
|
||||
else
|
||||
{nil, list}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_and_update_at([head | rest], index, next, updates) when index > 0 do
|
||||
get_and_update_at(rest, index - 1, next, [head | updates])
|
||||
end
|
||||
|
||||
|
||||
+12
-5
@@ -32,11 +32,19 @@ defmodule Agent do
|
||||
Usage would be:
|
||||
|
||||
Counter.start_link(0)
|
||||
#=> {:ok, #PID<0.123.0>}
|
||||
|
||||
Counter.value #=> 0
|
||||
Counter.increment #=> :ok
|
||||
Counter.increment #=> :ok
|
||||
Counter.value #=> 2
|
||||
Counter.value()
|
||||
#=> 0
|
||||
|
||||
Counter.increment()
|
||||
#=> :ok
|
||||
|
||||
Counter.increment()
|
||||
#=> :ok
|
||||
|
||||
Counter.value()
|
||||
#=> 2
|
||||
|
||||
Thanks to the agent server process, the counter can be safely incremented
|
||||
concurrently.
|
||||
@@ -115,7 +123,6 @@ defmodule Agent do
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `: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, either immediately or by giving it time to shut down
|
||||
|
||||
|
||||
@@ -198,8 +198,8 @@ defmodule Application do
|
||||
will shut down every application in the opposite order they had been started.
|
||||
|
||||
By default, a SIGTERM from the operating system will automatically translate to
|
||||
`System.stop/0`. You can also have more explicit control over OS signals via the
|
||||
`:os.set_signal/2` function.
|
||||
`System.stop/0`. You can also have more explicit control over operating system
|
||||
signals via the `:os.set_signal/2` function.
|
||||
|
||||
## Tooling
|
||||
|
||||
@@ -333,13 +333,6 @@ defmodule Application do
|
||||
end
|
||||
end
|
||||
|
||||
@type app :: atom
|
||||
@type key :: atom
|
||||
@type value :: term
|
||||
@type state :: term
|
||||
@type start_type :: :normal | {:takeover, node} | {:failover, node}
|
||||
@type restart_type :: :permanent | :transient | :temporary
|
||||
|
||||
@application_keys [
|
||||
:description,
|
||||
:id,
|
||||
@@ -354,6 +347,16 @@ defmodule Application do
|
||||
:start_phases
|
||||
]
|
||||
|
||||
application_key_specs = Enum.reduce(@application_keys, &{:|, [], [&1, &2]})
|
||||
|
||||
@type app :: atom
|
||||
@type key :: atom
|
||||
@type application_key :: unquote(application_key_specs)
|
||||
@type value :: term
|
||||
@type state :: term
|
||||
@type start_type :: :normal | {:takeover, node} | {:failover, node}
|
||||
@type restart_type :: :permanent | :transient | :temporary
|
||||
|
||||
@doc """
|
||||
Returns the spec for `app`.
|
||||
|
||||
@@ -364,7 +367,7 @@ defmodule Application do
|
||||
Note the environment is not returned as it can be accessed via
|
||||
`fetch_env/2`. Returns `nil` if the application is not loaded.
|
||||
"""
|
||||
@spec spec(app) :: [{key, value}] | nil
|
||||
@spec spec(app) :: [{application_key, value}] | nil
|
||||
def spec(app) when is_atom(app) do
|
||||
case :application.get_all_key(app) do
|
||||
{:ok, info} -> :lists.keydelete(:env, 1, info)
|
||||
@@ -379,7 +382,7 @@ defmodule Application do
|
||||
specification parameter does not exist, this function
|
||||
will raise. Returns `nil` if the application is not loaded.
|
||||
"""
|
||||
@spec spec(app, key) :: value | nil
|
||||
@spec spec(app, application_key) :: value | nil
|
||||
def spec(app, key) when is_atom(app) and key in @application_keys do
|
||||
case :application.get_key(app, key) do
|
||||
{:ok, value} -> value
|
||||
@@ -523,10 +526,41 @@ defmodule Application do
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the environment for multiple apps at the same time.
|
||||
|
||||
The given config should not:
|
||||
|
||||
* have the same application listed more than once
|
||||
* have the same key inside the same application listed more than once
|
||||
|
||||
If those conditions are not met, the behaviour is undefined
|
||||
(on Erlang/OTP 21 and earlier) or will raise (on Erlang/OTP 22
|
||||
and later).
|
||||
|
||||
It receives the same options as `put_env/4`. Returns `:ok`.
|
||||
"""
|
||||
@spec put_all_env([{app, [{key, value}]}], timeout: timeout, persistent: boolean) :: :ok
|
||||
def put_all_env(config, opts \\ []) when is_list(config) and is_list(opts) do
|
||||
# TODO: Remove function exported? check when we require Erlang/OTP 22+
|
||||
if function_exported?(:application, :set_env, 2) do
|
||||
:application.set_env(config, opts)
|
||||
else
|
||||
for app_keyword <- config,
|
||||
{app, keyword} = app_keyword,
|
||||
key_value <- keyword,
|
||||
{key, value} = key_value do
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the `key` from the given `app` environment.
|
||||
|
||||
See `put_env/4` for a description of the options.
|
||||
It receives the same options as `put_env/4`. Returns `:ok`.
|
||||
"""
|
||||
@spec delete_env(app, key, timeout: timeout, persistent: boolean) :: :ok
|
||||
def delete_env(app, key, opts \\ []) when is_atom(app) do
|
||||
|
||||
@@ -37,7 +37,6 @@ defmodule Atom do
|
||||
:erlang.atom_to_list(atom)
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Atom.to_charlist/1 instead"
|
||||
@spec to_char_list(atom) :: charlist
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Behaviour do
|
||||
do_defcallback(:defmacro, split_spec(spec, quote(do: Macro.t())))
|
||||
end
|
||||
|
||||
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
|
||||
defp split_spec({:when, _, [{:"::", _, [spec, return]}, guard]}, _default) do
|
||||
{spec, return, guard}
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule Behaviour do
|
||||
{spec, default, guard}
|
||||
end
|
||||
|
||||
defp split_spec({:::, _, [spec, return]}, _default) do
|
||||
defp split_spec({:"::", _, [spec, return]}, _default) do
|
||||
{spec, return, []}
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ defmodule Behaviour do
|
||||
|
||||
defp do_callback(kind, name, args, return, guards) do
|
||||
fun = fn
|
||||
{:::, _, [left, right]} ->
|
||||
{:"::", _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
|
||||
@@ -343,7 +343,7 @@ defmodule Date do
|
||||
|
||||
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)
|
||||
Calendar.ISO.date_to_string(year, month, day, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = date, format) when format in [:basic, :extended] do
|
||||
@@ -745,10 +745,11 @@ defmodule Date do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Date.day_of_era(~D[0001-01-01])
|
||||
{1, 1}
|
||||
iex> Date.day_of_era(~D[0000-12-31])
|
||||
{1, 0}
|
||||
iex> Date.day_of_era(~D[0001-01-01])
|
||||
{1, 1}
|
||||
|
||||
iex> Date.day_of_era(~D[0000-12-31])
|
||||
{1, 0}
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
|
||||
@@ -97,17 +97,17 @@ defmodule DateTime do
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1_464_096_368)
|
||||
iex> datetime
|
||||
#DateTime<2016-05-24 13:26:08Z>
|
||||
~U[2016-05-24 13:26:08Z]
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_unix(1_432_560_368_868_569, :microsecond)
|
||||
iex> datetime
|
||||
#DateTime<2015-05-25 13:26:08.868569Z>
|
||||
~U[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(143_256_036_886_856, 1024)
|
||||
iex> datetime
|
||||
#DateTime<6403-03-17 07:05:22.320Z>
|
||||
~U[6403-03-17 07:05:22.320312Z]
|
||||
|
||||
Negative Unix times are supported, up to -62167219200 seconds,
|
||||
which is equivalent to "0000-01-01T00:00:00Z" or 0 Gregorian seconds.
|
||||
@@ -152,17 +152,20 @@ defmodule DateTime do
|
||||
|
||||
# An easy way to get the Unix epoch is passing 0 to this function
|
||||
iex> DateTime.from_unix!(0)
|
||||
#DateTime<1970-01-01 00:00:00Z>
|
||||
~U[1970-01-01 00:00:00Z]
|
||||
|
||||
iex> DateTime.from_unix!(1_464_096_368)
|
||||
#DateTime<2016-05-24 13:26:08Z>
|
||||
~U[2016-05-24 13:26:08Z]
|
||||
|
||||
iex> DateTime.from_unix!(1_432_560_368_868_569, :microsecond)
|
||||
#DateTime<2015-05-25 13:26:08.868569Z>
|
||||
~U[2015-05-25 13:26:08.868569Z]
|
||||
|
||||
iex> DateTime.from_unix!(143_256_036_886_856, 1024)
|
||||
~U[6403-03-17 07:05:22.320312Z]
|
||||
|
||||
"""
|
||||
@spec from_unix!(integer, :native | System.time_unit(), Calendar.calendar()) :: t
|
||||
def from_unix!(integer, unit \\ :second, calendar \\ Calendar.ISO) when is_atom(unit) do
|
||||
def from_unix!(integer, unit \\ :second, calendar \\ Calendar.ISO) do
|
||||
case from_unix(integer, unit, calendar) do
|
||||
{:ok, datetime} ->
|
||||
datetime
|
||||
@@ -183,9 +186,8 @@ defmodule DateTime do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, datetime} = DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Etc/UTC")
|
||||
iex> datetime
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
iex> DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Etc/UTC")
|
||||
{:ok, ~U[2016-05-24 13:26:08.003Z]}
|
||||
|
||||
When the datetime is ambiguous - for instance during changing from summer
|
||||
to winter time - the two possible valid datetimes are returned. First the one
|
||||
@@ -227,7 +229,7 @@ defmodule DateTime do
|
||||
iex> cph_datetime = DateTime.from_naive!(~N[2018-08-24 10:00:00], "Europe/Copenhagen", FakeTimeZoneDatabase)
|
||||
iex> {:ok, utc_datetime} = DateTime.from_naive(cph_datetime, "Etc/UTC", FakeTimeZoneDatabase)
|
||||
iex> utc_datetime
|
||||
#DateTime<2018-08-24 10:00:00Z>
|
||||
~U[2018-08-24 10:00:00Z]
|
||||
|
||||
If instead you want a `DateTime` for the same point time in a different time zone see the
|
||||
`DateTime.shift_zone/3` function which would convert 2018-08-24 10:00:00 in Copenhagen
|
||||
@@ -356,7 +358,7 @@ defmodule DateTime do
|
||||
## Examples
|
||||
|
||||
iex> DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC")
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
~U[2016-05-24 13:26:08.003Z]
|
||||
|
||||
iex> DateTime.from_naive!(~N[2018-05-24 13:26:08.003], "Europe/Copenhagen", FakeTimeZoneDatabase)
|
||||
#DateTime<2018-05-24 13:26:08.003+02:00 CEST Europe/Copenhagen>
|
||||
@@ -480,9 +482,11 @@ defmodule DateTime do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, datetime} = DateTime.now("Europe/Copenhagen", FakeTimeZoneDatabase)
|
||||
iex> {:ok, datetime} = DateTime.now("Etc/UTC")
|
||||
iex> datetime.time_zone
|
||||
"Europe/Copenhagen"
|
||||
"Etc/UTC"
|
||||
iex> DateTime.now("Europe/Copenhagen")
|
||||
{:error, :utc_only_time_zone_database}
|
||||
iex> DateTime.now("not a real time zone name", FakeTimeZoneDatabase)
|
||||
{:error, :time_zone_not_found}
|
||||
|
||||
@@ -553,18 +557,17 @@ defmodule DateTime do
|
||||
|
||||
"""
|
||||
@spec to_naive(Calendar.datetime()) :: NaiveDateTime.t()
|
||||
def to_naive(datetime) do
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = datetime
|
||||
|
||||
def to_naive(%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: _
|
||||
}) do
|
||||
%NaiveDateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
@@ -593,8 +596,17 @@ defmodule DateTime do
|
||||
|
||||
"""
|
||||
@spec to_date(Calendar.datetime()) :: Date.t()
|
||||
def to_date(datetime) do
|
||||
%{year: year, month: month, day: day, calendar: calendar} = datetime
|
||||
def to_date(%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
calendar: calendar,
|
||||
hour: _,
|
||||
minute: _,
|
||||
second: _,
|
||||
microsecond: _,
|
||||
time_zone: _
|
||||
}) do
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
|
||||
@@ -614,10 +626,17 @@ defmodule DateTime do
|
||||
|
||||
"""
|
||||
@spec to_time(Calendar.datetime()) :: Time.t()
|
||||
def to_time(datetime) do
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar} =
|
||||
datetime
|
||||
|
||||
def to_time(%{
|
||||
year: _,
|
||||
month: _,
|
||||
day: _,
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: _
|
||||
}) do
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
@@ -730,23 +749,23 @@ defmodule DateTime do
|
||||
|
||||
iex> {:ok, datetime, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
|
||||
iex> datetime
|
||||
#DateTime<2015-01-23 23:50:07Z>
|
||||
~U[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>
|
||||
~U[2015-01-23 21:20:07.123Z]
|
||||
|
||||
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>
|
||||
~U[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>
|
||||
~U[-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>
|
||||
~U[-2015-01-23 21:20:07.123Z]
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
|
||||
{:error, :invalid_format}
|
||||
@@ -1019,9 +1038,8 @@ defmodule DateTime do
|
||||
iex> dt |> DateTime.add(3600, :second, FakeTimeZoneDatabase)
|
||||
#DateTime<2018-11-15 11:00:00+01:00 CET Europe/Copenhagen>
|
||||
|
||||
iex> dt = DateTime.from_naive!(~N[2018-11-15 10:00:00], "Etc/UTC")
|
||||
iex> dt |> DateTime.add(3600, :second)
|
||||
#DateTime<2018-11-15 11:00:00Z>
|
||||
iex> DateTime.add(~U[2018-11-15 10:00:00Z], 3600, :second)
|
||||
~U[2018-11-15 11:00:00Z]
|
||||
|
||||
When adding 3 seconds just before "spring forward" we go from 1:59:59 to 3:00:02
|
||||
|
||||
@@ -1301,7 +1319,7 @@ defmodule DateTime do
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
|
||||
"#DateTime<" <>
|
||||
formatted =
|
||||
Calendar.ISO.datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
@@ -1314,7 +1332,15 @@ defmodule DateTime do
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
) <> ">"
|
||||
)
|
||||
|
||||
case datetime do
|
||||
%{utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"} ->
|
||||
"~U[" <> formatted <> "]"
|
||||
|
||||
_ ->
|
||||
"#DateTime<" <> formatted <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(datetime, opts) do
|
||||
|
||||
@@ -213,7 +213,7 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
def date_to_iso_days(year, month, day) when year in -9999..9999 do
|
||||
true = day <= days_in_month(year, month)
|
||||
ensure_day_in_month!(year, month, day)
|
||||
|
||||
days_in_previous_years(year) + days_before_month(month) + leap_day_offset(year, month) + day -
|
||||
1
|
||||
@@ -260,6 +260,7 @@ defmodule Calendar.ISO do
|
||||
31
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec days_in_month(year, month) :: 28..31
|
||||
@impl true
|
||||
def days_in_month(year, month)
|
||||
@@ -304,6 +305,7 @@ defmodule Calendar.ISO do
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.3.0"
|
||||
@spec leap_year?(year) :: boolean()
|
||||
@impl true
|
||||
def leap_year?(year) when is_integer(year) do
|
||||
@@ -335,6 +337,7 @@ defmodule Calendar.ISO do
|
||||
4
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec day_of_week(year, month, day) :: 1..7
|
||||
@impl true
|
||||
def day_of_week(year, month, day)
|
||||
@@ -366,7 +369,7 @@ defmodule Calendar.ISO do
|
||||
@impl true
|
||||
def day_of_year(year, month, day)
|
||||
when is_integer(year) and is_integer(month) and is_integer(day) do
|
||||
true = day <= days_in_month(year, month)
|
||||
ensure_day_in_month!(year, month, day)
|
||||
days_before_month(month) + leap_day_offset(year, month) + day
|
||||
end
|
||||
|
||||
@@ -461,6 +464,10 @@ defmodule Calendar.ISO do
|
||||
@doc """
|
||||
Converts the given time into a string.
|
||||
|
||||
By default, returns times formatted in the "extended" format,
|
||||
for human readability. It also supports the "basic" format
|
||||
by passing the `:basic` option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6})
|
||||
@@ -470,23 +477,29 @@ defmodule Calendar.ISO do
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 0})
|
||||
"02:02:02"
|
||||
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6}, :basic)
|
||||
"020202.000002"
|
||||
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6}, :extended)
|
||||
"02:02:02.000002"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@doc since: "1.5.0"
|
||||
@spec time_to_string(
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
Calendar.microsecond(),
|
||||
:basic | :extended
|
||||
) :: String.t()
|
||||
@impl true
|
||||
def time_to_string(hour, minute, second, microsecond) do
|
||||
time_to_string(hour, minute, second, microsecond, :extended)
|
||||
end
|
||||
def time_to_string(hour, minute, second, microsecond, format \\ :extended)
|
||||
|
||||
def time_to_string(hour, minute, second, {_, 0}, format) do
|
||||
def time_to_string(hour, minute, second, {_, 0}, format) when format in [:basic, :extended] do
|
||||
time_to_string_format(hour, minute, second, format)
|
||||
end
|
||||
|
||||
def time_to_string(hour, minute, second, {microsecond, precision}, format) do
|
||||
def time_to_string(hour, minute, second, {microsecond, precision}, format)
|
||||
when format in [:basic, :extended] do
|
||||
time_to_string_format(hour, minute, second, format) <>
|
||||
"." <> (microsecond |> zero_pad(6) |> binary_part(0, precision))
|
||||
end
|
||||
@@ -502,6 +515,10 @@ defmodule Calendar.ISO do
|
||||
@doc """
|
||||
Converts the given date into a string.
|
||||
|
||||
By default, returns dates formatted in the "extended" format,
|
||||
for human readability. It also supports the "basic" format
|
||||
by passing the `:basic` option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
@@ -511,24 +528,32 @@ defmodule Calendar.ISO do
|
||||
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)
|
||||
end
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28, :basic)
|
||||
"20150228"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31, :basic)
|
||||
"-00990131"
|
||||
|
||||
defp date_to_string(year, month, day, :extended) do
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec date_to_string(year, month, day, :basic | :extended) :: String.t()
|
||||
@impl true
|
||||
def date_to_string(year, month, day, format \\ :extended)
|
||||
|
||||
def date_to_string(year, month, day, :extended) do
|
||||
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
|
||||
end
|
||||
|
||||
defp date_to_string(year, month, day, :basic) do
|
||||
def date_to_string(year, month, day, :basic) do
|
||||
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the datetime (without time zone) into a string.
|
||||
|
||||
By default, returns datetimes formatted in the "extended" format,
|
||||
for human readability. It also supports the "basic" format
|
||||
by passing the `:basic` option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
@@ -536,7 +561,11 @@ defmodule Calendar.ISO do
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6}, :basic)
|
||||
"20150228 010203.000004"
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@impl true
|
||||
@spec naive_datetime_to_string(
|
||||
year,
|
||||
@@ -545,15 +574,31 @@ defmodule Calendar.ISO do
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
Calendar.microsecond(),
|
||||
:basic | :extended
|
||||
) :: 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)
|
||||
def naive_datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format \\ :extended
|
||||
)
|
||||
when format in [:basic, :extended] do
|
||||
date_to_string(year, month, day, format) <>
|
||||
" " <> time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the datetime (with time zone) into a string.
|
||||
|
||||
By default, returns datetimes formatted in the "extended" format,
|
||||
for human readability. It also supports the "basic" format
|
||||
by passing the `:basic` option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> time_zone = "Europe/Berlin"
|
||||
@@ -568,7 +613,12 @@ defmodule Calendar.ISO do
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, time_zone, "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
|
||||
iex> time_zone = "Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "CET", 3600, 0, :basic)
|
||||
"20170801 010203.00000+0100 CET Europe/Berlin"
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@impl true
|
||||
@spec datetime_to_string(
|
||||
year,
|
||||
@@ -581,7 +631,8 @@ defmodule Calendar.ISO do
|
||||
Calendar.time_zone(),
|
||||
Calendar.zone_abbr(),
|
||||
Calendar.utc_offset(),
|
||||
Calendar.std_offset()
|
||||
Calendar.std_offset(),
|
||||
:basic | :extended
|
||||
) :: String.t()
|
||||
def datetime_to_string(
|
||||
year,
|
||||
@@ -594,12 +645,14 @@ defmodule Calendar.ISO do
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
) do
|
||||
date_to_string(year, month, day) <>
|
||||
std_offset,
|
||||
format \\ :extended
|
||||
)
|
||||
when format in [:basic, :extended] do
|
||||
date_to_string(year, month, day, format) <>
|
||||
" " <>
|
||||
time_to_string(hour, minute, second, microsecond) <>
|
||||
offset_to_string(utc_offset, std_offset, time_zone) <>
|
||||
time_to_string(hour, minute, second, microsecond, format) <>
|
||||
offset_to_string(utc_offset, std_offset, time_zone, format) <>
|
||||
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
|
||||
end
|
||||
|
||||
@@ -662,7 +715,6 @@ defmodule Calendar.ISO do
|
||||
{0, 1}
|
||||
end
|
||||
|
||||
defp offset_to_string(utc, std, zone, format \\ :extended)
|
||||
defp offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
|
||||
|
||||
defp offset_to_string(utc, std, _zone, format) do
|
||||
@@ -714,24 +766,15 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
defp precision_for_unit(unit) do
|
||||
subsecond = div(System.convert_time_unit(1, :second, unit), 10)
|
||||
precision_for_unit(subsecond, 0)
|
||||
end
|
||||
|
||||
defp precision_for_unit(0, precision), do: precision
|
||||
defp precision_for_unit(_, 6), do: 6
|
||||
|
||||
defp precision_for_unit(number, precision),
|
||||
do: precision_for_unit(div(number, 10), precision + 1)
|
||||
|
||||
@doc false
|
||||
def date_to_iso8601(year, month, day, format \\ :extended) do
|
||||
date_to_string(year, month, day, format)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def time_to_iso8601(hour, minute, second, microsecond, format \\ :extended) do
|
||||
time_to_string(hour, minute, second, microsecond, format)
|
||||
case System.convert_time_unit(1, :second, unit) do
|
||||
1 -> 0
|
||||
10 -> 1
|
||||
100 -> 2
|
||||
1_000 -> 3
|
||||
10_000 -> 4
|
||||
100_000 -> 5
|
||||
_ -> 6
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -993,4 +1036,10 @@ defmodule Calendar.ISO do
|
||||
|
||||
{hour, minute, second}
|
||||
end
|
||||
|
||||
defp ensure_day_in_month!(year, month, day) do
|
||||
if day < 1 or day > days_in_month(year, month) do
|
||||
raise ArgumentError, "invalid date: #{date_to_string(year, month, day)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -258,7 +258,7 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.add(~N[0000-01-01 00:00:00], 63_579_428_950)
|
||||
~N[2014-10-02 00:29:10]
|
||||
|
||||
Passing a `Datetime` automatically converts it to `NaiveDateTime`,
|
||||
Passing a `DateTime` automatically converts it to `NaiveDateTime`,
|
||||
discarding the time zone information:
|
||||
|
||||
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
|
||||
@@ -392,10 +392,6 @@ defmodule NaiveDateTime do
|
||||
|
||||
"""
|
||||
@spec to_date(Calendar.naive_datetime()) :: Date.t()
|
||||
def to_date(%NaiveDateTime{year: year, month: month, day: day, calendar: calendar}) do
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
|
||||
def to_date(%{
|
||||
year: year,
|
||||
month: month,
|
||||
@@ -422,24 +418,6 @@ defmodule NaiveDateTime do
|
||||
|
||||
"""
|
||||
@spec to_time(Calendar.naive_datetime()) :: Time.t()
|
||||
def to_time(%NaiveDateTime{} = naive_datetime) do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
} = naive_datetime
|
||||
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
}
|
||||
end
|
||||
|
||||
def to_time(%{
|
||||
year: _,
|
||||
month: _,
|
||||
|
||||
@@ -301,7 +301,7 @@ defmodule Time do
|
||||
microsecond: microsecond
|
||||
} = time
|
||||
|
||||
Calendar.ISO.time_to_iso8601(hour, minute, second, microsecond, format)
|
||||
Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = time, format) when format in [:extended, :basic] do
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule Calendar.TimeZoneDatabase do
|
||||
|
||||
A beginning is inclusive. An ending is exclusive. Eg. if a period is from
|
||||
2015-03-29 01:00:00 and until 2015-10-25 01:00:00, the period includes and
|
||||
begins from the begining of 2015-03-29 01:00:00 and lasts until just before
|
||||
begins from the beginning of 2015-03-29 01:00:00 and lasts until just before
|
||||
2015-10-25 01:00:00.
|
||||
|
||||
A beginning or end for certain periods are infinite. For instance the latest
|
||||
|
||||
+82
-36
@@ -30,6 +30,14 @@ defmodule Code do
|
||||
the result of evaluating the file rather than the modules it defines.
|
||||
"""
|
||||
|
||||
@available_compiler_options [
|
||||
:docs,
|
||||
:debug_info,
|
||||
:ignore_module_conflict,
|
||||
:relative_paths,
|
||||
:warnings_as_errors
|
||||
]
|
||||
|
||||
@doc """
|
||||
Lists all required files.
|
||||
|
||||
@@ -46,7 +54,7 @@ defmodule Code do
|
||||
:elixir_code_server.call(:required)
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on 1.9
|
||||
# TODO: Deprecate on v1.9
|
||||
@doc false
|
||||
def loaded_files do
|
||||
required_files()
|
||||
@@ -78,7 +86,7 @@ defmodule Code do
|
||||
:elixir_code_server.cast({:unrequire_files, files})
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on 1.9
|
||||
# TODO: Deprecate on v1.9
|
||||
@doc false
|
||||
def unload_files(files) do
|
||||
unrequire_files(files)
|
||||
@@ -264,6 +272,13 @@ defmodule Code do
|
||||
expects a valid `Version` which is usually the minimum Elixir
|
||||
version supported by the project.
|
||||
|
||||
* `:force_do_end_blocks` (since v1.9.0) - when `true`, converts all
|
||||
inline usages of `do: ...`, `else: ...` and friends into `do/end`
|
||||
blocks. Defaults to `false`. Notice this option is convergent:
|
||||
once you set it to `true`, all keywords will be converted. If you
|
||||
set it to `false` later on, `do/end` blocks won't be converted
|
||||
back to keywords.
|
||||
|
||||
## Design principles
|
||||
|
||||
The formatter was designed under three principles.
|
||||
@@ -409,8 +424,8 @@ defmodule Code do
|
||||
broken into multiple lines if they are followed by a newline in the
|
||||
opening bracket and preceded by a new line in the closing bracket
|
||||
|
||||
* Pipeline operators, like `|>` and others with the same precedence,
|
||||
will span multiple lines if they spanned multiple lines in the input
|
||||
* Newlines before certain operators (such as the pipeline operators)
|
||||
and before other operators (such as comparison operators)
|
||||
|
||||
The behaviours above are not guaranteed. We may remove or add new
|
||||
rules in the future. The goal of documenting them is to provide better
|
||||
@@ -650,6 +665,12 @@ defmodule Code do
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
Defaults to `false`.
|
||||
|
||||
* `:static_atom_encoder` - The static atom encoder function, see
|
||||
"The `:static_atom_encoder` function" section below. This option
|
||||
overrides the `:existing_atoms_only` behaviour for static atoms
|
||||
but `:existing_atoms_only` is still used for dynamic atoms, such
|
||||
as atoms with interpolations.
|
||||
|
||||
* `:warn_on_unnecessary_quotes` - when `false`, does not warn
|
||||
when atoms, keywords or calls have unnecessary quotes on
|
||||
them. Defaults to `true`.
|
||||
@@ -659,6 +680,37 @@ defmodule Code do
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
|
||||
## The `:static_atom_encoder` function
|
||||
|
||||
When `static_atom_encoder: &my_encoder/2` is passed as an argument,
|
||||
`my_encoder/2` is called every time the tokenizer needs to create a
|
||||
"static" atom. Static atoms are atoms in the AST that function as
|
||||
aliases, remote calls, local calls, variable names, regular atoms
|
||||
and keyword lists.
|
||||
|
||||
The encoder function will receive the atom name (as a binary) and a
|
||||
keyword list with the current file, line and column. It must return
|
||||
`{:ok, token :: term} | {:error, reason :: binary}`.
|
||||
|
||||
The encoder function is supposed to create an atom from the given
|
||||
string. It is required to return either `{:ok, term}`, where term is
|
||||
an atom. It is possible to return something else than an atom,
|
||||
however, in that case the AST is no longer "valid" in that it cannot
|
||||
be used to compile or evaluate Elixir code. A use case for this is
|
||||
if you want to use the Elixir parser in a user-facing situation, but
|
||||
you don't want to exhaust the atom table.
|
||||
|
||||
The atom encoder is not called for *all* atoms that are present in
|
||||
the AST. It won't be invoked for the following atoms:
|
||||
|
||||
* operators (`:+`, `:-`, and so on)
|
||||
|
||||
* syntax keywords (`fn`, `do`, `else`, and so on)
|
||||
|
||||
* atoms containing interpolation (`:"\#{1 + 1} is two"`), as these
|
||||
atoms are constructed at runtime.
|
||||
|
||||
"""
|
||||
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
||||
{:ok, Macro.t()} | {:error, {line :: pos_integer, term, term}}
|
||||
@@ -707,12 +759,12 @@ defmodule Code do
|
||||
eval_string(File.read!(file), [], file: file, line: 1)
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on 1.9
|
||||
# TODO: Deprecate on v1.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)
|
||||
loaded = :elixir_compiler.file(file, fn _, _ -> :ok end)
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
end
|
||||
@@ -753,18 +805,12 @@ defmodule Code do
|
||||
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
|
||||
:required ->
|
||||
nil
|
||||
|
||||
{:queued, ref} ->
|
||||
receive do
|
||||
{:elixir_code_server, ^ref, :required} -> nil
|
||||
end
|
||||
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file(file)
|
||||
loaded = :elixir_compiler.file(file, fn _, _ -> :ok end)
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
end
|
||||
@@ -778,8 +824,7 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
Code.compiler_options()
|
||||
#=> %{debug_info: true, docs: true,
|
||||
#=> warnings_as_errors: false, ignore_module_conflict: false}
|
||||
#=> %{debug_info: true, docs: true, ...}
|
||||
|
||||
"""
|
||||
@spec compiler_options() :: %{optional(atom) => boolean}
|
||||
@@ -794,13 +839,13 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.available_compiler_options()
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
Code.available_compiler_options()
|
||||
#=> [:docs, :debug_info, ...]
|
||||
|
||||
"""
|
||||
@spec available_compiler_options() :: [atom]
|
||||
def available_compiler_options do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
@available_compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -859,19 +904,14 @@ defmodule Code do
|
||||
"""
|
||||
@spec compiler_options(Enumerable.t()) :: %{optional(atom) => boolean}
|
||||
def compiler_options(opts) do
|
||||
available = available_compiler_options()
|
||||
|
||||
Enum.each(opts, fn {key, value} ->
|
||||
cond do
|
||||
key not in available ->
|
||||
raise "unknown compiler option: #{inspect(key)}"
|
||||
|
||||
not is_boolean(value) ->
|
||||
Enum.each(opts, fn
|
||||
{key, value} when key in @available_compiler_options ->
|
||||
if not is_boolean(value) do
|
||||
raise "compiler option #{inspect(key)} should be a boolean, got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
true ->
|
||||
:ok
|
||||
end
|
||||
{key, _} ->
|
||||
raise "unknown compiler option: #{inspect(key)}"
|
||||
end)
|
||||
|
||||
:elixir_config.update(:compiler_options, &Enum.into(opts, &1))
|
||||
@@ -893,7 +933,7 @@ defmodule Code do
|
||||
"""
|
||||
@spec compile_string(List.Chars.t(), binary) :: [{module, binary}]
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string(to_charlist(string), file)
|
||||
:elixir_compiler.string(to_charlist(string), file, fn _, _ -> :ok end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -906,7 +946,7 @@ defmodule Code do
|
||||
"""
|
||||
@spec compile_quoted(Macro.t(), binary) :: [{module, binary}]
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted(quoted, file)
|
||||
:elixir_compiler.quoted(quoted, file, fn _, _ -> :ok end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -923,9 +963,10 @@ defmodule Code do
|
||||
|
||||
For compiling many files concurrently, see `Kernel.ParallelCompiler.compile/2`.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@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))
|
||||
:elixir_compiler.file(find_file(file, relative_to), fn _, _ -> :ok end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1014,6 +1055,9 @@ defmodule Code do
|
||||
If it succeeds in loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
If the module being checked is currently in a compiler deadlock,
|
||||
this functions returns `{:error, :nofile}`.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
"""
|
||||
@@ -1022,9 +1066,11 @@ defmodule Code do
|
||||
def ensure_compiled(module) when is_atom(module) do
|
||||
case :code.ensure_loaded(module) do
|
||||
{:error, :nofile} = error ->
|
||||
if is_pid(:erlang.get(:elixir_compiler_pid)) and
|
||||
Kernel.ErrorHandler.ensure_compiled(module, :module) do
|
||||
{:module, module}
|
||||
if is_pid(:erlang.get(:elixir_compiler_pid)) do
|
||||
case Kernel.ErrorHandler.ensure_compiled(module, :module, :soft) do
|
||||
:found -> {:module, module}
|
||||
:not_found -> error
|
||||
end
|
||||
else
|
||||
error
|
||||
end
|
||||
@@ -1077,7 +1123,7 @@ defmodule Code do
|
||||
| {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary}}
|
||||
when annotation: :erl_anno.anno(),
|
||||
beam_language: :elixir | :erlang | :lfe | :alpaca | atom(),
|
||||
doc_content: %{binary => binary} | :none | :hidden,
|
||||
doc_content: %{required(binary) => binary} | :none | :hidden,
|
||||
doc_element:
|
||||
{{kind :: atom, function_name :: atom, arity}, annotation, signature, doc_content,
|
||||
metadata},
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Code.Formatter do
|
||||
@required_parens_logical_binary_operands [:||, :|||, :or, :&&, :&&&, :and]
|
||||
|
||||
# Operators with next break fits. = and :: do not consider new lines though
|
||||
@next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :::]
|
||||
@next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :"::"]
|
||||
|
||||
# Operators that always require parens on operands when they are the parent
|
||||
@required_parens_on_binary_operands [
|
||||
@@ -49,7 +49,7 @@ defmodule Code.Formatter do
|
||||
:<>
|
||||
]
|
||||
|
||||
locals_without_parens = [
|
||||
@locals_without_parens [
|
||||
# Special forms
|
||||
alias: 1,
|
||||
alias: 2,
|
||||
@@ -77,6 +77,7 @@ defmodule Code.Formatter do
|
||||
defmacro: 2,
|
||||
defmacrop: 1,
|
||||
defmacrop: 2,
|
||||
defmodule: 2,
|
||||
defdelegate: 2,
|
||||
defexception: 1,
|
||||
defoverridable: 1,
|
||||
@@ -98,7 +99,6 @@ defmodule Code.Formatter do
|
||||
defrecordp: 3,
|
||||
|
||||
# Testing
|
||||
all: :*,
|
||||
assert: 1,
|
||||
assert: 2,
|
||||
assert_in_delta: 3,
|
||||
@@ -110,12 +110,8 @@ defmodule Code.Formatter do
|
||||
assert_receive: 3,
|
||||
assert_received: 1,
|
||||
assert_received: 2,
|
||||
check: 1,
|
||||
check: 2,
|
||||
doctest: 1,
|
||||
doctest: 2,
|
||||
property: 1,
|
||||
property: 2,
|
||||
refute: 1,
|
||||
refute: 2,
|
||||
refute_in_delta: 3,
|
||||
@@ -138,7 +134,7 @@ defmodule Code.Formatter do
|
||||
import_config: 1
|
||||
]
|
||||
|
||||
@locals_without_parens MapSet.new(locals_without_parens)
|
||||
@do_end_keywords [:rescue, :catch, :else, :after]
|
||||
|
||||
@doc """
|
||||
Checks if two strings are equivalent.
|
||||
@@ -242,6 +238,8 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp state(comments, opts) do
|
||||
force_do_end_blocks = Keyword.get(opts, :force_do_end_blocks, false)
|
||||
|
||||
rename_deprecated_at =
|
||||
if version = opts[:rename_deprecated_at] do
|
||||
case Version.parse(version) do
|
||||
@@ -255,13 +253,10 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
locals_without_parens =
|
||||
opts
|
||||
|> Keyword.get(:locals_without_parens, [])
|
||||
|> MapSet.new()
|
||||
|> MapSet.union(@locals_without_parens)
|
||||
|> MapSet.to_list()
|
||||
Keyword.get(opts, :locals_without_parens, []) ++ @locals_without_parens
|
||||
|
||||
%{
|
||||
force_do_end_blocks: force_do_end_blocks,
|
||||
locals_without_parens: locals_without_parens,
|
||||
operand_nesting: 2,
|
||||
rename_deprecated_at: rename_deprecated_at,
|
||||
@@ -796,7 +791,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
# TODO: We can remove this workaround once we remove
|
||||
# ?rearrange_uop from the parser in Elixir v2.0.
|
||||
# ?rearrange_uop from the parser on v2.0.
|
||||
# (! left) in right
|
||||
# (not left) in right
|
||||
defp binary_operand_to_algebra(
|
||||
@@ -1058,7 +1053,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
# We can only rename functions in the same module because
|
||||
# introducing a new module may wrong due to aliases.
|
||||
# introducing a new module may be wrong due to aliases.
|
||||
defp deprecated(Enum, :partition, 2), do: {"split_with", "~> 1.4"}
|
||||
defp deprecated(Code, :unload_files, 2), do: {"unrequire_files", "~> 1.7"}
|
||||
defp deprecated(Code, :loaded_files, 2), do: {"required_files", "~> 1.7"}
|
||||
@@ -1080,7 +1075,7 @@ defmodule Code.Formatter do
|
||||
defp local_to_algebra(fun, meta, args, context, state) when is_atom(fun) do
|
||||
skip_parens =
|
||||
cond do
|
||||
not Keyword.get(meta, :no_parens, false) -> :required
|
||||
not Keyword.get(meta, :no_parens, false) -> :skip_if_only_do_end
|
||||
local_without_parens?(fun, args, state) -> :skip_unless_many_args
|
||||
true -> :skip_if_do_end
|
||||
end
|
||||
@@ -1098,9 +1093,10 @@ defmodule Code.Formatter do
|
||||
{doc, state}
|
||||
end
|
||||
|
||||
# parens may one of:
|
||||
# parens may be one of:
|
||||
#
|
||||
# * :skip_unless_many_args - skips parens unless we are the argument context
|
||||
# * :skip_if_only_do_end - skip parens if we are do-end and the only arg
|
||||
# * :skip_if_do_end - skip parens if we are do-end
|
||||
# * :required - never skip parens
|
||||
#
|
||||
@@ -1114,13 +1110,18 @@ defmodule Code.Formatter do
|
||||
defp call_args_to_algebra(args, meta, context, parens, list_to_keyword?, state) do
|
||||
{rest, last} = split_last(args)
|
||||
|
||||
if blocks = do_end_blocks(last) do
|
||||
if blocks = do_end_blocks(last, state) do
|
||||
{call_doc, state} =
|
||||
if rest == [] do
|
||||
{" do", state}
|
||||
else
|
||||
no_parens? = parens != :required
|
||||
call_args_to_algebra_no_blocks(meta, rest, no_parens?, list_to_keyword?, " do", state)
|
||||
case rest do
|
||||
[] when parens == :required ->
|
||||
{"() do", state}
|
||||
|
||||
[] ->
|
||||
{" do", state}
|
||||
|
||||
_ ->
|
||||
no_parens? = parens not in [:required, :skip_if_only_do_end]
|
||||
call_args_to_algebra_no_blocks(meta, rest, no_parens?, list_to_keyword?, " do", state)
|
||||
end
|
||||
|
||||
{blocks_doc, state} = do_end_blocks_to_algebra(blocks, state)
|
||||
@@ -1161,7 +1162,7 @@ defmodule Code.Formatter do
|
||||
{left_doc, _join, state} =
|
||||
args_to_algebra_with_comments(
|
||||
left,
|
||||
Keyword.delete(meta, :end_line),
|
||||
Keyword.delete(meta, :closing),
|
||||
skip_parens?,
|
||||
:force_comma,
|
||||
join,
|
||||
@@ -1268,16 +1269,19 @@ defmodule Code.Formatter do
|
||||
not Enum.any?(args, &match?({:<-, _, [_, _]}, &1))
|
||||
end
|
||||
|
||||
defp do_end_blocks([{{:__block__, meta, [:do]}, _} | _] = blocks) do
|
||||
if meta[:format] == :block do
|
||||
defp do_end_blocks([{{:__block__, meta, [:do]}, _} | rest] = blocks, state) do
|
||||
if meta[:format] == :block or can_force_do_end_blocks?(rest, state) do
|
||||
blocks
|
||||
|> Enum.map(fn {{:__block__, meta, [key]}, value} -> {key, line(meta), value} end)
|
||||
|> do_end_blocks_with_range(end_line(meta))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_end_blocks(_) do
|
||||
nil
|
||||
defp do_end_blocks(_, _), do: nil
|
||||
|
||||
defp can_force_do_end_blocks?(rest, state) do
|
||||
state.force_do_end_blocks and
|
||||
Enum.all?(rest, fn {{:__block__, _, [key]}, _} -> key in @do_end_keywords end)
|
||||
end
|
||||
|
||||
defp do_end_blocks_with_range([{key1, line1, value1}, {_, line2, _} = h | t], end_line) do
|
||||
@@ -1316,7 +1320,7 @@ defmodule Code.Formatter do
|
||||
|
||||
defp interpolated?(entries) do
|
||||
Enum.all?(entries, fn
|
||||
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
entry when is_binary(entry) -> true
|
||||
_ -> false
|
||||
end)
|
||||
@@ -1354,7 +1358,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp interpolation_to_algebra([entry | entries], escape, state, acc, last) do
|
||||
{:::, _, [{{:., _, [Kernel, :to_string]}, meta, [quoted]}, {:binary, _, _}]} = entry
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, meta, [quoted]}, {:binary, _, _}]} = entry
|
||||
{doc, state} = block_to_algebra(quoted, line(meta), end_line(meta), state)
|
||||
doc = surround("\#{", doc, "}")
|
||||
interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
|
||||
@@ -1367,26 +1371,26 @@ defmodule Code.Formatter do
|
||||
## Sigils
|
||||
|
||||
defp maybe_sigil_to_algebra(fun, meta, args, state) do
|
||||
case {Atom.to_string(fun), args} do
|
||||
{<<"sigil_", name>>, [{:<<>>, _, entries}, modifiers]} ->
|
||||
opening_terminator = Keyword.fetch!(meta, :terminator)
|
||||
doc = <<?~, name, opening_terminator::binary>>
|
||||
with <<"sigil_", name>> <- Atom.to_string(fun),
|
||||
[{:<<>>, _, entries}, modifiers] when is_list(modifiers) <- args,
|
||||
opening_terminator when not is_nil(opening_terminator) <- Keyword.get(meta, :terminator) do
|
||||
doc = <<?~, name, opening_terminator::binary>>
|
||||
|
||||
if opening_terminator in [@double_heredoc, @single_heredoc] do
|
||||
closing_terminator = concat(opening_terminator, List.to_string(modifiers))
|
||||
if opening_terminator in [@double_heredoc, @single_heredoc] do
|
||||
closing_terminator = concat(opening_terminator, List.to_string(modifiers))
|
||||
|
||||
{doc, state} =
|
||||
entries
|
||||
|> prepend_heredoc_line()
|
||||
|> interpolation_to_algebra(:heredoc, state, doc, closing_terminator)
|
||||
|
||||
{force_unfit(doc), state}
|
||||
else
|
||||
escape = closing_sigil_terminator(opening_terminator)
|
||||
closing_terminator = concat(escape, List.to_string(modifiers))
|
||||
interpolation_to_algebra(entries, escape, state, doc, closing_terminator)
|
||||
end
|
||||
{doc, state} =
|
||||
entries
|
||||
|> prepend_heredoc_line()
|
||||
|> interpolation_to_algebra(:heredoc, state, doc, closing_terminator)
|
||||
|
||||
{force_unfit(doc), state}
|
||||
else
|
||||
escape = closing_sigil_terminator(opening_terminator)
|
||||
closing_terminator = concat(escape, List.to_string(modifiers))
|
||||
interpolation_to_algebra(entries, escape, state, doc, closing_terminator)
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
@@ -1423,7 +1427,7 @@ defmodule Code.Formatter do
|
||||
{bitstring_wrap_parens(doc, i, last), state}
|
||||
end
|
||||
|
||||
defp bitstring_segment_to_algebra({{:::, _, [segment, spec]}, i}, state, last) do
|
||||
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
|
||||
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
||||
{spec, state} = bitstring_spec_to_algebra(spec, state)
|
||||
|
||||
@@ -1849,7 +1853,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp add_max_line_to_last_clause([{op, meta, args}], max_line) do
|
||||
[{op, [end_line: max_line] ++ meta, args}]
|
||||
[{op, [closing: [line: max_line]] ++ meta, args}]
|
||||
end
|
||||
|
||||
defp add_max_line_to_last_clause([clause | clauses], max_line) do
|
||||
@@ -2021,7 +2025,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
# TODO: We can remove this workaround once we remove
|
||||
# ?rearrange_uop from the parser in Elixir v2.0.
|
||||
# ?rearrange_uop from the parser on v2.0.
|
||||
defp wrap_in_parens_if_operator(doc, {:__block__, _, [expr]}) do
|
||||
wrap_in_parens_if_operator(doc, expr)
|
||||
end
|
||||
@@ -2237,11 +2241,11 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp line(meta) do
|
||||
Keyword.get(meta, :line, @max_line)
|
||||
meta[:line] || @max_line
|
||||
end
|
||||
|
||||
defp end_line(meta) do
|
||||
Keyword.get(meta, :end_line, @min_line)
|
||||
meta[:closing][:line] || @min_line
|
||||
end
|
||||
|
||||
## Algebra helpers
|
||||
|
||||
@@ -34,7 +34,7 @@ defmodule Code.Identifier do
|
||||
cond do
|
||||
op in [:<-, :\\] -> {:left, 40}
|
||||
op in [:when] -> {:right, 50}
|
||||
op in [:::] -> {:right, 60}
|
||||
op in [:"::"] -> {:right, 60}
|
||||
op in [:|] -> {:right, 70}
|
||||
op in [:=] -> {:right, 100}
|
||||
op in [:||, :|||, :or] -> {:left, 130}
|
||||
@@ -60,9 +60,13 @@ defmodule Code.Identifier do
|
||||
* `: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_operator` - all callable operators, such as `:<>`. Note
|
||||
operators such as `:..` are not callable because of ambiguity
|
||||
|
||||
* `:not_atomable` - callable operators that must be wrapped in quotes when
|
||||
defined as an atom. For example, `::` must be written as `:"::"` to avoid
|
||||
the ambiguity between the atom and the keyword identifier
|
||||
|
||||
* `: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
|
||||
@@ -80,6 +84,9 @@ defmodule Code.Identifier do
|
||||
atom in [:%, :%{}, :{}, :<<>>, :..., :.., :., :->] ->
|
||||
:not_callable
|
||||
|
||||
atom in [:"::"] ->
|
||||
:not_atomable
|
||||
|
||||
unary_op(atom) != :error or binary_op(atom) != :error ->
|
||||
:callable_operator
|
||||
|
||||
@@ -143,7 +150,7 @@ defmodule Code.Identifier do
|
||||
type when type in [:callable_local, :callable_operator, :not_callable] ->
|
||||
":" <> binary
|
||||
|
||||
:other ->
|
||||
_ ->
|
||||
{escaped, _} = escape(binary, ?")
|
||||
IO.iodata_to_binary([?:, ?", escaped, ?"])
|
||||
end
|
||||
@@ -172,7 +179,7 @@ defmodule Code.Identifier do
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
case classify(atom) do
|
||||
type when type in [:callable_local, :callable_operator] ->
|
||||
type when type in [:callable_local, :callable_operator, :not_atomable] ->
|
||||
binary
|
||||
|
||||
type ->
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule Code.Typespec do
|
||||
uniq: true,
|
||||
do: {var, {:var, meta, nil}}
|
||||
|
||||
spec = {:::, meta, [body, typespec_to_quoted(result)]}
|
||||
spec = {:"::", meta, [body, typespec_to_quoted(result)]}
|
||||
|
||||
if vars == [] do
|
||||
spec
|
||||
@@ -28,7 +28,7 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
def spec_to_quoted(name, {:type, line, :fun, []}) when is_atom(name) do
|
||||
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
|
||||
{:"::", [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
|
||||
@@ -52,7 +52,7 @@ defmodule Code.Typespec do
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
|
||||
when_args = [
|
||||
{:::, meta, [{name, [line: line], args}, typespec_to_quoted(result)]},
|
||||
{:"::", meta, [{name, [line: line], args}, typespec_to_quoted(result)]},
|
||||
guards ++ vars
|
||||
]
|
||||
|
||||
@@ -329,7 +329,7 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:var, line, var}) do
|
||||
{erl_to_ex_var(var), line, nil}
|
||||
{erl_to_ex_var(var), [line: line], nil}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, line, op, arg}) do
|
||||
@@ -341,7 +341,7 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:ann_type, line, [var, type]}) do
|
||||
{:::, [line: line], [typespec_to_quoted(var), typespec_to_quoted(type)]}
|
||||
{:"::", [line: line], [typespec_to_quoted(var), typespec_to_quoted(type)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted(
|
||||
|
||||
@@ -21,9 +21,9 @@ defprotocol Collectable do
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. `into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
|
||||
`Collectable.into/1` is about collecting those values into a structure.
|
||||
`Enumerable` protocol. `Collectable.into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If the functions in `Enumerable` are about taking values out,
|
||||
then `Collectable.into/1` is about collecting those values into a structure.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
defmodule Config do
|
||||
@moduledoc ~S"""
|
||||
A simple keyword-based configuration API.
|
||||
|
||||
## Example
|
||||
|
||||
This module is most commonly used to define application configuration,
|
||||
typically in `config/config.exs`:
|
||||
|
||||
import Config
|
||||
|
||||
config :some_app,
|
||||
key1: "value1",
|
||||
key2: "value2"
|
||||
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
||||
`import Config` will import the functions `config/2`, `config/3`
|
||||
and `import_config/1` to help you manage your configuration.
|
||||
|
||||
`config/2` and `config/3` are used to define key-value configuration
|
||||
for a given application. Once Mix starts, it will automatically
|
||||
evaluate the configuration file and persist the configuration above
|
||||
into `:some_app`'s application environment, which can be accessed in
|
||||
as follows:
|
||||
|
||||
"value1" = Application.fetch_env!(:some_app, :key1)
|
||||
|
||||
Finally, the line `import_config "#{Mix.env()}.exs"` will import other
|
||||
config files, based on the current Mix environment, such as
|
||||
`config/dev.exs` and `config/test.exs`.
|
||||
|
||||
`Config` also provides a low-level API for evaluating and reading
|
||||
configuration, under the `Config.Reader` module.
|
||||
|
||||
**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).
|
||||
|
||||
## Migrating from `use Mix.Config`
|
||||
|
||||
The `Config` module in Elixir was introduced in v1.9 as a replacement to
|
||||
`Mix.Config`, which was specific to Mix and has been deprecated.
|
||||
|
||||
You can leverage `Config` instead of `Mix.Config` in two steps. The first
|
||||
step is to replace `use Mix.Config` at the top of your config files by
|
||||
`import Config`.
|
||||
|
||||
The second is to make sure your `import_config/1` calls do not have a
|
||||
wildcard character. If so, you need to perform the wildcard lookup
|
||||
manually. For example, if you did:
|
||||
|
||||
import_config "../apps/*/config/config.exs"
|
||||
|
||||
It has to be replaced by:
|
||||
|
||||
for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
|
||||
import_config config
|
||||
end
|
||||
|
||||
## config/releases.exs
|
||||
|
||||
If you are using releases, see `mix release`, there another configuration
|
||||
file called `config/releases.exs`. While `config/config.exs` and friends
|
||||
mentioned in the previous section are executed whenever you run a Mix
|
||||
command, including when you assemble a release, `config/releases.exs` is
|
||||
execute every time your production system boots. Since Mix is not available
|
||||
in a production system, `config/releases.exs` must not use any of the
|
||||
functions from Mix.
|
||||
"""
|
||||
|
||||
@config_key {__MODULE__, :config}
|
||||
@files_key {__MODULE__, :files}
|
||||
|
||||
defp get_config!() do
|
||||
Process.get(@config_key) || raise_improper_use!()
|
||||
end
|
||||
|
||||
defp put_config(value) do
|
||||
Process.put(@config_key, value)
|
||||
end
|
||||
|
||||
defp delete_config() do
|
||||
Process.delete(@config_key)
|
||||
end
|
||||
|
||||
defp get_files!() do
|
||||
Process.get(@files_key) || raise_improper_use!()
|
||||
end
|
||||
|
||||
defp put_files(value) do
|
||||
Process.put(@files_key, value)
|
||||
end
|
||||
|
||||
defp delete_files() do
|
||||
Process.delete(@files_key)
|
||||
end
|
||||
|
||||
defp raise_improper_use!() do
|
||||
raise "could not set configuration via Config. " <>
|
||||
"This usually means you are trying to execute a configuration file " <>
|
||||
"directly, instead of reading it with Config.Reader"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Configures the given `root_key`.
|
||||
|
||||
Keyword lists are always deep-merged.
|
||||
|
||||
## Examples
|
||||
|
||||
The given `opts` are merged into the existing configuration
|
||||
for the given `root_key`. Conflicting keys are overridden by the
|
||||
ones specified in `opts`. For example, the application
|
||||
configuration below
|
||||
|
||||
config :logger,
|
||||
level: :warn,
|
||||
backends: [:console]
|
||||
|
||||
config :logger,
|
||||
level: :info,
|
||||
truncate: 1024
|
||||
|
||||
will have a final configuration for `:logger` of:
|
||||
|
||||
[level: :info, backends: [:console], truncate: 1024]
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
|
||||
unless Keyword.keyword?(opts) do
|
||||
raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
|
||||
end
|
||||
|
||||
get_config!()
|
||||
|> __merge__([{root_key, opts}])
|
||||
|> put_config()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Configures the given `key` for the given `root_key`.
|
||||
|
||||
Keyword lists are always deep merged.
|
||||
|
||||
## Examples
|
||||
|
||||
The given `opts` are merged into the existing values for `key`
|
||||
in the given `root_key`. Conflicting keys are overridden by the
|
||||
ones specified in `opts`. For example, the application
|
||||
configuration below
|
||||
|
||||
config :ecto, Repo,
|
||||
log_level: :warn,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
|
||||
config :ecto, Repo,
|
||||
log_level: :info,
|
||||
pool_size: 10
|
||||
|
||||
will have a final value of the configuration for the `Repo`
|
||||
key in the `:ecto` application of:
|
||||
|
||||
[log_level: :info, pool_size: 10, adapter: Ecto.Adapters.Postgres]
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
def config(root_key, key, opts) when is_atom(root_key) and is_atom(key) do
|
||||
get_config!()
|
||||
|> __merge__([{root_key, [{key, opts}]}])
|
||||
|> put_config()
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Imports configuration from the given file.
|
||||
|
||||
In case the file doesn't exist, an error is raised.
|
||||
|
||||
If file is a relative, it will be expanded relatively to the
|
||||
directory the current configuration file is in.
|
||||
|
||||
## Examples
|
||||
|
||||
This is often used to emulate configuration across environments:
|
||||
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
defmacro import_config(file) do
|
||||
quote do
|
||||
Config.__import__!(Path.expand(unquote(file), __DIR__))
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec __import__!(Path.t()) :: keyword()
|
||||
def __import__!(file) when is_binary(file) do
|
||||
current_files = get_files!()
|
||||
|
||||
if file in current_files do
|
||||
raise ArgumentError,
|
||||
"attempting to load configuration #{Path.relative_to_cwd(file)} recursively"
|
||||
end
|
||||
|
||||
put_files([file | current_files])
|
||||
Code.eval_file(file)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec __eval__!(Path.t(), [Path.t()]) :: {keyword, [Path.t()]}
|
||||
def __eval__!(file, imported_paths \\ []) when is_binary(file) and is_list(imported_paths) do
|
||||
previous_config = put_config([])
|
||||
previous_files = put_files(imported_paths)
|
||||
|
||||
try do
|
||||
{eval_config, _} = __import__!(Path.expand(file))
|
||||
|
||||
case get_config!() do
|
||||
[] when is_list(eval_config) ->
|
||||
{validate!(eval_config, file), get_files!()}
|
||||
|
||||
pdict_config ->
|
||||
{pdict_config, get_files!()}
|
||||
end
|
||||
after
|
||||
if previous_config, do: put_config(previous_config), else: delete_config()
|
||||
if previous_files, do: put_files(previous_files), else: delete_files()
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __merge__(config1, config2) when is_list(config1) and is_list(config2) do
|
||||
Keyword.merge(config1, config2, fn _, app1, app2 ->
|
||||
Keyword.merge(app1, app2, &deep_merge/3)
|
||||
end)
|
||||
end
|
||||
|
||||
defp deep_merge(_key, value1, value2) do
|
||||
if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
|
||||
Keyword.merge(value1, value2, &deep_merge/3)
|
||||
else
|
||||
value2
|
||||
end
|
||||
end
|
||||
|
||||
defp validate!(config, file) do
|
||||
Enum.all?(config, fn
|
||||
{app, value} when is_atom(app) ->
|
||||
if Keyword.keyword?(value) do
|
||||
true
|
||||
else
|
||||
raise ArgumentError,
|
||||
"expected config for app #{inspect(app)} in #{Path.relative_to_cwd(file)} " <>
|
||||
"to return keyword list, got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
|
||||
config
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,249 @@
|
||||
defmodule Config.Provider do
|
||||
@moduledoc """
|
||||
Specifies a provider API that loads configuration during boot.
|
||||
|
||||
Config providers are typically used during releases to load
|
||||
external configuration while the system boots. This is done
|
||||
by starting the VM with the minimum amount of applications
|
||||
running, then invoking all of the providers, and then
|
||||
restarting the system. This requires a mutable configuration
|
||||
file on disk, as the results of the providers are written to
|
||||
the file system. For more information on runtime configuration,
|
||||
see `mix release`.
|
||||
|
||||
## Sample config provider
|
||||
|
||||
For example, imagine you need to load some configuration from
|
||||
a JSON file and load that into the system. Said configuration
|
||||
provider would look like:
|
||||
|
||||
defmodule JSONConfigProvider do
|
||||
@behaviour Config.Provider
|
||||
|
||||
# Let's pass the path to the JSON file as config
|
||||
def init(path) when is_binary(path), do: path
|
||||
|
||||
def load(config, path) do
|
||||
# We need to start any app we may depend on.
|
||||
{:ok, _} = Application.ensure_all_started(:jason)
|
||||
|
||||
json = path |> File.read!() |> Jason.decode!()
|
||||
|
||||
Config.Reader.merge(
|
||||
config,
|
||||
my_app: [
|
||||
some_value: json["my_app_some_value"],
|
||||
another_value: json["my_app_another_value"],
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
Then when specifying your release, you can specify the provider:
|
||||
|
||||
config_providers: [{JSONConfigProvider, "/etc/config.json"}]
|
||||
|
||||
Now once the system boots, it will invoke the provider early in
|
||||
the boot process, save the merged configuration to the disk, and
|
||||
reboot the system with the new values in place.
|
||||
"""
|
||||
|
||||
@type config :: keyword
|
||||
@type state :: term
|
||||
|
||||
@typedoc """
|
||||
A path pointing to a configuration file.
|
||||
|
||||
Since configuration files are often accessed on target machines,
|
||||
it can be expressed either as:
|
||||
|
||||
* a binary representing an absolute path
|
||||
|
||||
* a tuple {:system, system_var, path} where the config is the
|
||||
concatenation of the `system_var` with the given `path`
|
||||
|
||||
"""
|
||||
@type config_path :: {:system, binary(), binary()} | binary()
|
||||
|
||||
@doc """
|
||||
Invoked when initializing a config provider.
|
||||
|
||||
A config provider is typically initialized on the machine
|
||||
where the system is assembled and not on the target machine.
|
||||
The `c:init/1` callback is useful to verify the arguments
|
||||
given to the provider and prepare the state that will be
|
||||
given to `c:load/2`.
|
||||
|
||||
Furthermore, because the state returned by `c:init/1` can
|
||||
be written to text-based config files, it should be
|
||||
restricted only to simple data types, such as integers,
|
||||
strings, atoms, tuples, maps, and lists. Entries such as
|
||||
PIDs, references, and functions cannot be serialized.
|
||||
"""
|
||||
@callback init(term) :: state
|
||||
|
||||
@doc """
|
||||
Loads configuration (typically during system boot).
|
||||
|
||||
It receives the current `config` and the `state` returned by
|
||||
`c:init/1`. Then you typically read the extra configuration
|
||||
from an external source and merge it into the received `config`.
|
||||
Merging should be done with `Config.Reader.merge/2`, as it
|
||||
performs deep merge. It should return the updated config.
|
||||
|
||||
Note that `c:load/2` is typically invoked very early in the
|
||||
boot process, therefore if you need to use an application
|
||||
in the provider, it is your responsibility to start it.
|
||||
"""
|
||||
@callback load(config, state) :: config
|
||||
|
||||
@doc false
|
||||
defstruct [:providers, :config_path, extra_config: [], prune_after_boot: false]
|
||||
|
||||
@doc """
|
||||
Validates a `t:config_path/0`.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec validate_config_path!(config_path) :: :ok
|
||||
def validate_config_path!({:system, name, path})
|
||||
when is_binary(name) and is_binary(path),
|
||||
do: :ok
|
||||
|
||||
def validate_config_path!(path) do
|
||||
if is_binary(path) and Path.type(path) != :relative do
|
||||
:ok
|
||||
else
|
||||
raise ArgumentError, """
|
||||
expected configuration path to be:
|
||||
|
||||
* a binary representing an absolute path
|
||||
* a tuple {:system, system_var, path} where the config is the \
|
||||
concatenation of the `system_var` with the given `path`
|
||||
|
||||
Got: #{inspect(path)}
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resolves a `t:config_path/0` to an actual path.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec resolve_config_path!(config_path) :: binary
|
||||
def resolve_config_path!(path) when is_binary(path), do: path
|
||||
def resolve_config_path!({:system, name, path}), do: System.fetch_env!(name) <> path
|
||||
|
||||
@doc false
|
||||
def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do
|
||||
validate_config_path!(config_path)
|
||||
providers = for {provider, init} <- providers, do: {provider, provider.init(init)}
|
||||
struct!(%Config.Provider{config_path: config_path, providers: providers}, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def boot(app, key, restart_fun \\ &System.restart/0) do
|
||||
# The app with the config provider settings may not
|
||||
# have been loaded at this point, so make sure we load
|
||||
# its environment before querying it.
|
||||
_ = :application.load(app)
|
||||
|
||||
# The config provider typically runs very early in the
|
||||
# release process, so we need to make sure Elixir is started
|
||||
# before we go around running Elixir code.
|
||||
{:ok, _} = :application.ensure_all_started(:elixir)
|
||||
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, %Config.Provider{} = provider} ->
|
||||
path = resolve_config_path!(provider.config_path)
|
||||
validate_no_cyclic_boot!(path)
|
||||
|
||||
read_config!(path)
|
||||
|> Config.__merge__([{app, [{key, booted_key(provider, path)}]} | provider.extra_config])
|
||||
|> run_providers(provider)
|
||||
|> write_config!(path)
|
||||
|
||||
restart_fun.()
|
||||
|
||||
{:ok, {:booted, path}} ->
|
||||
File.rm(path)
|
||||
:booted
|
||||
|
||||
{:ok, :booted} ->
|
||||
:booted
|
||||
|
||||
_ ->
|
||||
:skip
|
||||
end
|
||||
end
|
||||
|
||||
defp booted_key(%{prune_after_boot: true}, path), do: {:booted, path}
|
||||
defp booted_key(%{prune_after_boot: false}, _path), do: :booted
|
||||
|
||||
defp validate_no_cyclic_boot!(path) do
|
||||
if System.get_env("ELIXIR_CONFIG_PROVIDER_BOOTED") do
|
||||
bad_path_abort("Got infinite loop when running Config.Provider", path)
|
||||
else
|
||||
System.put_env("ELIXIR_CONFIG_PROVIDER_BOOTED", "1")
|
||||
end
|
||||
end
|
||||
|
||||
defp read_config!(path) do
|
||||
case :file.consult(path) do
|
||||
{:ok, [inner]} ->
|
||||
inner
|
||||
|
||||
{:error, reason} ->
|
||||
bad_path_abort(
|
||||
"Could not read runtime configuration due to reason: #{inspect(reason)}",
|
||||
path
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_providers(config, %{providers: providers}) do
|
||||
Enum.reduce(providers, config, fn {provider, state}, acc ->
|
||||
try do
|
||||
provider.load(acc, state)
|
||||
catch
|
||||
kind, error ->
|
||||
IO.puts(:stderr, "ERROR! Config provider #{inspect(provider)} failed with:")
|
||||
IO.puts(:stderr, Exception.format(kind, error, __STACKTRACE__))
|
||||
:erlang.raise(kind, error, __STACKTRACE__)
|
||||
else
|
||||
term when is_list(term) ->
|
||||
term
|
||||
|
||||
term ->
|
||||
abort("Expected provider #{inspect(provider)} to return a list, got: #{inspect(term)}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp write_config!(config, path) do
|
||||
contents = :io_lib.format("%% coding: utf-8~n~tw.~n", [config])
|
||||
|
||||
case File.write(path, contents, [:utf8]) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
bad_path_abort(
|
||||
"Could not write runtime configuration due to reason: #{inspect(reason)}",
|
||||
path
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp bad_path_abort(msg, path) do
|
||||
abort(
|
||||
msg <>
|
||||
". Please make sure #{inspect(path)} is writable and accessible " <>
|
||||
"or choose a different path"
|
||||
)
|
||||
end
|
||||
|
||||
defp abort(msg) do
|
||||
IO.puts(:stderr, "ERROR! " <> msg)
|
||||
raise(msg)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
defmodule Config.Reader do
|
||||
@moduledoc """
|
||||
API for reading config files defined with `Config`.
|
||||
|
||||
## As a provider
|
||||
|
||||
`Config.Reader` can also be used as a `Config.Provider`.
|
||||
When used as a provider, it expects a single argument:
|
||||
which the configuration path (as outlined in
|
||||
`t:Config.Provider.config_path/0`) for the configuration
|
||||
to be read and loaded during the system boot.
|
||||
"""
|
||||
|
||||
@behaviour Config.Provider
|
||||
|
||||
@impl true
|
||||
def init(path) do
|
||||
Config.Provider.validate_config_path!(path)
|
||||
path
|
||||
end
|
||||
|
||||
@impl true
|
||||
def load(config, path) do
|
||||
merge(config, path |> Config.Provider.resolve_config_path!() |> read!())
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the configuration file.
|
||||
|
||||
The same as `read_imports!/2` but only returns the configuration
|
||||
in the given file, without returning the imported paths.
|
||||
|
||||
It exists for convenience purposes. For example, you could
|
||||
invoke it inside your `mix.exs` to read some external data
|
||||
you decided to move to a configuration file:
|
||||
|
||||
releases: Config.Reader.read!("rel/releases.exs")
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec read!(Path.t(), [Path.t()]) :: keyword
|
||||
def read!(file, imported_paths \\ [])
|
||||
when is_binary(file) and is_list(imported_paths) do
|
||||
Config.__eval__!(file, imported_paths) |> elem(0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the given configuration file alongside its imports.
|
||||
|
||||
It accepts a list of `imported_paths` that should raise if attempted
|
||||
to be imported again (to avoid recursive imports).
|
||||
|
||||
It returns a tuple with the configuration and the imported paths.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec read_imports!(Path.t(), [Path.t()]) :: {keyword, [Path.t()]}
|
||||
def read_imports!(file, imported_paths \\ [])
|
||||
when is_binary(file) and is_list(imported_paths) do
|
||||
Config.__eval__!(file, imported_paths)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two configurations.
|
||||
|
||||
The configurations are merged together with the values in
|
||||
the second one having higher preference than the first in
|
||||
case of conflicts. In case both values are set to keyword
|
||||
lists, it deep merges them.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Config.Reader.merge([app: [k: :v1]], [app: [k: :v2]])
|
||||
[app: [k: :v2]]
|
||||
|
||||
iex> Config.Reader.merge([app: [k: [v1: 1, v2: 2]]], [app: [k: [v2: :a, v3: :b]]])
|
||||
[app: [k: [v1: 1, v2: :a, v3: :b]]]
|
||||
|
||||
iex> Config.Reader.merge([app1: []], [app2: []])
|
||||
[app1: [], app2: []]
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec merge(keyword, keyword) :: keyword
|
||||
def merge(config1, config2) when is_list(config1) and is_list(config2) do
|
||||
Config.__merge__(config1, config2)
|
||||
end
|
||||
end
|
||||
@@ -18,8 +18,6 @@ defmodule Dict do
|
||||
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
|
||||
|
||||
@@ -47,19 +47,19 @@ defmodule DynamicSupervisor do
|
||||
# Automatically defines child_spec/1
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
def start_link(init_arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
def init(_init_arg) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
See the `Supervisor` docs for a discussion of when you may want to use
|
||||
module-based supervisors. The `@doc` annotation immediately preceding
|
||||
`use DymamicSupervisor` will be attached to the generated `child_spec/1`
|
||||
module-based supervisors. A `@doc` annotation immediately preceding
|
||||
`use DynamicSupervisor` will be attached to the generated `child_spec/1`
|
||||
function.
|
||||
|
||||
## Name registration
|
||||
@@ -78,20 +78,20 @@ defmodule DynamicSupervisor do
|
||||
defmodule MySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
def start_link(init_arg) do
|
||||
Supervisor.start_link(__MODULE__, init_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)
|
||||
# This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
|
||||
Supervisor.start_child(__MODULE__, [foo, bar, baz])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
def init(init_arg) do
|
||||
children = [
|
||||
# Or the deprecated: worker(MyWorker, [initial_arg])
|
||||
%{id: MyWorker, start: {MyWorker, :start_link, [initial_arg]}}
|
||||
# Or the deprecated: worker(MyWorker, [init_arg])
|
||||
%{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :simple_one_for_one)
|
||||
@@ -103,8 +103,8 @@ defmodule DynamicSupervisor do
|
||||
defmodule MySupervisor do
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
def start_link(init_arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_child(foo, bar, baz) do
|
||||
@@ -115,10 +115,10 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
def init(init_arg) do
|
||||
DynamicSupervisor.init(
|
||||
strategy: :one_for_one,
|
||||
extra_arguments: [initial_arg]
|
||||
extra_arguments: [init_arg]
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -302,8 +302,8 @@ defmodule DynamicSupervisor do
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous
|
||||
value, or if it fails, the child specification is discarded and this function
|
||||
returns `{:error, error}` where `error` is a term containing information about
|
||||
the error and child specification.
|
||||
returns `{:error, error}` where `error` is the error or erroneous value
|
||||
returned from child process start function, or failure reason if it fails.
|
||||
|
||||
If the supervisor already has N children in a way that N exceeds the amount
|
||||
of `:max_children` set on the supervisor initialization (see `init/1`), then
|
||||
@@ -405,7 +405,7 @@ defmodule DynamicSupervisor do
|
||||
|
||||
* `id` - it is always `:undefined` for dynamic supervisors
|
||||
|
||||
* `child` - the pid of the corresponding child process or the
|
||||
* `child` - the PID of the corresponding child process or the
|
||||
atom `:restarting` if the process is about to be restarted
|
||||
|
||||
* `type` - `:worker` or `:supervisor` as defined in the child
|
||||
@@ -487,7 +487,7 @@ defmodule DynamicSupervisor do
|
||||
|
||||
* `:strategy` - the restart strategy option. The only supported
|
||||
value is `:one_for_one` which means that no other child is
|
||||
terminate if a child process terminates. You can learn more
|
||||
terminated if a child process terminates. You can learn more
|
||||
about strategies in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
|
||||
+106
-98
@@ -121,7 +121,7 @@ defprotocol Enumerable do
|
||||
|
||||
Most of the operations in `Enum` are implemented in terms of reduce.
|
||||
This function should apply the given `t:reducer/0` function to each
|
||||
item in the `enumerable` and proceed as expected by the returned
|
||||
element in the `enumerable` and proceed as expected by the returned
|
||||
accumulator.
|
||||
|
||||
See the documentation of the types `t:result/0` and `t:acc/0` for
|
||||
@@ -223,11 +223,11 @@ defmodule Enum do
|
||||
and the data type returned by `File.stream!/3` which allows a file to be
|
||||
traversed as if it was an enumerable.
|
||||
|
||||
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.
|
||||
The functions in this module work in linear time. This means that, the
|
||||
time it takes to perform an operation grows at the same rate as the length
|
||||
of the enumerable. 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.
|
||||
|
||||
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
|
||||
@@ -276,10 +276,11 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given `fun` evaluates to a truthy value (neither `false` nor `nil`)
|
||||
on all of the items in the `enumerable`.
|
||||
Returns `true` if `fun.(element)` is truthy for all elements in `enumerable`.
|
||||
|
||||
It stops the iteration at the first invocation that returns either `false` or `nil`.
|
||||
Iterates over the `enumerable` and invokes `fun` on each element. When an invocation
|
||||
of `fun` returns a falsy value (`false` or `nil`) iteration stops immediately and
|
||||
`false` is returned. In all other cases `true` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -289,8 +290,12 @@ defmodule Enum do
|
||||
iex> Enum.all?([2, 3, 4], fn x -> rem(x, 2) == 0 end)
|
||||
false
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
all items in the `enumerable` are truthy values.
|
||||
iex> Enum.all?([], fn x -> x > 0 end)
|
||||
true
|
||||
|
||||
If no function is given, the truthiness of each element is checked during iteration.
|
||||
When an element has a falsy value (`false` or `nil`) iteration stops immediately and
|
||||
`false` is returned. In all other cases `true` is returned.
|
||||
|
||||
iex> Enum.all?([1, 2, 3])
|
||||
true
|
||||
@@ -298,6 +303,9 @@ defmodule Enum do
|
||||
iex> Enum.all?([1, nil, 3])
|
||||
false
|
||||
|
||||
iex> Enum.all?([])
|
||||
true
|
||||
|
||||
"""
|
||||
@spec all?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
@@ -315,9 +323,11 @@ 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 `fun.(element)` is truthy for at least one element in `enumerable`.
|
||||
|
||||
It stops the iteration at the first invocation that returns a truthy value (neither `false` nor `nil`).
|
||||
Iterates over the `enumerable` and invokes `fun` on each element. When an invocation
|
||||
of `fun` returns a truthy value (neither `false` nor `nil`) iteration stops
|
||||
immediately and `true` is returned. In all other cases `false` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -327,8 +337,12 @@ defmodule Enum do
|
||||
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
|
||||
in the `enumerable` is a truthy value.
|
||||
iex> Enum.any?([], fn x -> x > 0 end)
|
||||
false
|
||||
|
||||
If no function is given, the truthiness of each element is checked during iteration.
|
||||
When an element has a truthy value (neither `false` nor `nil`) iteration stops
|
||||
immediately and `true` is returned. In all other cases `false` is returned.
|
||||
|
||||
iex> Enum.any?([false, false, false])
|
||||
false
|
||||
@@ -336,6 +350,9 @@ defmodule Enum do
|
||||
iex> Enum.any?([false, true, false])
|
||||
true
|
||||
|
||||
iex> Enum.any?([])
|
||||
false
|
||||
|
||||
"""
|
||||
@spec any?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
@@ -358,7 +375,7 @@ defmodule Enum do
|
||||
Returns `default` if `index` is out of bounds.
|
||||
|
||||
A negative `index` can be passed, which means the `enumerable` is
|
||||
enumerated once and the `index` is counted from the end (e.g.
|
||||
enumerated once and the `index` is counted from the end (for example,
|
||||
`-1` finds the last element).
|
||||
|
||||
## Examples
|
||||
@@ -384,19 +401,16 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Enum.chunk_every/2 instead"
|
||||
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@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
|
||||
@@ -411,7 +425,7 @@ defmodule Enum do
|
||||
def chunk_every(enumerable, count), do: chunk_every(enumerable, count, count, [])
|
||||
|
||||
@doc """
|
||||
Returns list of lists containing `count` items each, where
|
||||
Returns list of lists containing `count` elements each, where
|
||||
each new chunk starts `step` elements into the `enumerable`.
|
||||
|
||||
`step` is optional and, if not passed, defaults to `count`, i.e.
|
||||
@@ -468,11 +482,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> chunk_fun = fn item, acc ->
|
||||
...> if rem(item, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([item | acc]), []}
|
||||
iex> chunk_fun = fn element, acc ->
|
||||
...> if rem(element, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([element | acc]), []}
|
||||
...> else
|
||||
...> {:cont, [item | acc]}
|
||||
...> {:cont, [element | acc]}
|
||||
...> end
|
||||
...> end
|
||||
iex> after_fun = fn
|
||||
@@ -593,7 +607,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the count of items in the `enumerable` for which `fun` returns
|
||||
Returns the count of elements in the `enumerable` for which `fun` returns
|
||||
a truthy value.
|
||||
|
||||
## Examples
|
||||
@@ -655,7 +669,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the `amount` of items from the `enumerable`.
|
||||
Drops the `amount` of elements from the `enumerable`.
|
||||
|
||||
If a negative `amount` is given, the `amount` of last values will be dropped.
|
||||
The `enumerable` will be enumerated once to retrieve the proper index and
|
||||
@@ -699,12 +713,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of every `nth` item in the `enumerable` dropped,
|
||||
Returns a list of every `nth` element in the `enumerable` dropped,
|
||||
starting with the first element.
|
||||
|
||||
The first item is always dropped, unless `nth` is 0.
|
||||
The first element is always dropped, unless `nth` is 0.
|
||||
|
||||
The second argument specifying every `nth` item must be a non-negative
|
||||
The second argument specifying every `nth` element must be a non-negative
|
||||
integer.
|
||||
|
||||
## Examples
|
||||
@@ -732,7 +746,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops items at the beginning of the `enumerable` while `fun` returns a
|
||||
Drops elements at the beginning of the `enumerable` while `fun` returns a
|
||||
truthy value.
|
||||
|
||||
## Examples
|
||||
@@ -752,7 +766,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes the given `fun` for each item in the `enumerable`.
|
||||
Invokes the given `fun` for each element in the `enumerable`.
|
||||
|
||||
Returns `:ok`.
|
||||
|
||||
@@ -799,7 +813,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def empty?(enumerable) do
|
||||
case backwards_compatible_slice(enumerable) do
|
||||
case Enumerable.slice(enumerable) do
|
||||
{:ok, value, _} ->
|
||||
value == 0
|
||||
|
||||
@@ -816,7 +830,7 @@ defmodule Enum do
|
||||
Returns `{:ok, element}` if found, otherwise `:error`.
|
||||
|
||||
A negative `index` can be passed, which means the `enumerable` is
|
||||
enumerated once and the `index` is counted from the end (e.g.
|
||||
enumerated once and the `index` is counted from the end (for example,
|
||||
`-1` fetches the last element).
|
||||
|
||||
## Examples
|
||||
@@ -908,10 +922,9 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@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)
|
||||
for element <- enumerable, filter.(element), do: mapper.(element)
|
||||
end
|
||||
|
||||
def filter_map(enumerable, filter, mapper) do
|
||||
@@ -921,8 +934,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item for which `fun` returns a truthy value.
|
||||
If no such item is found, returns `default`.
|
||||
Returns the first element for which `fun` returns a truthy value.
|
||||
If no such element is found, returns `default`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1049,7 +1062,7 @@ defmodule Enum do
|
||||
Maps and reduces an `enumerable`, flattening the given results (only one level deep).
|
||||
|
||||
It expects an accumulator and a function that receives each enumerable
|
||||
item, and must return a tuple containing a new enumerable (often a list)
|
||||
element, and must return a tuple containing a new enumerable (often a list)
|
||||
with the new accumulator or a tuple with `:halt` as first element and
|
||||
the accumulator as second.
|
||||
|
||||
@@ -1066,9 +1079,8 @@ defmodule Enum do
|
||||
{[[1], [2], [3], [4], [5]], 15}
|
||||
|
||||
"""
|
||||
@spec flat_map_reduce(t, acc, fun) :: {[any], any}
|
||||
when fun: (element, acc -> {t, acc} | {:halt, acc}),
|
||||
acc: any
|
||||
@spec flat_map_reduce(t, acc, fun) :: {[any], acc}
|
||||
when fun: (element, acc -> {t, acc} | {:halt, acc})
|
||||
def flat_map_reduce(enumerable, acc, fun) do
|
||||
{_, {list, acc}} =
|
||||
Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {list, acc} ->
|
||||
@@ -1122,7 +1134,6 @@ defmodule Enum do
|
||||
end)
|
||||
end
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
def group_by(enumerable, dict, fun) do
|
||||
IO.warn(
|
||||
"Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
|
||||
@@ -1140,8 +1151,6 @@ defmodule Enum do
|
||||
@doc """
|
||||
Intersperses `element` between each element of the enumeration.
|
||||
|
||||
Complexity: O(n).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.intersperse([1, 2, 3], 0)
|
||||
@@ -1276,7 +1285,7 @@ defmodule Enum do
|
||||
|
||||
If `joiner` is not passed at all, it defaults to the empty binary.
|
||||
|
||||
All items in the `enumerable` must be convertible to a binary,
|
||||
All elements in the `enumerable` must be convertible to a binary,
|
||||
otherwise an error is raised.
|
||||
|
||||
## Examples
|
||||
@@ -1306,8 +1315,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list where each item is the result of invoking
|
||||
`fun` on each corresponding item of `enumerable`.
|
||||
Returns a list where each element is the result of invoking
|
||||
`fun` on each corresponding element of `enumerable`.
|
||||
|
||||
For maps, the function expects a key-value tuple.
|
||||
|
||||
@@ -1333,11 +1342,11 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Returns a list of results of invoking `fun` on every `nth`
|
||||
item of `enumerable`, starting with the first element.
|
||||
element of `enumerable`, starting with the first element.
|
||||
|
||||
The first item is always passed to the given function, unless `nth` is `0`.
|
||||
The first element is always passed to the given function, unless `nth` is `0`.
|
||||
|
||||
The second argument specifying every `nth` item must be a non-negative
|
||||
The second argument specifying every `nth` element must be a non-negative
|
||||
integer.
|
||||
|
||||
If `nth` is `0`, then `enumerable` is directly converted to a list,
|
||||
@@ -1378,7 +1387,7 @@ defmodule Enum do
|
||||
the same type as `joiner`.
|
||||
If `joiner` is not passed at all, it defaults to an empty binary.
|
||||
|
||||
All items returned from invoking the `mapper` must be convertible to
|
||||
All elements returned from invoking the `mapper` must be convertible to
|
||||
a binary, otherwise an error is raised.
|
||||
|
||||
## Examples
|
||||
@@ -1408,7 +1417,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes the given function to each item in the `enumerable` to reduce
|
||||
Invokes the given function to each element in the `enumerable` to reduce
|
||||
it to a single element, while keeping an accumulator.
|
||||
|
||||
Returns a tuple where the first element is the mapped enumerable and
|
||||
@@ -1426,7 +1435,7 @@ defmodule Enum do
|
||||
{[2, 4, 6], 6}
|
||||
|
||||
"""
|
||||
@spec map_reduce(t, any, (element, any -> {any, any})) :: {any, any}
|
||||
@spec map_reduce(t, acc, (element, acc -> {element, acc})) :: {list, acc}
|
||||
def map_reduce(enumerable, acc, fun) when is_list(enumerable) do
|
||||
:lists.mapfoldl(fun, acc, enumerable)
|
||||
end
|
||||
@@ -1467,7 +1476,7 @@ defmodule Enum do
|
||||
|
||||
In the example above, `max/1` returned March 31st instead of April 1st
|
||||
because the structural comparison compares the day before the year. This
|
||||
can be addressed by using `max_by/1` and by relying on structures where
|
||||
can be addressed by using `max_by/3` and by relying on structures where
|
||||
the most significant digits come first. In this particular case, we can
|
||||
use `Date.to_erl/1` to get a tuple representation with year, month and day
|
||||
fields:
|
||||
@@ -1589,7 +1598,7 @@ defmodule Enum do
|
||||
|
||||
In the example above, `min/1` returned April 1st instead of March 31st
|
||||
because the structural comparison compares the day before the year. This
|
||||
can be addressed by using `min_by/1` and by relying on structures where
|
||||
can be addressed by using `min_by/3` and by relying on structures where
|
||||
the most significant digits come first. In this particular case, we can
|
||||
use `Date.to_erl/1` to get a tuple representation with year, month and day
|
||||
fields:
|
||||
@@ -1676,7 +1685,7 @@ defmodule Enum do
|
||||
first_fun = &{&1, &1}
|
||||
|
||||
reduce_fun = fn entry, {min, max} ->
|
||||
{Kernel.min(entry, min), Kernel.max(entry, max)}
|
||||
{Kernel.min(min, entry), Kernel.max(max, entry)}
|
||||
end
|
||||
|
||||
case reduce_by(enumerable, first_fun, reduce_fun) do
|
||||
@@ -1747,8 +1756,8 @@ defmodule Enum do
|
||||
`fun` returned a falsy value (`false` or `nil`).
|
||||
|
||||
The elements in both the returned lists are in the same relative order as they
|
||||
were in the original enumerable (if such enumerable was ordered, e.g., a
|
||||
list); see the examples below.
|
||||
were in the original enumerable (if such enumerable was ordered, like a
|
||||
list). See the examples below.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1766,7 +1775,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec split_with(t, (element -> any)) :: {list, list}
|
||||
@spec split_with(t, (element -> as_boolean(term))) :: {list, list}
|
||||
def split_with(enumerable, fun) do
|
||||
{acc1, acc2} =
|
||||
reduce(enumerable, {[], []}, fn entry, {acc1, acc2} ->
|
||||
@@ -1781,7 +1790,6 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use Enum.split_with/2 instead"
|
||||
def partition(enumerable, fun) do
|
||||
split_with(enumerable, fun)
|
||||
@@ -1830,7 +1838,7 @@ defmodule Enum do
|
||||
|
||||
def random(enumerable) do
|
||||
result =
|
||||
case backwards_compatible_slice(enumerable) do
|
||||
case Enumerable.slice(enumerable) do
|
||||
{:ok, 0, _} ->
|
||||
[]
|
||||
|
||||
@@ -1873,7 +1881,7 @@ defmodule Enum do
|
||||
24
|
||||
|
||||
"""
|
||||
@spec reduce(t, (element, any -> any)) :: any
|
||||
@spec reduce(t, (element, acc -> acc)) :: acc
|
||||
def reduce(enumerable, fun)
|
||||
|
||||
def reduce([h | t], fun) do
|
||||
@@ -2025,8 +2033,8 @@ defmodule Enum do
|
||||
|
||||
def reverse([]), do: []
|
||||
def reverse([_] = list), do: list
|
||||
def reverse([item1, item2]), do: [item2, item1]
|
||||
def reverse([item1, item2 | rest]), do: :lists.reverse(rest, [item2, item1])
|
||||
def reverse([element1, element2]), do: [element2, element1]
|
||||
def reverse([element1, element2 | rest]), do: :lists.reverse(rest, [element2, element1])
|
||||
def reverse(enumerable), do: reduce(enumerable, [], &[&1 | &2])
|
||||
|
||||
@doc """
|
||||
@@ -2150,7 +2158,7 @@ defmodule Enum do
|
||||
until element `index_range.last` (inclusively).
|
||||
|
||||
Indexes are normalized, meaning that negative indexes will be counted
|
||||
from the end (e.g. `-1` means the last element of the `enumerable`).
|
||||
from the end (for example, `-1` means the last element of the `enumerable`).
|
||||
|
||||
If `index_range.last` is out of bounds, then it is assigned as the index
|
||||
of the last element.
|
||||
@@ -2206,8 +2214,12 @@ defmodule Enum do
|
||||
with `amount` number of elements if available.
|
||||
|
||||
Given an `enumerable`, it drops elements right before element `start_index`,
|
||||
then takes `amount` of elements, returning as many elements as possible if there are not enough
|
||||
elements.
|
||||
then takes `amount` of elements, returning as many elements as possible if
|
||||
there are not enough elements.
|
||||
|
||||
A negative `start_index` can be passed, which means the `enumerable` is
|
||||
enumerated once and the index is counted from the end (for example,
|
||||
`-1` starts slicing from the last element).
|
||||
|
||||
It returns `[]` if `amount` is `0` or if `start_index` is out of bounds.
|
||||
|
||||
@@ -2223,7 +2235,11 @@ defmodule Enum do
|
||||
iex> Enum.slice(1..10, 5, 0)
|
||||
[]
|
||||
|
||||
# out of bound start index
|
||||
# using a negative start index
|
||||
iex> Enum.slice(1..10, -6, 3)
|
||||
[5, 6, 7]
|
||||
|
||||
# out of bound start index (positive)
|
||||
iex> Enum.slice(1..10, 10, 5)
|
||||
[]
|
||||
|
||||
@@ -2450,12 +2466,17 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the first `amount` items from the `enumerable`.
|
||||
Takes an `amount` of elements from the beginning or the end of the `enumerable`.
|
||||
|
||||
If a negative `amount` is given, the `amount` of last values will be taken.
|
||||
If a positive `amount` is given, it takes the `amount` elements from the
|
||||
beginning of the `enumerable`.
|
||||
|
||||
If a negative `amount` is given, the `amount` of elements will be taken from the end.
|
||||
The `enumerable` will be enumerated once to retrieve the proper index and
|
||||
the remaining calculation is performed from the end.
|
||||
|
||||
If amount is `0`, it returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.take([1, 2, 3], 2)
|
||||
@@ -2500,12 +2521,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of every `nth` item in the `enumerable`,
|
||||
Returns a list of every `nth` element in the `enumerable`,
|
||||
starting with the first element.
|
||||
|
||||
The first item is always included, unless `nth` is 0.
|
||||
The first element is always included, unless `nth` is 0.
|
||||
|
||||
The second argument specifying every `nth` item must be a non-negative
|
||||
The second argument specifying every `nth` element must be a non-negative
|
||||
integer.
|
||||
|
||||
## Examples
|
||||
@@ -2533,7 +2554,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes `count` random items from `enumerable`.
|
||||
Takes `count` random elements from `enumerable`.
|
||||
|
||||
Notice this function will traverse the whole `enumerable` to
|
||||
get the random sublist.
|
||||
@@ -2606,7 +2627,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the items from the beginning of the `enumerable` while `fun` returns
|
||||
Takes the elements from the beginning of the `enumerable` while `fun` returns
|
||||
a truthy value.
|
||||
|
||||
## Examples
|
||||
@@ -2663,7 +2684,6 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use Enum.uniq_by/2 instead"
|
||||
def uniq(enumerable, fun) do
|
||||
uniq_by(enumerable, fun)
|
||||
@@ -2671,7 +2691,7 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Enumerates the `enumerable`, by removing the elements for which
|
||||
function `fun` returned duplicate items.
|
||||
function `fun` returned duplicate elements.
|
||||
|
||||
The function `fun` maps every element to a term. Two elements are
|
||||
considered duplicates if the return value of `fun` is equal for
|
||||
@@ -2703,7 +2723,7 @@ defmodule Enum do
|
||||
Opposite of `zip/2`. Extracts two-element tuples from the
|
||||
given `enumerable` and groups them together.
|
||||
|
||||
It takes an `enumerable` with items being two-element tuples and returns
|
||||
It takes an `enumerable` with elements being two-element tuples and returns
|
||||
a tuple with two lists, each of which is formed by the first and
|
||||
second element of each tuple, respectively.
|
||||
|
||||
@@ -2793,9 +2813,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec zip([t]) :: t
|
||||
@spec zip(t) :: t
|
||||
|
||||
@spec zip(enumerables) :: [tuple()] when enumerables: [t()] | t()
|
||||
def zip([]), do: []
|
||||
|
||||
def zip(enumerables) do
|
||||
@@ -2813,7 +2831,7 @@ defmodule Enum do
|
||||
defp entry_to_string(entry), do: String.Chars.to_string(entry)
|
||||
|
||||
defp aggregate([head | tail], fun, _empty) do
|
||||
:lists.foldl(fun, head, tail)
|
||||
aggregate_list(tail, head, fun)
|
||||
end
|
||||
|
||||
defp aggregate([], _fun, empty) do
|
||||
@@ -2830,7 +2848,7 @@ defmodule Enum do
|
||||
enumerable
|
||||
|> reduce(ref, fn
|
||||
element, ^ref -> element
|
||||
element, acc -> fun.(element, acc)
|
||||
element, acc -> fun.(acc, element)
|
||||
end)
|
||||
|> case do
|
||||
^ref -> empty.()
|
||||
@@ -2838,6 +2856,9 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
defp aggregate_list([head | tail], acc, fun), do: aggregate_list(tail, fun.(acc, head), fun)
|
||||
defp aggregate_list([], acc, _fun), do: acc
|
||||
|
||||
defp reduce_by([head | tail], first, fun) do
|
||||
:lists.foldl(fun, first.(head), tail)
|
||||
end
|
||||
@@ -2865,19 +2886,6 @@ defmodule Enum do
|
||||
lower_limit + :rand.uniform(upper_limit - lower_limit + 1) - 1
|
||||
end
|
||||
|
||||
# TODO: Remove me on Elixir v1.9
|
||||
defp backwards_compatible_slice(args) do
|
||||
try do
|
||||
Enumerable.slice(args)
|
||||
catch
|
||||
:error, :undef ->
|
||||
case __STACKTRACE__ do
|
||||
[{module, :slice, [^args], _} | _] -> {:error, module}
|
||||
stack -> :erlang.raise(:error, :undef, stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Implementations
|
||||
|
||||
## all?
|
||||
@@ -3072,7 +3080,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp slice_any(enumerable, start, amount) do
|
||||
case backwards_compatible_slice(enumerable) do
|
||||
case Enumerable.slice(enumerable) do
|
||||
{:ok, count, _} when start >= count ->
|
||||
[]
|
||||
|
||||
@@ -3105,7 +3113,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp slice_count_and_fun(enumerable) do
|
||||
case backwards_compatible_slice(enumerable) do
|
||||
case Enumerable.slice(enumerable) do
|
||||
{:ok, count, fun} when is_function(fun) ->
|
||||
{count, fun}
|
||||
|
||||
|
||||
@@ -208,7 +208,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.scope(meta)
|
||||
scope = :elixir_erl.scope(meta, true)
|
||||
|
||||
{erl_args, scope} =
|
||||
:elixir_erl_clauses.match(&:elixir_erl_pass.translate_args/2, ex_args, scope)
|
||||
@@ -1148,10 +1148,23 @@ defmodule Protocol.UndefinedError do
|
||||
|
||||
@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)
|
||||
"protocol #{inspect(protocol)} not implemented for #{inspect(value)} of type " <>
|
||||
value_type(value) <> maybe_description(description) <> maybe_available(protocol)
|
||||
end
|
||||
|
||||
defp value_type(%{__struct__: struct}), do: "#{inspect(struct)} (a struct)"
|
||||
defp value_type(value) when is_atom(value), do: "Atom"
|
||||
defp value_type(value) when is_bitstring(value), do: "BitString"
|
||||
defp value_type(value) when is_float(value), do: "Float"
|
||||
defp value_type(value) when is_function(value), do: "Function"
|
||||
defp value_type(value) when is_integer(value), do: "Integer"
|
||||
defp value_type(value) when is_list(value), do: "List"
|
||||
defp value_type(value) when is_map(value), do: "Map"
|
||||
defp value_type(value) when is_pid(value), do: "PID"
|
||||
defp value_type(value) when is_port(value), do: "Port"
|
||||
defp value_type(value) when is_reference(value), do: "Reference"
|
||||
defp value_type(value) when is_tuple(value), do: "Tuple"
|
||||
|
||||
defp maybe_description(""), do: ""
|
||||
defp maybe_description(description), do: ", " <> description
|
||||
|
||||
@@ -1161,7 +1174,8 @@ defmodule Protocol.UndefinedError do
|
||||
". There are no implementations for this protocol."
|
||||
|
||||
{:consolidated, types} ->
|
||||
". This protocol is implemented for: #{Enum.map_join(types, ", ", &inspect/1)}"
|
||||
". This protocol is implemented for the following type(s): " <>
|
||||
Enum.map_join(types, ", ", &inspect/1)
|
||||
|
||||
:not_consolidated ->
|
||||
""
|
||||
@@ -1305,6 +1319,24 @@ defmodule File.CopyError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.RenameError do
|
||||
defexception [:reason, :source, :destination, on: "", action: ""]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
location =
|
||||
case exception.on() do
|
||||
"" -> ""
|
||||
on -> ". #{on}"
|
||||
end
|
||||
|
||||
"could not #{exception.action} from #{inspect(exception.source)} to " <>
|
||||
"#{inspect(exception.destination)}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.LinkError do
|
||||
defexception [:reason, :existing, :new, action: ""]
|
||||
|
||||
|
||||
+69
-33
@@ -30,7 +30,7 @@ defmodule File do
|
||||
always treated as UTF-8. In particular, we expect that the
|
||||
shell and the operating system are configured to use UTF-8
|
||||
encoding. Binary filenames are considered raw and passed
|
||||
to the OS as is.
|
||||
to the operating system as is.
|
||||
|
||||
## API
|
||||
|
||||
@@ -373,6 +373,8 @@ defmodule File do
|
||||
machine
|
||||
* `:posix` - returns the time as integer seconds since epoch
|
||||
|
||||
Note: Since file times are stored in POSIX time format on most operating systems,
|
||||
it is faster to retrieve file information with the `time: :posix` option.
|
||||
"""
|
||||
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
|
||||
def stat(path, opts \\ []) do
|
||||
@@ -424,6 +426,8 @@ defmodule File do
|
||||
* `:local` - returns a `{date, time}` tuple using the machine time
|
||||
* `:posix` - returns the time as integer seconds since epoch
|
||||
|
||||
Note: Since file times are stored in POSIX time format on most operating systems,
|
||||
it is faster to retrieve file information with the `time: :posix` option.
|
||||
"""
|
||||
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
|
||||
def lstat(path, opts \\ []) do
|
||||
@@ -532,6 +536,12 @@ defmodule File do
|
||||
(as returned by `:erlang.universaltime()`) or an integer
|
||||
representing the POSIX timestamp (as returned by `System.os_time(:second)`).
|
||||
|
||||
In Unix-like systems, changing the modification time may require
|
||||
you to be either `root` or the owner of the file. Having write
|
||||
access may not be enough. In those cases, touching the file the
|
||||
first time (to create it) will succeed, but touching an existing
|
||||
file with fail with `{:error, :eperm}`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.touch("/tmp/a.txt", {{2018, 1, 30}, {13, 59, 59}})
|
||||
@@ -712,8 +722,8 @@ defmodule File do
|
||||
|
||||
Returns `:ok` in case of success, `{:error, reason}` otherwise.
|
||||
|
||||
Note: The command `mv` in Unix systems behaves differently depending
|
||||
if `source` is a file and the `destination` is an existing directory.
|
||||
Note: The command `mv` in Unix systems behaves differently depending on
|
||||
whether `source` is a file and the `destination` is an existing directory.
|
||||
We have chosen to explicitly disallow this behaviour.
|
||||
|
||||
## Examples
|
||||
@@ -731,30 +741,54 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
The same as `rename/2` but raises a `File.RenameError` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec rename!(Path.t(), Path.t()) :: :ok
|
||||
def rename!(source, destination) do
|
||||
case rename(source, destination) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.RenameError,
|
||||
reason: reason,
|
||||
action: "rename",
|
||||
source: IO.chardata_to_string(source),
|
||||
destination: IO.chardata_to_string(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents in `source_file` to `destination_file` preserving its modes.
|
||||
|
||||
`source_file` and `destination_file` must be a file or a symbolic link to one,
|
||||
or in the case of destination, a path to a non-existent file. If either one of
|
||||
them is a directory, `{:error, :eisdir}` will be returned.
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. The callback defaults to return `true`.
|
||||
|
||||
The function returns `:ok` in case of success, returns
|
||||
`{:error, reason}` otherwise.
|
||||
The function returns `:ok` in case of success. Otherwise, it returns
|
||||
`{:error, reason}`.
|
||||
|
||||
If you want to copy contents from an IO device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `copy/3` instead.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently depending
|
||||
if `destination` is an existing directory or not. We have chosen to
|
||||
explicitly disallow this behaviour. If destination is a directory, an
|
||||
error will be returned.
|
||||
Note: The command `cp` in Unix systems behaves differently depending on
|
||||
whether the destination is an existing directory or not. We have chosen to
|
||||
explicitly disallow copying to a destination which is a directory,
|
||||
and an error will be returned if tried.
|
||||
"""
|
||||
@spec cp(Path.t(), Path.t(), (Path.t(), Path.t() -> boolean)) :: :ok | {:error, posix}
|
||||
def cp(source, destination, callback \\ fn _, _ -> true end) do
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
def cp(source_file, destination_file, callback \\ fn _, _ -> true end) do
|
||||
source_file = IO.chardata_to_string(source_file)
|
||||
destination_file = IO.chardata_to_string(destination_file)
|
||||
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
case do_cp_file(source_file, destination_file, callback, []) do
|
||||
{:error, reason, _} -> {:error, reason}
|
||||
_ -> :ok
|
||||
end
|
||||
@@ -771,8 +805,8 @@ defmodule File do
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
@spec cp!(Path.t(), Path.t(), (Path.t(), Path.t() -> boolean)) :: :ok
|
||||
def cp!(source, destination, callback \\ fn _, _ -> true end) do
|
||||
case cp(source, destination, callback) do
|
||||
def cp!(source_file, destination_file, callback \\ fn _, _ -> true end) do
|
||||
case cp(source_file, destination_file, callback) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
@@ -780,26 +814,29 @@ defmodule File do
|
||||
raise File.CopyError,
|
||||
reason: reason,
|
||||
action: "copy",
|
||||
source: IO.chardata_to_string(source),
|
||||
destination: IO.chardata_to_string(destination)
|
||||
source: IO.chardata_to_string(source_file),
|
||||
destination: IO.chardata_to_string(destination_file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Copies the contents in source to destination.
|
||||
Copies the contents in `source` to `destination` recursively, maintaining the
|
||||
source directory structure and modes.
|
||||
|
||||
If `source` is a file or a symbolic link to it, `destination` must be a path
|
||||
to an existent file, a symbolic link to one, or a path to a non-existent file.
|
||||
|
||||
If `source` is a directory, or a symbolic link to it, then `destination` must
|
||||
be an existent `directory` or a symbolic link to one, or a path to a non-existent directory.
|
||||
|
||||
If the source is a file, it copies `source` to
|
||||
`destination`. If the source is a directory, it copies
|
||||
the contents inside source into the destination.
|
||||
`destination`. If the `source` is a directory, it copies
|
||||
the contents inside source into the `destination` directory.
|
||||
|
||||
If a file already exists in the destination, it invokes `callback`.
|
||||
`callback` must be a function that takes two arguments: `source` and `destination`.
|
||||
The callback should return `true` if the existing file should be overwritten and `false` otherwise.
|
||||
|
||||
If a directory already exists in the destination
|
||||
where a file is meant to be (or vice versa), this
|
||||
function will fail.
|
||||
|
||||
This function may fail while copying files,
|
||||
in such cases, it will leave the destination
|
||||
directory in a dirty state, where file which have already been copied
|
||||
@@ -809,9 +846,10 @@ defmodule File do
|
||||
success, `files_and_directories` lists all files and directories copied in no
|
||||
specific order. It returns `{:error, reason, file}` otherwise.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently
|
||||
depending if `destination` is an existing directory or not.
|
||||
We have chosen to explicitly disallow this behaviour.
|
||||
Note: The command `cp` in Unix systems behaves differently depending on
|
||||
whether `destination` is an existing directory or not. We have chosen to
|
||||
explicitly disallow this behaviour. If `source` is a `file` and `destination`
|
||||
is a directory, `{:error, :eisdir}` will be returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -866,8 +904,6 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
@@ -1655,7 +1691,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the group given by the group id `gid`
|
||||
Changes the group given by the group ID `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
@@ -1683,7 +1719,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `uid`
|
||||
Changes the owner given by the user ID `uid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@@ -1733,7 +1769,7 @@ defmodule File do
|
||||
[read_ahead: @read_ahead_size] ++ normalize_modes(rest, binary?)
|
||||
end
|
||||
|
||||
# TODO: Remove :char_list mode by 2.0
|
||||
# TODO: Remove :char_list mode on v2.0
|
||||
defp normalize_modes([mode | rest], _binary?) when mode in [:charlist, :char_list] do
|
||||
if mode == :char_list do
|
||||
IO.warn("the :char_list mode is deprecated, use :charlist")
|
||||
|
||||
@@ -63,9 +63,9 @@ defmodule File.Stat do
|
||||
size: non_neg_integer(),
|
||||
type: :device | :directory | :regular | :other | :symlink,
|
||||
access: :read | :write | :read_write | :none,
|
||||
atime: :calendar.datetime(),
|
||||
mtime: :calendar.datetime(),
|
||||
ctime: :calendar.datetime(),
|
||||
atime: :calendar.datetime() | integer(),
|
||||
mtime: :calendar.datetime() | integer(),
|
||||
ctime: :calendar.datetime() | integer(),
|
||||
mode: non_neg_integer(),
|
||||
links: non_neg_integer(),
|
||||
major_device: non_neg_integer(),
|
||||
@@ -78,6 +78,7 @@ defmodule File.Stat do
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
@spec to_record(t()) :: :file.file_info()
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
@@ -85,6 +86,7 @@ defmodule File.Stat do
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
@spec from_record(:file.file_info()) :: t()
|
||||
def from_record(file_info)
|
||||
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
|
||||
+6
-11
@@ -36,7 +36,7 @@ defmodule Float do
|
||||
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/)
|
||||
* [What Every Programmer Should Know About Floating-Point Arithmetic](https://floating-point-gui.de/)
|
||||
|
||||
"""
|
||||
|
||||
@@ -268,16 +268,14 @@ defmodule Float do
|
||||
raise ArgumentError, invalid_precision_message(precision)
|
||||
end
|
||||
|
||||
defp round(0.0, _precision, _rounding), do: 0.0
|
||||
|
||||
defp round(float, precision, rounding) do
|
||||
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
|
||||
{num, count, _} = decompose(significant, 1)
|
||||
count = count - exp + 1023
|
||||
|
||||
cond do
|
||||
# There is no decimal precision on subnormal floats
|
||||
count <= 0 or exp == 0 ->
|
||||
float
|
||||
|
||||
# Precision beyond 15 digits
|
||||
count >= 104 ->
|
||||
case rounding do
|
||||
@@ -307,7 +305,7 @@ defmodule Float do
|
||||
num = rounding(rounding, sign, num, div)
|
||||
|
||||
# Convert back to float without loss
|
||||
# http://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
|
||||
# https://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
|
||||
den = power_of_10(precision)
|
||||
boundary = den <<< 52
|
||||
|
||||
@@ -444,11 +442,11 @@ defmodule Float do
|
||||
{acc, last_count, last_power}
|
||||
end
|
||||
|
||||
@compile {:inline, sign: 2, shift_left: 2}
|
||||
defp sign(0, num), do: num
|
||||
defp sign(1, num), do: -num
|
||||
|
||||
defp shift_left(num, 0), do: num
|
||||
defp shift_left(num, times), do: shift_left(num <<< 1, times - 1)
|
||||
defp shift_left(num, times), do: num <<< times
|
||||
|
||||
defp shift_right(num, 0), do: {num, 0}
|
||||
defp shift_right(1, times), do: {1, times}
|
||||
@@ -495,19 +493,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@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
|
||||
@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
|
||||
@deprecated "Use :erlang.float_to_binary/2 instead"
|
||||
def to_string(float, options) do
|
||||
:erlang.float_to_binary(float, expand_compact(options))
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
defmodule GenEvent do
|
||||
# TODO: Remove by 2.0
|
||||
|
||||
# Functions from this module are deprecated in elixir_dispatch.
|
||||
|
||||
@moduledoc """
|
||||
@@ -90,12 +88,10 @@ defmodule GenEvent do
|
||||
@deprecated message
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
%{file: file, line: line} = __CALLER__
|
||||
|
||||
deprecation_message =
|
||||
"the GenEvent module is deprecated, see its documentation for alternatives"
|
||||
|
||||
:elixir_errors.warn(line, file, deprecation_message)
|
||||
IO.warn(deprecation_message, Macro.Env.stacktrace(__CALLER__))
|
||||
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
@@ -328,14 +324,7 @@ defmodule GenEvent do
|
||||
|
||||
def init_it(starter, parent, name, _mod, _args, options) do
|
||||
Process.put(:"$initial_call", {__MODULE__, :init_it, 6})
|
||||
|
||||
debug =
|
||||
if function_exported?(:gen, :debug_options, 2) do
|
||||
:gen.debug_options(name, options)
|
||||
else
|
||||
:gen.debug_options(options)
|
||||
end
|
||||
|
||||
debug = :gen.debug_options(name, options)
|
||||
:proc_lib.init_ack(starter, {:ok, self()})
|
||||
loop(parent, name(name), [], debug, false)
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule GenServer do
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
Imagine we want a GenServer that works like a stack, allowing us to push
|
||||
and pop items:
|
||||
and pop elements:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
@@ -34,8 +34,8 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
def handle_cast({:push, element}, state) do
|
||||
{:noreply, [element | state]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ defmodule GenServer do
|
||||
|
||||
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
|
||||
representing the stack containing the element `:hello`). We can primarily
|
||||
interact with the server by sending two types of messages. **call**
|
||||
messages expect a reply from the server (and are therefore synchronous)
|
||||
while **cast** messages do not.
|
||||
@@ -63,7 +63,7 @@ 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`. There are 7 possible
|
||||
callbacks to be implemented when you use a `GenServer`. The only required
|
||||
callback is `init/1`.
|
||||
callback is `c:init/1`.
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
@@ -83,8 +83,8 @@ defmodule GenServer do
|
||||
GenServer.start_link(__MODULE__, default)
|
||||
end
|
||||
|
||||
def push(pid, item) do
|
||||
GenServer.cast(pid, {:push, item})
|
||||
def push(pid, element) do
|
||||
GenServer.cast(pid, {:push, element})
|
||||
end
|
||||
|
||||
def pop(pid) do
|
||||
@@ -104,8 +104,8 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
def handle_cast({:push, element}, state) do
|
||||
{:noreply, [element | state]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,14 +135,13 @@ defmodule GenServer do
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
In both cases, `Stack.start_link/1` is alwaus invoked.
|
||||
In both cases, `Stack.start_link/1` is always invoked.
|
||||
|
||||
`use GenServer` also accepts a list of options which configures the
|
||||
child specification and therefore how it runs under a supervisor.
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `: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, either immediately or by giving it time to shut down
|
||||
|
||||
@@ -234,7 +233,7 @@ defmodule GenServer do
|
||||
@impl true
|
||||
def handle_info(:work, state) do
|
||||
# Do the desired work here
|
||||
...
|
||||
# ...
|
||||
|
||||
# Reschedule once more
|
||||
schedule_work()
|
||||
@@ -248,6 +247,22 @@ defmodule GenServer do
|
||||
end
|
||||
end
|
||||
|
||||
## Timeouts
|
||||
|
||||
The return value of `c:init/1` or any of the `handle_*` callbacks may include
|
||||
a timeout value in milliseconds; if not, `:infinity` is assumed.
|
||||
The timeout can be used to detect a lull in incoming messages.
|
||||
|
||||
If the process has no messages waiting when the timeout is set and the
|
||||
number of given milliseconds pass without any message arriving,
|
||||
then `handle_info/2` will be called with `:timeout` as the first argument.
|
||||
The timeout is cleared if any message is waiting or arrives before the
|
||||
given timeout.
|
||||
|
||||
Because a message may arrive before the timeout is set, even a timeout of `0`
|
||||
milliseconds is not guaranteed to execute. To take another action immediately
|
||||
and unconditionally, use a `:continue` instruction.
|
||||
|
||||
## When (not) to use a GenServer
|
||||
|
||||
So far, we have learned that a `GenServer` can be used as a supervised process
|
||||
@@ -396,9 +411,9 @@ defmodule GenServer do
|
||||
Returning `{:ok, state}` will cause `start_link/3` to return
|
||||
`{:ok, pid}` and the process to enter its loop.
|
||||
|
||||
Returning `{:ok, state, timeout}` is similar to `{:ok, state}`
|
||||
except `handle_info(:timeout, state)` will be called after `timeout`
|
||||
milliseconds if no messages are received within the timeout.
|
||||
Returning `{:ok, state, timeout}` is similar to `{:ok, state}`,
|
||||
except that it also sets a timeout. See the "Timeouts" section
|
||||
in the module documentation for more information.
|
||||
|
||||
Returning `{:ok, state, :hibernate}` is similar to `{:ok, state}`
|
||||
except the process is hibernated before entering the loop. See
|
||||
@@ -447,8 +462,8 @@ defmodule GenServer do
|
||||
caller and continues the loop with new state `new_state`.
|
||||
|
||||
Returning `{:reply, reply, new_state, timeout}` is similar to
|
||||
`{:reply, reply, new_state}` except `handle_info(:timeout, new_state)` will be
|
||||
called after `timeout` milliseconds if no messages are received.
|
||||
`{:reply, reply, new_state}` except that it also sets a timeout.
|
||||
See the "Timeouts" section in the module documentation for more information.
|
||||
|
||||
Returning `{:reply, reply, new_state, :hibernate}` is similar to
|
||||
`{:reply, reply, new_state}` except the process is hibernated and will
|
||||
@@ -513,9 +528,9 @@ defmodule GenServer do
|
||||
|
||||
Returning `{:noreply, new_state}` continues the loop with new state `new_state`.
|
||||
|
||||
Returning `{:noreply, new_state, timeout}` is similar to
|
||||
`{:noreply, new_state}` except `handle_info(:timeout, new_state)` will be
|
||||
called after `timeout` milliseconds if no messages are received.
|
||||
Returning `{:noreply, new_state, timeout}` is similar to `{:noreply, new_state}`
|
||||
except that it also sets a timeout. See the "Timeouts" section in the module
|
||||
documentation for more information.
|
||||
|
||||
Returning `{:noreply, new_state, :hibernate}` is similar to
|
||||
`{:noreply, new_state}` except the process is hibernated before continuing the
|
||||
@@ -590,9 +605,9 @@ defmodule GenServer do
|
||||
* the `GenServer` traps exits (using `Process.flag/2`) *and* the parent
|
||||
process sends an exit signal
|
||||
|
||||
If part of a supervision tree, a `GenServer`'s will receive an exit
|
||||
If part of a supervision tree, a `GenServer` will receive an exit
|
||||
signal when the tree is shutting down. The exit signal is based on
|
||||
the shutdown strategy in the child's specification, where we this
|
||||
the shutdown strategy in the child's specification, where this
|
||||
value can be:
|
||||
|
||||
* `:brutal_kill`: the `GenServer` is killed and so `c:terminate/2` is not called.
|
||||
@@ -651,8 +666,7 @@ defmodule GenServer do
|
||||
@callback code_change(old_vsn, state :: term, extra :: term) ::
|
||||
{:ok, new_state :: term}
|
||||
| {:error, reason :: term}
|
||||
| {:down, term}
|
||||
when old_vsn: term
|
||||
when old_vsn: term | {:down, term}
|
||||
|
||||
@doc """
|
||||
Invoked in some cases to retrieve a formatted version of the `GenServer` status.
|
||||
@@ -702,7 +716,12 @@ defmodule GenServer do
|
||||
@typedoc "Debug options supported by the `start*` functions"
|
||||
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t()}]
|
||||
|
||||
@typedoc "The server reference"
|
||||
@typedoc """
|
||||
The server reference.
|
||||
|
||||
This is either a plain PID or a value representing a registered name.
|
||||
See the "Name registration" section of this document for more information.
|
||||
"""
|
||||
@type server :: pid | name | {atom, node}
|
||||
|
||||
@typedoc """
|
||||
@@ -737,7 +756,7 @@ defmodule GenServer do
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
# TODO: Remove this on Elixir v2.0
|
||||
# TODO: Remove this on v2.0
|
||||
@before_compile GenServer
|
||||
|
||||
@doc false
|
||||
@@ -819,7 +838,7 @@ defmodule GenServer do
|
||||
the arguments given to GenServer.start_link/3 to the server state.
|
||||
"""
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -973,7 +992,8 @@ defmodule GenServer do
|
||||
element.
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
def call(server, request, timeout \\ 5000)
|
||||
when (is_integer(timeout) and timeout >= 0) or timeout == :infinity do
|
||||
case whereis(server) do
|
||||
nil ->
|
||||
exit({:noproc, {__MODULE__, :call, [server, request, timeout]}})
|
||||
@@ -1006,6 +1026,9 @@ defmodule GenServer do
|
||||
not yet connected to the caller one, the semantics differ
|
||||
depending on the used Erlang/OTP version.
|
||||
|
||||
`server` can be any of the values described in the "Name registration"
|
||||
section of the documentation for this module.
|
||||
|
||||
Before Erlang/OTP 21, the call is going to block until a
|
||||
connection happens. This was done to guarantee ordering.
|
||||
Starting with Erlang/OTP 21, both Erlang and Elixir do
|
||||
|
||||
@@ -7,8 +7,6 @@ defmodule HashDict do
|
||||
|
||||
@moduledoc deprecated: "Use Map instead"
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
|
||||
use Dict
|
||||
|
||||
@node_bitmap 0b111
|
||||
|
||||
@@ -31,7 +31,7 @@ defprotocol Inspect do
|
||||
end
|
||||
end
|
||||
|
||||
The `concat/1` function comes from `Inspect.Algebra` and it
|
||||
The [`concat/1`](`Inspect.Algebra.concat/1`) function comes from `Inspect.Algebra` and it
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"MapSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
@@ -77,6 +77,15 @@ defprotocol Inspect do
|
||||
# Handle structs in Any
|
||||
@fallback_to_any true
|
||||
|
||||
@doc """
|
||||
Converts `term` into an algebra document.
|
||||
|
||||
This function shouldn't be invoked directly, unless when implementing
|
||||
a custom `inspect_fun` to be given to `Inspect.Opts`. Everywhere else,
|
||||
`Inspect.Algebra.to_doc/2` should be preferred as it handles structs
|
||||
and exceptions.
|
||||
"""
|
||||
@spec inspect(t, Inspect.Opts.t()) :: Inspect.Algebra.t()
|
||||
def inspect(term, opts)
|
||||
end
|
||||
|
||||
@@ -160,7 +169,7 @@ defimpl Inspect, for: List do
|
||||
color("[]", :list, opts)
|
||||
end
|
||||
|
||||
# TODO: Remove :char_list and :as_char_lists handling in 2.0
|
||||
# TODO: Remove :char_list and :as_char_lists handling on v2.0
|
||||
def inspect(term, opts) do
|
||||
%Inspect.Opts{
|
||||
charlists: lists,
|
||||
@@ -307,11 +316,20 @@ end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, opts) do
|
||||
{escaped, _} = Identifier.escape(regex.source, ?/, :infinity, &escape_map/1)
|
||||
{escaped, _} =
|
||||
regex.source
|
||||
|> normalize(<<>>)
|
||||
|> Identifier.escape(?/, :infinity, &escape_map/1)
|
||||
|
||||
source = IO.iodata_to_binary(['~r/', escaped, ?/, regex.opts])
|
||||
color(source, :regex, opts)
|
||||
end
|
||||
|
||||
defp normalize(<<?\\, ?\\, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?\\, ?\\>>)
|
||||
defp normalize(<<?\\, ?/, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?/>>)
|
||||
defp normalize(<<char, rest::binary>>, acc), do: normalize(rest, <<acc::binary, char>>)
|
||||
defp normalize(<<>>, acc), do: acc
|
||||
|
||||
defp escape_map(?\a), do: '\\a'
|
||||
defp escape_map(?\f), do: '\\f'
|
||||
defp escape_map(?\n), do: '\\n'
|
||||
@@ -415,8 +433,7 @@ defimpl Inspect, for: Any do
|
||||
defimpl Inspect, for: unquote(module) do
|
||||
def inspect(struct, opts) do
|
||||
map = Map.take(struct, unquote(filtered_fields))
|
||||
colorless_opts = %{opts | syntax_colors: []}
|
||||
name = Inspect.Atom.inspect(unquote(module), colorless_opts)
|
||||
name = Identifier.inspect_as_atom(unquote(module))
|
||||
unquote(inspect_module).inspect(map, name, opts)
|
||||
end
|
||||
end
|
||||
@@ -432,8 +449,7 @@ defimpl Inspect, for: Any do
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(struct) do
|
||||
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, struct))
|
||||
colorless_opts = %{opts | syntax_colors: []}
|
||||
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(module, colorless_opts), opts)
|
||||
Inspect.Map.inspect(pruned, Identifier.inspect_as_atom(module), opts)
|
||||
else
|
||||
Inspect.Map.inspect(struct, opts)
|
||||
end
|
||||
|
||||
@@ -13,7 +13,8 @@ defmodule Inspect.Opts do
|
||||
When `:as_binaries` all binaries will be printed in bit syntax.
|
||||
|
||||
When the default `:infer`, the binary will be printed as a string if it
|
||||
is printable, otherwise in bit syntax.
|
||||
is printable, otherwise in bit syntax. See `String.printable?/1` to learn
|
||||
when a string is printable.
|
||||
|
||||
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
|
||||
non-printable elements will be escaped.
|
||||
@@ -21,16 +22,20 @@ defmodule Inspect.Opts do
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
When the default `:infer`, the list will be printed as a charlist if it
|
||||
is printable, otherwise as list.
|
||||
is printable, otherwise as list. See `List.ascii_printable?/1` to learn
|
||||
when a charlist is printable.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples,
|
||||
* `:limit` - limits the number of items that are inspected for tuples,
|
||||
bitstrings, maps, lists and any other collection of items. It does not
|
||||
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`.
|
||||
apply to printable strings nor printable 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 charlists. Defaults to 4096. If you don't want to limit the number of items
|
||||
to a particular number, use `:infinity`.
|
||||
* `:printable_limit` - limits the number of characters that are inspected
|
||||
on printable strings and printable charlists. You can use `String.printable?/1`
|
||||
and `List.ascii_printable?/1` to check if a given string or charlist is
|
||||
printable. Defaults to 4096. If you don't want to limit the number of
|
||||
characters to a particular number, use `:infinity`.
|
||||
|
||||
* `:pretty` - if set to `true` enables pretty printing, defaults to `false`.
|
||||
|
||||
@@ -46,17 +51,24 @@ defmodule Inspect.Opts do
|
||||
* `:safe` - when `false`, failures while inspecting structs will be raised
|
||||
as errors instead of being wrapped in the `Inspect.Error` exception. This
|
||||
is useful when debugging failures and crashes for custom inspect
|
||||
implementations
|
||||
implementations.
|
||||
|
||||
* `:syntax_colors` - when set to a keyword list of colors the output will
|
||||
be colorized. The keys are types and the values are the colors to use for
|
||||
* `:syntax_colors` - when set to a keyword list of colors the output is
|
||||
colorized. The keys are types and the values are the colors to use for
|
||||
each type (for example, `[number: :red, atom: :blue]`). Types can include
|
||||
`:number`, `:atom`, `regex`, `:tuple`, `:map`, `:list`, and `:reset`.
|
||||
Colors can be any `t:IO.ANSI.ansidata/0` as accepted by `IO.ANSI.format/1`.
|
||||
|
||||
* `:inspect_fun` (since v1.9.0) - a function to build algebra documents,
|
||||
defaults to `Inspect.inspect/2`
|
||||
|
||||
* `:custom_options` (since v1.9.0) - a keyword list storing custom user-defined
|
||||
options. Useful when implementing the `Inspect` protocol for nested structs
|
||||
to pass the custom options through.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Remove :char_lists key by 2.0
|
||||
# TODO: Remove :char_lists key on v2.0
|
||||
defstruct structs: true,
|
||||
binaries: :infer,
|
||||
charlists: :infer,
|
||||
@@ -67,11 +79,13 @@ defmodule Inspect.Opts do
|
||||
base: :decimal,
|
||||
pretty: false,
|
||||
safe: true,
|
||||
syntax_colors: []
|
||||
syntax_colors: [],
|
||||
inspect_fun: &Inspect.inspect/2,
|
||||
custom_options: []
|
||||
|
||||
@type color_key :: atom
|
||||
|
||||
# TODO: Remove :char_lists key and :as_char_lists value by 2.0
|
||||
# TODO: Remove :char_lists key and :as_char_lists value on v2.0
|
||||
@type t :: %__MODULE__{
|
||||
structs: boolean,
|
||||
binaries: :infer | :as_binaries | :as_strings,
|
||||
@@ -83,7 +97,9 @@ defmodule Inspect.Opts do
|
||||
base: :decimal | :binary | :hex | :octal,
|
||||
pretty: boolean,
|
||||
safe: boolean,
|
||||
syntax_colors: [{color_key, IO.ANSI.ansidata()}]
|
||||
syntax_colors: [{color_key, IO.ANSI.ansidata()}],
|
||||
inspect_fun: (any, t -> Inspect.Algebra.t()),
|
||||
custom_options: keyword
|
||||
}
|
||||
end
|
||||
|
||||
@@ -257,10 +273,10 @@ defmodule Inspect.Algebra do
|
||||
@spec to_doc(any, Inspect.Opts.t()) :: t
|
||||
def to_doc(term, opts)
|
||||
|
||||
def to_doc(%_{} = struct, %Inspect.Opts{} = opts) do
|
||||
def to_doc(%_{} = struct, %Inspect.Opts{inspect_fun: fun} = opts) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(struct, opts)
|
||||
fun.(struct, opts)
|
||||
rescue
|
||||
caught_exception ->
|
||||
# Because we try to raise a nice error message in case
|
||||
@@ -299,8 +315,8 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) do
|
||||
Inspect.inspect(arg, opts)
|
||||
def to_doc(arg, %Inspect.Opts{inspect_fun: fun} = opts) do
|
||||
fun.(arg, opts)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -410,14 +426,12 @@ defmodule Inspect.Algebra do
|
||||
defp simple?(:doc_nil), do: true
|
||||
defp simple?(other), do: is_binary(other)
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
@doc false
|
||||
@deprecated "Use a combination of concat/2 and nest/2 instead"
|
||||
def surround(left, doc, right) when is_doc(left) and is_doc(doc) and is_doc(right) do
|
||||
concat(concat(left, nest(doc, 1)), right)
|
||||
end
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
@doc false
|
||||
@deprecated "Use Inspect.Algebra.container_doc/6 instead"
|
||||
def surround_many(
|
||||
@@ -686,7 +700,7 @@ defmodule Inspect.Algebra do
|
||||
to the document fitting. On the other hand, they are more expensive
|
||||
since each break needs to be re-evaluated.
|
||||
|
||||
This function is used by `container_doc/4` and friends to the
|
||||
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"
|
||||
@@ -809,7 +823,7 @@ defmodule Inspect.Algebra do
|
||||
@doc ~S"""
|
||||
Inserts a mandatory linebreak between two documents.
|
||||
|
||||
See `line/1`.
|
||||
See `line/0`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -882,10 +896,11 @@ defmodule Inspect.Algebra do
|
||||
# * flat_no_break - represents a document with breaks as flat not allowed to enter in break mode
|
||||
# * break_no_flat - represents a document with breaks as breaks not allowed to enter in flat mode
|
||||
#
|
||||
@typep mode :: :flat | :flat_no_break | :break
|
||||
@typep mode :: :flat | :flat_no_break | :break | :break_no_flat
|
||||
|
||||
@spec fits?(width :: integer(), column :: integer(), break? :: boolean(), entries) :: boolean()
|
||||
when entries: [{integer(), mode(), t()}] | {:tail, boolean(), entries}
|
||||
when entries:
|
||||
maybe_improper_list({integer(), mode(), t()}, {:tail, boolean(), entries} | [])
|
||||
|
||||
# We need at least a break to consider the document does not fit since a
|
||||
# large document without breaks has no option but fitting its current line.
|
||||
|
||||
@@ -269,6 +269,9 @@ defmodule Integer do
|
||||
|
||||
defp count_digits_nosign(<<_::binary>>, _, count), do: count
|
||||
|
||||
# TODO: Remove Integer.to_string/1 once the minimum supported version is
|
||||
# Erlang/OTP 22, since it is covered by the now BIF Integer.to_string/2.
|
||||
# Please reapply commit 2622fd6b0aa419a983a899a1fbdb5deefba3d85d.
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `integer`.
|
||||
@@ -320,6 +323,9 @@ defmodule Integer do
|
||||
:erlang.integer_to_binary(integer, base)
|
||||
end
|
||||
|
||||
# TODO: Remove Integer.to_charlist/1 once the minimum supported version is
|
||||
# Erlang/OTP 22, since it is covered by the now BIF Integer.to_charlist/2.
|
||||
# Please reapply commit 2622fd6b0aa419a983a899a1fbdb5deefba3d85d.
|
||||
@doc """
|
||||
Returns a charlist which corresponds to the text representation of the given `integer`.
|
||||
|
||||
@@ -399,8 +405,7 @@ defmodule Integer do
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec gcd(0, 0) :: 0
|
||||
@spec gcd(integer, integer) :: pos_integer
|
||||
@spec gcd(integer, integer) :: non_neg_integer
|
||||
def gcd(integer1, integer2) when is_integer(integer1) and is_integer(integer2) do
|
||||
gcd_positive(abs(integer1), abs(integer2))
|
||||
end
|
||||
@@ -409,12 +414,10 @@ defmodule Integer do
|
||||
defp gcd_positive(integer1, 0), do: integer1
|
||||
defp gcd_positive(integer1, integer2), do: gcd_positive(integer2, rem(integer1, integer2))
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Integer.to_charlist/1 instead"
|
||||
def to_char_list(integer), do: Integer.to_charlist(integer)
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Integer.to_charlist/2 instead"
|
||||
def to_char_list(integer, base), do: Integer.to_charlist(integer, base)
|
||||
|
||||
+143
-42
@@ -1,5 +1,5 @@
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Functions handling input/output (IO).
|
||||
|
||||
Many functions in this module expect an IO device as an argument.
|
||||
@@ -7,13 +7,10 @@ defmodule IO do
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcuts to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
The majority of the functions expect chardata, i.e. strings or
|
||||
lists of characters and strings. In case another type is given,
|
||||
functions will convert to string via the `String.Chars` protocol
|
||||
(as shown in typespecs).
|
||||
|
||||
The functions starting with `bin` expect iodata as an argument,
|
||||
i.e. binaries or lists of bytes and binaries.
|
||||
The majority of the functions expect chardata. In case another type is given,
|
||||
functions will convert those types to string via the `String.Chars` protocol
|
||||
(as shown in typespecs). For more information on chardata, see the
|
||||
"IO data" section below.
|
||||
|
||||
## IO devices
|
||||
|
||||
@@ -32,17 +29,100 @@ defmodule IO do
|
||||
was last accessed. The position of files can be changed using the
|
||||
`:file.position/2` function.
|
||||
|
||||
## IO data
|
||||
|
||||
IO data is a data type that can be used as a more efficient alternative to binaries
|
||||
in certain situations.
|
||||
|
||||
A term of type **IO data** is a binary or a list containing bytes (integers in `0..255`)
|
||||
or nested IO data. The type is recursive. Let's see an example of one of
|
||||
the possible IO data representing the binary `"hello"`:
|
||||
|
||||
[?h, "el", ["l", [?o]]]
|
||||
|
||||
The built-in `t:iodata/0` type is defined in terms of `t:iolist/0`. An IO list is
|
||||
the same as IO data but it doesn't allow for a binary at the top level (but binaries
|
||||
are still allowed in the list itself).
|
||||
|
||||
### Use cases for IO data
|
||||
|
||||
IO data exists because often you need to do many append operations
|
||||
on smaller chunks of binaries in order to create a bigger binary. However, in
|
||||
Erlang and Elixir concatenating binaries will copy the concatenated binaries
|
||||
into a new binary.
|
||||
|
||||
def email(username, domain) do
|
||||
username <> "@" <> domain
|
||||
end
|
||||
|
||||
In this function, creating the email address will copy the `username` and `domain`
|
||||
binaries. Now imagine you want to use the resulting email inside another binary:
|
||||
|
||||
def welcome_message(name, username, domain) do
|
||||
"Welcome #{name}, your email is: #{email(username, domain)}"
|
||||
end
|
||||
|
||||
IO.puts(welcome_message("Meg", "meg", "example.com"))
|
||||
#=> "Welcome Meg, your email is: meg@example.com"
|
||||
|
||||
Every time you concatenate binaries or use interpolation (`#{}`) you are making
|
||||
copies of those binaries. However, in many cases you don't need the complete
|
||||
binary while you create it, but only at the end to print it out or send it
|
||||
somewhere. In such cases, you can construct the binary by creating IO data:
|
||||
|
||||
def email(username, domain) do
|
||||
[username, ?@, domain]
|
||||
end
|
||||
|
||||
def welcome_message(name, username, domain) do
|
||||
["Welcome ", name, ", your email is: ", email(username, domain)]
|
||||
end
|
||||
|
||||
IO.puts(welcome_message("Meg", "meg", "example.com"))
|
||||
#=> "Welcome Meg, your email is: meg@example.com"
|
||||
|
||||
Building IO data is cheaper than concatenating binaries. Concatenating multiple
|
||||
pieces of IO data just means putting them together inside a list since IO data
|
||||
can be arbitrarily nested, and that's a cheap and efficient operation. Most of
|
||||
the IO-based APIs, such as `:gen_tcp`, `IO`, etc, receive IO data and write it
|
||||
to the socket directly without converting it to binary.
|
||||
|
||||
One drawback of IO data is that you can't do things like pattern match on the
|
||||
first part of a piece of IO data like you can with a binary, because you usually
|
||||
don't know the shape of the IO data. In those cases, you may need to convert it
|
||||
to a binary by calling `iodata_to_binary/1`, which is reasonably efficient
|
||||
since it's implemented natively in C. Other functionality, like computing the
|
||||
length of IO data, can be computed directly on the iodata by calling `iodata_length/1`.
|
||||
|
||||
### Chardata
|
||||
|
||||
Erlang and Elixir also have the idea of `t:chardata/0`. Chardata is very
|
||||
similar to IO data: the only difference is that integers in IO data represent
|
||||
bytes while integers in chardata represent Unicode codepoints. Bytes
|
||||
(`t:byte/0`) are integers in the `0..255` range, while Unicode codepoints
|
||||
(`t:char/0`) are integers in the range `0..0x10FFFF`. The `IO` module provides
|
||||
the `chardata_to_string/1` function for chardata as the "counter-part" of the
|
||||
`iodata_to_binary/1` function for IO data.
|
||||
|
||||
If you try to use `iodata_to_binary/1` on chardata, it will result in an
|
||||
argument error. For example, let's try to put a codepoint that is not
|
||||
representable with one byte, like `?π`, inside IO data:
|
||||
|
||||
iex> IO.iodata_to_binary(["The symbol for pi is: ", ?π])
|
||||
** (ArgumentError) argument error
|
||||
|
||||
If we use chardata instead, it will work as expected:
|
||||
|
||||
iex> IO.chardata_to_string(["The symbol for pi is: ", ?π])
|
||||
"The symbol for pi is: π"
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type nodata :: {:error, term} | :eof
|
||||
@type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | [])
|
||||
|
||||
defmacrop is_iodata(data) do
|
||||
quote do
|
||||
is_list(unquote(data)) or is_binary(unquote(data))
|
||||
end
|
||||
end
|
||||
defguardp is_iodata(data) when is_list(data) or is_binary(data)
|
||||
|
||||
@doc """
|
||||
Reads from the IO `device`.
|
||||
@@ -141,7 +221,7 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes `item` to the given `device`.
|
||||
Writes `chardata` to the given `device`.
|
||||
|
||||
By default, the `device` is the standard output.
|
||||
|
||||
@@ -155,23 +235,30 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec write(device, chardata | String.Chars.t()) :: :ok
|
||||
def write(device \\ :stdio, item) do
|
||||
:io.put_chars(map_dev(device), to_chardata(item))
|
||||
def write(device \\ :stdio, chardata) do
|
||||
:io.put_chars(map_dev(device), to_chardata(chardata))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes `item` as a binary to the given `device`.
|
||||
No Unicode conversion happens.
|
||||
The operation is Unicode unsafe.
|
||||
Writes `iodata` to the given `device`.
|
||||
|
||||
Check `write/2` for more information.
|
||||
This operation is meant to be used with "raw" devices
|
||||
that are started without an encoding. The given `iodata`
|
||||
is written as is to the device, without conversion. For
|
||||
more information on IO data, see the "IO data" section in
|
||||
the module documentation.
|
||||
|
||||
Note: do not use this function on IO devices in Unicode mode
|
||||
as it will return the wrong result.
|
||||
Use `write/2` for devices with encoding.
|
||||
|
||||
Important: do **not** use this function on IO devices in
|
||||
Unicode mode as it will write the wrong data. In particular,
|
||||
the standard IO device is set to Unicode by default, so writing
|
||||
to stdio with this function will likely result in the wrong data
|
||||
being sent down the wire.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ :stdio, item) when is_iodata(item) do
|
||||
:file.write(map_dev(device), item)
|
||||
def binwrite(device \\ :stdio, iodata) when is_iodata(iodata) do
|
||||
:file.write(map_dev(device), iodata)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -214,15 +301,22 @@ defmodule IO do
|
||||
"""
|
||||
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
|
||||
def warn(message, []) do
|
||||
:elixir_errors.bare_warn(nil, nil, [to_chardata(message), ?\n])
|
||||
message = [to_chardata(message), ?\n]
|
||||
:elixir_errors.io_warn(nil, nil, message, message)
|
||||
end
|
||||
|
||||
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
|
||||
message = to_chardata(message)
|
||||
formatted_trace = Enum.map_join(stacktrace, "\n ", &Exception.format_stacktrace_entry(&1))
|
||||
message = [to_chardata(message), ?\n, " ", formatted_trace, ?\n]
|
||||
line = opts[:line]
|
||||
file = opts[:file]
|
||||
:elixir_errors.bare_warn(line, file && List.to_string(file), message)
|
||||
|
||||
:elixir_errors.io_warn(
|
||||
line,
|
||||
file && List.to_string(file),
|
||||
message,
|
||||
[message, ?\n, " ", formatted_trace, ?\n]
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -315,7 +409,7 @@ defmodule IO do
|
||||
Gets a number of bytes from IO device `:stdio`.
|
||||
|
||||
If `:stdio` is a Unicode device, `count` implies
|
||||
the number of Unicode codepoints to be retrieved.
|
||||
the number of Unicode code points to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
|
||||
See `IO.getn/3` for a description of return values.
|
||||
@@ -337,7 +431,7 @@ defmodule IO do
|
||||
Gets a number of bytes from the IO `device`.
|
||||
|
||||
If the IO `device` is a Unicode device, `count` implies
|
||||
the number of Unicode codepoints to be retrieved.
|
||||
the number of Unicode code points to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
|
||||
It returns:
|
||||
@@ -439,8 +533,10 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts chardata (a list of integers representing codepoints,
|
||||
lists and strings) into a string.
|
||||
Converts chardata into a string.
|
||||
|
||||
For more information about chardata, see the ["Chardata"](#module-chardata)
|
||||
section in the module documentation.
|
||||
|
||||
In case the conversion fails, it raises an `UnicodeConversionError`.
|
||||
If a string is given, it returns the string itself.
|
||||
@@ -467,14 +563,16 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts iodata (a list of integers representing bytes, lists
|
||||
and binaries) into a binary.
|
||||
Converts IO data into a binary
|
||||
|
||||
The operation is Unicode unsafe.
|
||||
|
||||
Notice that this function treats lists of integers as raw bytes
|
||||
and does not perform any kind of encoding conversion. If you want
|
||||
to convert from a charlist to a string (UTF-8 encoded), please
|
||||
use `chardata_to_string/1` instead.
|
||||
Notice that this function treats integers in the given IO data as
|
||||
raw bytes and does not perform any kind of encoding conversion.
|
||||
If you want to convert from a charlist to a UTF-8-encoded string,
|
||||
use `chardata_to_string/1` instead. For more information about
|
||||
IO data and chardata, see the ["IO data"](#module-io-data) section in the
|
||||
module documentation.
|
||||
|
||||
If this function receives a binary, the same binary is returned.
|
||||
|
||||
@@ -494,12 +592,15 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec iodata_to_binary(iodata) :: binary
|
||||
def iodata_to_binary(item) do
|
||||
:erlang.iolist_to_binary(item)
|
||||
def iodata_to_binary(iodata) do
|
||||
:erlang.iolist_to_binary(iodata)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the size of an iodata.
|
||||
Returns the size of an IO data.
|
||||
|
||||
For more information about IO data, see the ["IO data"](#module-io-data)
|
||||
section in the module documentation.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -510,8 +611,8 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec iodata_length(iodata) :: non_neg_integer
|
||||
def iodata_length(item) do
|
||||
:erlang.iolist_size(item)
|
||||
def iodata_length(iodata) do
|
||||
:erlang.iolist_size(iodata)
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule IO.ANSI do
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will be emitted.
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -525,15 +525,11 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
# Regular expression adapted from https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
~r{[a-z][a-z0-9\+\-\.]*://\S*}i
|
||||
|> Regex.recompile!()
|
||||
|> Regex.replace(text, &String.replace(&1, "_", "\\_"))
|
||||
Regex.replace(~r{[a-z][a-z0-9\+\-\.]*://\S*}i, text, &String.replace(&1, "_", "\\_"))
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
~r{\[(.*?)\]\((.*?)\)}
|
||||
|> Regex.recompile!()
|
||||
|> Regex.replace(text, "\\1 (\\2)")
|
||||
Regex.replace(~r{\[([^\]]*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# We have four entries: **, *, _ and `.
|
||||
|
||||
+277
-405
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ defmodule Kernel.CLI do
|
||||
commands: [],
|
||||
output: ".",
|
||||
compile: [],
|
||||
halt: true,
|
||||
no_halt: false,
|
||||
compiler_options: [],
|
||||
errors: [],
|
||||
pa: [],
|
||||
@@ -21,6 +21,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
{config, argv} = parse_argv(argv)
|
||||
System.argv(argv)
|
||||
System.no_halt(config.no_halt)
|
||||
|
||||
fun = fn _ ->
|
||||
errors = process_commands(config)
|
||||
@@ -31,7 +32,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
run(fun, config.halt)
|
||||
run(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -42,10 +43,10 @@ defmodule Kernel.CLI do
|
||||
This function is used by Elixir's CLI and also
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt \\ true) do
|
||||
def run(fun) do
|
||||
{ok_or_shutdown, status} = exec_fun(fun, {:ok, 0})
|
||||
|
||||
if ok_or_shutdown == :shutdown or halt do
|
||||
if ok_or_shutdown == :shutdown or not System.no_halt() do
|
||||
{_, status} = at_exit({ok_or_shutdown, status})
|
||||
|
||||
# Ensure Logger messages are flushed before halting
|
||||
@@ -58,19 +59,25 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Parses the CLI arguments. Made public for testing.
|
||||
"""
|
||||
def parse_argv(argv) do
|
||||
parse_argv(argv, @blank_config)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Process CLI commands. Made public for testing.
|
||||
"""
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
Enum.reverse(config.errors, errors)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Shared helper for error formatting on CLI tools.
|
||||
"""
|
||||
def format_error(kind, reason, stacktrace) do
|
||||
{blamed, stacktrace} = Exception.blame(kind, reason, stacktrace)
|
||||
|
||||
@@ -88,6 +95,15 @@ defmodule Kernel.CLI do
|
||||
[iodata, ?\n, Exception.format_stacktrace(prune_stacktrace(stacktrace))]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Function invoked across nodes for `--rpc-eval`.
|
||||
"""
|
||||
def rpc_eval(expr) do
|
||||
wrapper(fn -> :elixir.eval(to_charlist(expr), [], []) end)
|
||||
catch
|
||||
kind, reason -> {kind, reason, __STACKTRACE__}
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp at_exit(res) do
|
||||
@@ -225,13 +241,22 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_shared(["--no-halt" | t], config) do
|
||||
parse_shared(t, %{config | halt: false})
|
||||
parse_shared(t, %{config | no_halt: true})
|
||||
end
|
||||
|
||||
defp parse_shared(["-e", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
|
||||
end
|
||||
|
||||
defp parse_shared(["--eval", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
|
||||
end
|
||||
|
||||
defp parse_shared(["--rpc-eval", node, h | t], config) do
|
||||
node = append_hostname(node)
|
||||
parse_shared(t, %{config | commands: [{:rpc_eval, node, h} | config.commands]})
|
||||
end
|
||||
|
||||
defp parse_shared(["-r", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:require, h} | config.commands]})
|
||||
end
|
||||
@@ -240,23 +265,17 @@ defmodule Kernel.CLI do
|
||||
parse_shared(t, %{config | commands: [{:parallel_require, h} | config.commands]})
|
||||
end
|
||||
|
||||
@erl_arg_options ["--erl", "--sname", "--name", "--cookie"] ++
|
||||
["--logger-otp-reports", "--logger-sasl-reports"]
|
||||
|
||||
@erl_boolean_options ["--detached", "--hidden", "--werl"]
|
||||
|
||||
defp parse_shared([erl, _ | t], config) when erl in @erl_arg_options do
|
||||
parse_shared(t, config)
|
||||
end
|
||||
|
||||
defp parse_shared([erl | t], config) when erl in @erl_boolean_options do
|
||||
parse_shared(t, config)
|
||||
end
|
||||
|
||||
defp parse_shared(list, config) do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
defp append_hostname(node) do
|
||||
case :string.find(node, "@") do
|
||||
:nomatch -> node <> :string.find(Atom.to_string(node()), "@")
|
||||
_ -> node
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_code_path(path) do
|
||||
path = Path.expand(path)
|
||||
|
||||
@@ -290,7 +309,7 @@ defmodule Kernel.CLI do
|
||||
shared_option?(list, config, &parse_argv(&1, &2))
|
||||
|
||||
_ ->
|
||||
if Keyword.has_key?(config.commands, :eval) do
|
||||
if List.keymember?(config.commands, :eval, 0) do
|
||||
{config, list}
|
||||
else
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
@@ -395,6 +414,15 @@ defmodule Kernel.CLI do
|
||||
wrapper(fn -> Code.eval_string(expr, []) end)
|
||||
end
|
||||
|
||||
defp process_command({:rpc_eval, node, expr}, _config) when is_binary(expr) do
|
||||
case :rpc.call(String.to_atom(node), __MODULE__, :rpc_eval, [expr]) do
|
||||
:ok -> :ok
|
||||
{:badrpc, {:EXIT, exit}} -> Process.exit(self(), exit)
|
||||
{:badrpc, reason} -> {:error, "--rpc-eval : RPC failed with reason #{inspect(reason)}"}
|
||||
{kind, error, stack} -> :erlang.raise(kind, error, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.ensure_all_started(String.to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
|
||||
@@ -5,13 +5,13 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
@spec undefined_function(module, atom, list) :: term
|
||||
def undefined_function(module, fun, args) do
|
||||
ensure_loaded(module) or ensure_compiled(module, :module)
|
||||
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
|
||||
:error_handler.undefined_function(module, fun, args)
|
||||
end
|
||||
|
||||
@spec undefined_lambda(module, fun, list) :: term
|
||||
def undefined_lambda(module, fun, args) do
|
||||
ensure_loaded(module) or ensure_compiled(module, :module)
|
||||
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
|
||||
:error_handler.undefined_lambda(module, fun, args)
|
||||
end
|
||||
|
||||
@@ -23,21 +23,21 @@ defmodule Kernel.ErrorHandler do
|
||||
end
|
||||
end
|
||||
|
||||
@spec ensure_compiled(module, atom) :: boolean
|
||||
@spec ensure_compiled(module, atom, atom) :: :found | :not_found | :deadlock
|
||||
# Never wait on nil because it should never be defined.
|
||||
def ensure_compiled(nil, _kind) do
|
||||
false
|
||||
def ensure_compiled(nil, _kind, _deadlock) do
|
||||
:not_found
|
||||
end
|
||||
|
||||
def ensure_compiled(module, kind) do
|
||||
def ensure_compiled(module, kind, deadlock) do
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref()
|
||||
send(parent, {:waiting, kind, self(), ref, module, :elixir_module.compiler_modules()})
|
||||
modules = :elixir_module.compiler_modules()
|
||||
send(parent, {:waiting, kind, self(), ref, module, modules, deadlock})
|
||||
:erlang.garbage_collect(self())
|
||||
|
||||
receive do
|
||||
{^ref, :found} -> true
|
||||
{^ref, :not_found} -> false
|
||||
{^ref, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,22 +12,15 @@ defmodule Kernel.LexicalTracker do
|
||||
@doc """
|
||||
Returns all remotes referenced in this lexical scope.
|
||||
"""
|
||||
def remote_references(arg) do
|
||||
:gen_server.call(to_pid(arg), :remote_references, @timeout)
|
||||
def remote_references(pid) do
|
||||
:gen_server.call(pid, :remote_references, @timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all remote dispatches in this lexical scope.
|
||||
"""
|
||||
def remote_dispatches(arg) do
|
||||
:gen_server.call(to_pid(arg), :remote_dispatches, @timeout)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
{set, _} = :elixir_module.data_tables(mod)
|
||||
:ets.lookup_element(set, {:elixir, :lexical_tracker}, 2)
|
||||
def remote_dispatches(pid) do
|
||||
:gen_server.call(pid, :remote_dispatches, @timeout)
|
||||
end
|
||||
|
||||
# Internal API
|
||||
@@ -40,7 +33,7 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
:gen_server.call(pid, :stop)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -153,6 +146,10 @@ defmodule Kernel.LexicalTracker do
|
||||
{:reply, :maps.get(key, cache), state}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
{:stop, :normal, :ok, state}
|
||||
end
|
||||
|
||||
def handle_cast({:write_cache, key, value}, %{cache: cache} = state) do
|
||||
{:noreply, %{state | cache: :maps.put(key, value, cache)}}
|
||||
end
|
||||
@@ -213,10 +210,6 @@ defmodule Kernel.LexicalTracker do
|
||||
{:noreply, %{state | directives: add_directive(state.directives, module, line, warn, :alias)}}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, state) do
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
|
||||
@@ -22,6 +22,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
{:error_handler, error_handler} = :erlang.process_info(self(), :error_handler)
|
||||
|
||||
Task.async(fn ->
|
||||
send(parent, {:async, self()})
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
dest != :undefined and :erlang.put(:elixir_compiler_dest, dest)
|
||||
@@ -66,7 +67,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:long_compilation_threshold` - the timeout (in seconds) after the
|
||||
`:each_long_compilation` callback is invoked; defaults to `15`
|
||||
|
||||
* `:dest` - the destination directory for the BEAM files. When using `files/2`,
|
||||
* `:dest` - the destination directory for the BEAM files. When using `compile/2`,
|
||||
this information is only used to properly annotate the BEAM files before
|
||||
they are loaded into memory. If you want a file to actually be written to
|
||||
`dest`, use `compile_to_path/3` instead.
|
||||
@@ -107,7 +108,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
spawn_workers(files, :require, options)
|
||||
end
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
@doc false
|
||||
@deprecated "Use Kernel.ParallelCompiler.compile/2 instead"
|
||||
def files(files, options \\ []) when is_list(options) do
|
||||
@@ -117,7 +117,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
@doc false
|
||||
@deprecated "Use Kernel.ParallelCompiler.compile_to_path/2 instead"
|
||||
def files_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
|
||||
@@ -134,10 +133,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result =
|
||||
spawn_workers(files, [], [], [], [], %{
|
||||
spawn_workers(files, 0, [], [], %{}, [], %{
|
||||
dest: Keyword.get(options, :dest),
|
||||
each_cycle: Keyword.get(options, :each_cycle, fn -> [] end),
|
||||
each_file: Keyword.get(options, :each_file, fn _file -> :ok end),
|
||||
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
|
||||
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
|
||||
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
|
||||
output: output,
|
||||
@@ -163,165 +162,189 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp each_file(fun) when is_function(fun, 1), do: fn file, _ -> fun.(file) end
|
||||
defp each_file(fun) when is_function(fun, 2), do: fun
|
||||
|
||||
defp each_file(file, lexical, parent) do
|
||||
ref = Process.monitor(parent)
|
||||
send(parent, {:file_ok, self(), ref, file, lexical})
|
||||
|
||||
receive do
|
||||
^ref -> :ok
|
||||
{:DOWN, ^ref, _, _, _} -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
# We already have n=schedulers currently running, don't spawn new ones
|
||||
defp spawn_workers(files, waiting, queued, result, warnings, %{schedulers: schedulers} = state)
|
||||
when length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, waiting, queued, result, warnings, state)
|
||||
defp spawn_workers(
|
||||
queue,
|
||||
spawned,
|
||||
waiting,
|
||||
files,
|
||||
result,
|
||||
warnings,
|
||||
%{schedulers: schedulers} = state
|
||||
)
|
||||
when spawned - length(waiting) >= schedulers do
|
||||
wait_for_messages(queue, spawned, waiting, files, result, warnings, state)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_workers([{ref, found} | t], waiting, queued, result, warnings, state) do
|
||||
defp spawn_workers([{ref, found} | t], spawned, waiting, files, result, warnings, state) do
|
||||
waiting =
|
||||
case List.keytake(waiting, ref, 2) do
|
||||
{{_kind, pid, ^ref, _on, _defining}, waiting} ->
|
||||
{{_kind, pid, ^ref, _on, _defining, _deadlock}, waiting} ->
|
||||
send(pid, {ref, found})
|
||||
waiting
|
||||
|
||||
nil ->
|
||||
# In case the waiting process died (for example, it was an async process),
|
||||
# it will no longer be on the list. So we need to take it into account here.
|
||||
waiting
|
||||
end
|
||||
|
||||
spawn_workers(t, waiting, queued, result, warnings, state)
|
||||
spawn_workers(t, spawned, waiting, files, result, warnings, state)
|
||||
end
|
||||
|
||||
defp spawn_workers([file | files], waiting, queued, result, warnings, state) do
|
||||
defp spawn_workers([file | queue], spawned, waiting, files, result, warnings, state) do
|
||||
%{output: output, long_compilation_threshold: threshold, dest: dest} = state
|
||||
parent = self()
|
||||
file = Path.expand(file)
|
||||
|
||||
{pid, ref} =
|
||||
:erlang.spawn_monitor(fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
|
||||
result =
|
||||
try do
|
||||
_ =
|
||||
case output do
|
||||
{:compile, path} ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:erlang.put(:elixir_compiler_dest, path)
|
||||
:elixir_compiler.file_to_path(Path.expand(file), path)
|
||||
try do
|
||||
case output do
|
||||
{:compile, path} ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:erlang.put(:elixir_compiler_dest, path)
|
||||
:elixir_compiler.file_to_path(file, path, &each_file(&1, &2, parent))
|
||||
|
||||
:compile ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:erlang.put(:elixir_compiler_dest, dest)
|
||||
Code.compile_file(file)
|
||||
:compile ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:erlang.put(:elixir_compiler_dest, dest)
|
||||
:elixir_compiler.file(file, &each_file(&1, &2, parent))
|
||||
|
||||
:require ->
|
||||
Code.require_file(file)
|
||||
:require ->
|
||||
case :elixir_code_server.call({:acquire, file}) do
|
||||
:required ->
|
||||
send(parent, {:file_cancel, self()})
|
||||
|
||||
:proceed ->
|
||||
:elixir_compiler.file(file, &each_file(&1, &2, parent))
|
||||
:elixir_code_server.cast({:required, file})
|
||||
end
|
||||
|
||||
:ok
|
||||
catch
|
||||
kind, reason ->
|
||||
{kind, reason, __STACKTRACE__}
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
send(parent, {:file_error, self(), file, {kind, reason, __STACKTRACE__}})
|
||||
end
|
||||
|
||||
send(parent, {:file_done, self(), file, result})
|
||||
exit(:shutdown)
|
||||
end)
|
||||
|
||||
timer_ref = Process.send_after(self(), {:timed_out, pid}, threshold * 1000)
|
||||
queued = [{pid, ref, file, timer_ref} | queued]
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
files = [{pid, ref, file, timer_ref} | files]
|
||||
spawn_workers(queue, spawned + 1, waiting, files, result, warnings, state)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, this cycle is done
|
||||
defp spawn_workers([], [], [], result, warnings, state) do
|
||||
# No more queue, nothing waiting, this cycle is done
|
||||
defp spawn_workers([], 0, [], [], result, warnings, state) do
|
||||
case state.each_cycle.() do
|
||||
[] ->
|
||||
modules = for {:module, mod} <- result, do: mod
|
||||
modules = for {{:module, mod}, _} <- result, do: mod
|
||||
warnings = Enum.reverse(warnings)
|
||||
{:ok, modules, warnings}
|
||||
|
||||
more ->
|
||||
spawn_workers(more, [], [], result, warnings, state)
|
||||
spawn_workers(more, 0, [], [], result, warnings, state)
|
||||
end
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
# files 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)
|
||||
defp spawn_workers(
|
||||
[],
|
||||
1,
|
||||
[{_, pid, ref, _, _, _}] = waiting,
|
||||
[{pid, _, _, _}] = files,
|
||||
result,
|
||||
warnings,
|
||||
state
|
||||
) do
|
||||
spawn_workers([{ref, :not_found}], 1, waiting, files, 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.
|
||||
without_definition =
|
||||
for {pid, _, _, _} <- queued,
|
||||
entry = waiting_on_without_definition(waiting, pid),
|
||||
do: entry
|
||||
defp spawn_workers([], spawned, waiting, files, result, warnings, state)
|
||||
when length(waiting) == spawned do
|
||||
# There is potentially a deadlock. We will release modules with
|
||||
# the following order:
|
||||
#
|
||||
# 1. Code.ensure_compiled?/1 checks (deadlock = soft)
|
||||
# 2. Struct checks (deadlock = hard)
|
||||
# 3. Modules without a known definition
|
||||
# 4. Code invocation (deadlock = raise)
|
||||
#
|
||||
# In theory there is no difference between hard and raise, the
|
||||
# difference is where the raise is happening, inside the compiler
|
||||
# or in the caller.
|
||||
cond do
|
||||
deadlocked = deadlocked(waiting, :soft) || deadlocked(waiting, :hard) ->
|
||||
spawn_workers(deadlocked, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
# 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 {:module, _, ref, on, _} <- without_definition,
|
||||
do: {on, {ref, :not_found}}
|
||||
without_definition = without_definition(waiting, files) ->
|
||||
spawn_workers(without_definition, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
# 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
|
||||
# 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
|
||||
# sounds like a sane choice.
|
||||
pending
|
||||
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|
||||
|> Enum.sort_by(&length(elem(&1, 1)))
|
||||
|> case do
|
||||
[{_on, refs} | _] ->
|
||||
spawn_workers(refs, waiting, queued, result, warnings, state)
|
||||
|
||||
[] ->
|
||||
# There is a deadlock. Instead of printing a deadlock, let's release
|
||||
# structs, as a missing struct error is clearer than a deadlock one.
|
||||
structs = for {:struct, _, ref, _, _} <- without_definition, do: {ref, :not_found}
|
||||
|
||||
if structs != [] do
|
||||
spawn_workers(structs, waiting, queued, result, warnings, state)
|
||||
else
|
||||
errors = handle_deadlock(waiting, queued)
|
||||
{:error, errors, warnings}
|
||||
end
|
||||
true ->
|
||||
errors = handle_deadlock(waiting, files)
|
||||
{:error, errors, warnings}
|
||||
end
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_workers([], waiting, queued, result, warnings, state) do
|
||||
wait_for_messages([], waiting, queued, result, warnings, state)
|
||||
# No more queue, but spawned and length(waiting) do not match
|
||||
defp spawn_workers([], spawned, waiting, files, result, warnings, state) do
|
||||
wait_for_messages([], spawned, waiting, files, result, warnings, state)
|
||||
end
|
||||
|
||||
defp waiting_on_without_definition(waiting, pid) do
|
||||
{_, ^pid, _, on, _} = entry = List.keyfind(waiting, pid, 1)
|
||||
|
||||
if Enum.any?(waiting, fn {_, _, _, _, defining} -> on in defining end) do
|
||||
nil
|
||||
else
|
||||
entry
|
||||
end
|
||||
# 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.
|
||||
defp without_definition(waiting, files) do
|
||||
nillify_empty(
|
||||
for {pid, _, _, _} <- files,
|
||||
{_, ^pid, ref, on, _, _} = List.keyfind(waiting, pid, 1),
|
||||
not Enum.any?(waiting, fn {_, _, _, _, defining, _} -> on in defining end),
|
||||
do: {ref, :not_found}
|
||||
)
|
||||
end
|
||||
|
||||
defp deadlocked(waiting, type) do
|
||||
nillify_empty(for {_, _, ref, _, _, ^type} <- waiting, do: {ref, :not_found})
|
||||
end
|
||||
|
||||
defp nillify_empty([]), do: nil
|
||||
defp nillify_empty([_ | _] = list), do: list
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, waiting, queued, result, warnings, state) do
|
||||
defp wait_for_messages(queue, spawned, waiting, files, result, warnings, state) do
|
||||
%{output: output} = state
|
||||
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
{:async, process} ->
|
||||
Process.monitor(process)
|
||||
wait_for_messages(queue, spawned + 1, waiting, files, result, warnings, state)
|
||||
|
||||
{:available, kind, module} ->
|
||||
available =
|
||||
for {:struct, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
for {^kind, _, ref, ^module, _defining, _deadlock} <- waiting,
|
||||
do: {ref, :found}
|
||||
|
||||
result = [{:struct, module} | result]
|
||||
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
|
||||
result = Map.put(result, {kind, module}, true)
|
||||
spawn_workers(available ++ queue, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
state.each_module.(file, module, binary)
|
||||
@@ -330,77 +353,77 @@ defmodule Kernel.ParallelCompiler do
|
||||
send(child, {ref, :ack})
|
||||
|
||||
available =
|
||||
for {:module, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
for {:module, _, ref, ^module, _defining, _deadlock} <- waiting,
|
||||
do: {ref, :found}
|
||||
|
||||
cancel_waiting_timer(queued, child)
|
||||
|
||||
result = [{:module, module} | result]
|
||||
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
|
||||
cancel_waiting_timer(files, child)
|
||||
result = Map.put(result, {:module, module}, true)
|
||||
spawn_workers(available ++ queue, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
# If we are simply requiring files, we do not add to waiting.
|
||||
{:waiting, _kind, child, ref, _on, _defining} when output == :require ->
|
||||
{:waiting, _kind, child, ref, _on, _defining, _deadlock} when output == :require ->
|
||||
send(child, {ref, :not_found})
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
{:waiting, kind, child, ref, on, defining} ->
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
{:waiting, kind, child, ref, on, defining, deadlock?} ->
|
||||
# If we already got what we were waiting for, do not put it on waiting.
|
||||
# Alternatively, we're waiting on ourselves,
|
||||
# send :found so that we can crash with a better error.
|
||||
waiting =
|
||||
if :lists.any(&match?({^kind, ^on}, &1), result) or on in defining do
|
||||
if Map.has_key?(result, {kind, on}) or on in defining do
|
||||
send(child, {ref, :found})
|
||||
waiting
|
||||
else
|
||||
[{kind, child, ref, on, defining} | waiting]
|
||||
[{kind, child, ref, on, defining, deadlock?} | waiting]
|
||||
end
|
||||
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
{:timed_out, child} ->
|
||||
case List.keyfind(queued, child, 0) do
|
||||
{^child, _, file, _} ->
|
||||
state.each_long_compilation.(file)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
case List.keyfind(files, child, 0) do
|
||||
{^child, _, file, _} -> state.each_long_compilation.(file)
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
|
||||
|
||||
{:warning, file, line, message} ->
|
||||
file = file && Path.absname(file)
|
||||
message = :unicode.characters_to_binary(message)
|
||||
warning = {file, line, message}
|
||||
wait_for_messages(files, waiting, queued, result, [warning | warnings], state)
|
||||
wait_for_messages(queue, spawned, waiting, files, result, [warning | warnings], state)
|
||||
|
||||
{:file_ok, child_pid, ref, file, lexical} ->
|
||||
state.each_file.(file, lexical)
|
||||
send(child_pid, ref)
|
||||
cancel_waiting_timer(files, child_pid)
|
||||
|
||||
{:file_done, child_pid, file, :ok} ->
|
||||
discard_down(child_pid)
|
||||
state.each_file.(file)
|
||||
cancel_waiting_timer(queued, child_pid)
|
||||
new_files = List.keydelete(files, child_pid, 0)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_files = List.delete(files, child_pid)
|
||||
new_queued = List.keydelete(queued, child_pid, 0)
|
||||
# Sometimes we may have spurious entries in the waiting list
|
||||
# because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child_pid, 1)
|
||||
spawn_workers(new_files, new_waiting, new_queued, result, warnings, state)
|
||||
spawn_workers(queue, spawned - 1, new_waiting, new_files, result, warnings, state)
|
||||
|
||||
{:file_done, child_pid, file, {kind, reason, stack}} ->
|
||||
{:file_cancel, child_pid} ->
|
||||
cancel_waiting_timer(files, child_pid)
|
||||
discard_down(child_pid)
|
||||
new_files = List.keydelete(files, child_pid, 0)
|
||||
spawn_workers(queue, spawned - 1, waiting, new_files, result, warnings, state)
|
||||
|
||||
{:file_error, child_pid, file, {kind, reason, stack}} ->
|
||||
print_error(file, kind, reason, stack)
|
||||
cancel_waiting_timer(queued, child_pid)
|
||||
|
||||
queued
|
||||
|> List.keydelete(child_pid, 0)
|
||||
|> terminate()
|
||||
|
||||
cancel_waiting_timer(files, child_pid)
|
||||
discard_down(child_pid)
|
||||
files |> List.keydelete(child_pid, 0) |> terminate()
|
||||
{:error, [to_error(file, kind, reason, stack)], warnings}
|
||||
|
||||
{:DOWN, ref, :process, _pid, reason} ->
|
||||
case handle_down(queued, ref, reason) do
|
||||
:ok -> wait_for_messages(files, waiting, queued, result, warnings, state)
|
||||
{:DOWN, ref, :process, pid, reason} ->
|
||||
waiting = List.keydelete(waiting, pid, 1)
|
||||
|
||||
case handle_down(files, ref, reason) do
|
||||
:ok -> wait_for_messages(queue, spawned - 1, waiting, files, result, warnings, state)
|
||||
{:error, errors} -> {:error, errors, warnings}
|
||||
end
|
||||
end
|
||||
@@ -412,16 +435,16 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_down(_queued, _ref, :normal) do
|
||||
defp handle_down(_files, _ref, :normal) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_down(queued, ref, reason) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
defp handle_down(files, ref, reason) do
|
||||
case List.keyfind(files, ref, 1) do
|
||||
{child_pid, ^ref, file, _timer_ref} ->
|
||||
print_error(file, :exit, reason, [])
|
||||
|
||||
queued
|
||||
files
|
||||
|> List.keydelete(child_pid, 0)
|
||||
|> terminate()
|
||||
|
||||
@@ -432,18 +455,17 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_deadlock(waiting, queued) do
|
||||
defp handle_deadlock(waiting, files) do
|
||||
deadlock =
|
||||
for {pid, _, file, _} <- queued do
|
||||
for {pid, _, file, _} <- files do
|
||||
{:current_stacktrace, stacktrace} = Process.info(pid, :current_stacktrace)
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
{kind, ^pid, _, on, _} = List.keyfind(waiting, pid, 1)
|
||||
{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)
|
||||
|
||||
{file, on, description}
|
||||
{Path.relative_to_cwd(file), on, description}
|
||||
end
|
||||
|
||||
IO.puts("""
|
||||
@@ -469,9 +491,9 @@ defmodule Kernel.ParallelCompiler do
|
||||
for {file, _, description} <- deadlock, do: {Path.absname(file), nil, description}
|
||||
end
|
||||
|
||||
defp terminate(queued) do
|
||||
for {pid, _, _, _} <- queued, do: Process.exit(pid, :kill)
|
||||
for {pid, _, _, _} <- queued, do: discard_down(pid)
|
||||
defp terminate(files) do
|
||||
for {pid, _, _, _} <- files, do: Process.exit(pid, :kill)
|
||||
for {pid, _, _, _} <- files, do: discard_down(pid)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -482,12 +504,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
])
|
||||
end
|
||||
|
||||
defp cancel_waiting_timer(queued, child_pid) do
|
||||
case List.keyfind(queued, child_pid, 0) do
|
||||
defp cancel_waiting_timer(files, child_pid) do
|
||||
case List.keyfind(files, child_pid, 0) do
|
||||
{^child_pid, _ref, _file, timer_ref} ->
|
||||
Process.cancel_timer(timer_ref)
|
||||
# Let's flush the message in case it arrived before we canceled the
|
||||
# timeout.
|
||||
# Let's flush the message in case it arrived before we canceled the timeout.
|
||||
receive do
|
||||
{:timed_out, ^child_pid} -> :ok
|
||||
after
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
defmodule Kernel.ParallelRequire do
|
||||
# TODO: Remove on 2.0
|
||||
@moduledoc false
|
||||
|
||||
@deprecated "Use Kernel.ParallelCompiler.require/2 instead"
|
||||
|
||||
@@ -37,7 +37,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## AST representation
|
||||
|
||||
Only two-item tuples are considered literals in Elixir and return themselves
|
||||
Only two-element tuples are considered literals in Elixir and return themselves
|
||||
when quoted. Therefore, all other tuples are represented in the AST as calls to
|
||||
the `:{}` special form.
|
||||
|
||||
@@ -192,7 +192,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> <<102, rest::binary>>
|
||||
"foo"
|
||||
|
||||
The `utf8`, `utf16`, and `utf32` types are for Unicode codepoints. They
|
||||
The `utf8`, `utf16`, and `utf32` types are for Unicode code points. They
|
||||
can also be applied to literal strings and charlists:
|
||||
|
||||
iex> <<"foo"::utf16>>
|
||||
@@ -346,7 +346,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
def type(<<@png_signature, rest::binary>>), do: :png
|
||||
def type(<<@jpg_signature, rest::binary>>), do: :jpg
|
||||
def type(_), do :unknown
|
||||
def type(_), do: :unknown
|
||||
end
|
||||
|
||||
### Performance & Optimizations
|
||||
@@ -486,11 +486,11 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro unquote(:.)(left, right), do: error!([left, right])
|
||||
|
||||
@doc """
|
||||
`alias/2` is used to setup aliases, often useful with modules names.
|
||||
`alias/2` is used to set up aliases, often useful with modules' names.
|
||||
|
||||
## Examples
|
||||
|
||||
`alias/2` can be used to setup an alias for any module:
|
||||
`alias/2` can be used to set up an alias for any module:
|
||||
|
||||
defmodule Math do
|
||||
alias MyKeyword, as: Keyword
|
||||
@@ -503,7 +503,7 @@ defmodule Kernel.SpecialForms do
|
||||
In case one wants to access the original `Keyword`, it can be done
|
||||
by accessing `Elixir`:
|
||||
|
||||
Keyword.values #=> uses MyKeyword.values
|
||||
Keyword.values #=> uses MyKeyword.values
|
||||
Elixir.Keyword.values #=> uses Keyword.values
|
||||
|
||||
Notice that calling `alias` without the `:as` option automatically
|
||||
@@ -542,6 +542,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to `true` or `false`.
|
||||
|
||||
"""
|
||||
defmacro alias(module, opts), do: error!([module, opts])
|
||||
|
||||
@@ -576,7 +577,7 @@ defmodule Kernel.SpecialForms do
|
||||
Imports functions and macros from other modules.
|
||||
|
||||
`import/2` allows one to easily access functions or macros from
|
||||
others modules without using the qualified name.
|
||||
other modules without using the qualified name.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -852,7 +853,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
`quote/2` is commonly used with macros for code generation. As an exercise,
|
||||
let's define a macro that multiplies a number by itself (squared). In practice,
|
||||
there is no reason to define such as a macro (and it would actually be
|
||||
there is no reason to define such a macro (and it would actually be
|
||||
seen as a bad practice), but it is simple enough that it allows us to focus
|
||||
on the important aspects of quotes and macros:
|
||||
|
||||
@@ -867,7 +868,7 @@ defmodule Kernel.SpecialForms do
|
||||
We can invoke it as:
|
||||
|
||||
import Math
|
||||
IO.puts "Got #{squared(5)}"
|
||||
IO.puts("Got #{squared(5)}")
|
||||
|
||||
At first, there is nothing in this example that actually reveals it is a
|
||||
macro. But what is happening is that, at compilation time, `squared(5)`
|
||||
@@ -877,10 +878,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
import Math
|
||||
my_number = fn ->
|
||||
IO.puts "Returning 5"
|
||||
IO.puts("Returning 5")
|
||||
5
|
||||
end
|
||||
IO.puts "Got #{squared(my_number.())}"
|
||||
IO.puts("Got #{squared(my_number.())}")
|
||||
|
||||
The example above will print:
|
||||
|
||||
@@ -946,7 +947,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
import Math
|
||||
squared(5)
|
||||
x #=> ** (CompileError) undefined variable x or undefined function x/0
|
||||
x
|
||||
#=> ** (CompileError) undefined variable x or undefined function x/0
|
||||
|
||||
We can see that `x` did not leak to the user context. This happens
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
@@ -967,8 +969,9 @@ defmodule Kernel.SpecialForms do
|
||||
require Hygiene
|
||||
|
||||
a = 10
|
||||
Hygiene.no_interference
|
||||
a #=> 10
|
||||
Hygiene.no_interference()
|
||||
a
|
||||
#=> 10
|
||||
|
||||
In the example above, `a` returns 10 even if the macro
|
||||
is apparently setting it to 1 because variables defined
|
||||
@@ -987,8 +990,9 @@ defmodule Kernel.SpecialForms do
|
||||
require NoHygiene
|
||||
|
||||
a = 10
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
NoHygiene.interference()
|
||||
a
|
||||
#=> 1
|
||||
|
||||
You cannot even access variables defined in the same module unless
|
||||
you explicitly give it a context:
|
||||
@@ -1007,8 +1011,8 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Hygiene.write
|
||||
Hygiene.read
|
||||
Hygiene.write()
|
||||
Hygiene.read()
|
||||
#=> ** (RuntimeError) undefined variable a or undefined function a/0
|
||||
|
||||
For such, you can explicitly pass the current module scope as
|
||||
@@ -1028,8 +1032,8 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
ContextHygiene.write
|
||||
ContextHygiene.read
|
||||
ContextHygiene.write()
|
||||
ContextHygiene.read()
|
||||
#=> 1
|
||||
|
||||
## Hygiene in aliases
|
||||
@@ -1042,13 +1046,14 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro no_interference do
|
||||
quote do
|
||||
M.new
|
||||
M.new()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference #=> %{}
|
||||
Hygiene.no_interference()
|
||||
#=> %{}
|
||||
|
||||
Notice that, even though the alias `M` is not available
|
||||
in the context the macro is expanded, the code above works
|
||||
@@ -1062,31 +1067,32 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro no_interference do
|
||||
quote do
|
||||
M.new
|
||||
M.new()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
alias SomethingElse, as: M
|
||||
Hygiene.no_interference #=> %{}
|
||||
Hygiene.no_interference()
|
||||
#=> %{}
|
||||
|
||||
In some cases, you want to access an alias or a module defined
|
||||
in the caller. For such, you can use the `alias!` macro:
|
||||
|
||||
defmodule Hygiene do
|
||||
# This will expand to Elixir.Nested.hello
|
||||
# This will expand to Elixir.Nested.hello()
|
||||
defmacro no_interference do
|
||||
quote do
|
||||
Nested.hello
|
||||
Nested.hello()
|
||||
end
|
||||
end
|
||||
|
||||
# This will expand to Nested.hello for
|
||||
# This will expand to Nested.hello() for
|
||||
# whatever is Nested in the caller
|
||||
defmacro interference do
|
||||
quote do
|
||||
alias!(Nested).hello
|
||||
alias!(Nested).hello()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1097,10 +1103,10 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference
|
||||
Hygiene.no_interference()
|
||||
#=> ** (UndefinedFunctionError) ...
|
||||
|
||||
Hygiene.interference
|
||||
Hygiene.interference()
|
||||
#=> "world"
|
||||
end
|
||||
|
||||
@@ -1122,7 +1128,8 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Hygiene.return_length #=> 3
|
||||
Hygiene.return_length()
|
||||
#=> 3
|
||||
|
||||
Notice how `Hygiene.return_length/0` returns `3` even though the `Kernel.length/1`
|
||||
function is not imported. In fact, even if `return_length/0`
|
||||
@@ -1157,7 +1164,8 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Lazy.return_length #=> 5
|
||||
Lazy.return_length()
|
||||
#=> 5
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
@@ -1194,25 +1202,25 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
Elixir quote/unquote mechanisms provides a functionality called
|
||||
Elixir quote/unquote mechanisms provide a functionality called
|
||||
unquote fragments. Unquote fragments provide an easy way to generate
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn {k, v} ->
|
||||
Enum.each(kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end)
|
||||
|
||||
In the example above, we have generated the functions `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
`bar/0` dynamically. Now, imagine that we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.map kv, fn {k, v} ->
|
||||
Enum.map(kv, fn {k, v} ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
We can invoke this macro as:
|
||||
@@ -1235,9 +1243,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn {k, v} ->
|
||||
Enum.each(unquote(kv), fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1256,9 +1264,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote bind_quoted: [kv: kv] do
|
||||
Enum.each kv, fn {k, v} ->
|
||||
Enum.each(kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1601,6 +1609,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
&local_function/1
|
||||
|
||||
See also `Function.capture/3`.
|
||||
|
||||
## Anonymous functions
|
||||
|
||||
The capture operator can also be used to partially apply
|
||||
@@ -1676,7 +1686,7 @@ defmodule Kernel.SpecialForms do
|
||||
it can be sure that it represents a call and the second argument
|
||||
in the list is an atom.
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
On the other hand, aliases hold some properties:
|
||||
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
@@ -1725,7 +1735,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
#=> "This clause would match any value (x = 10)"
|
||||
|
||||
## Variables handling
|
||||
## Variable handling
|
||||
|
||||
Notice that variables bound in a clause "head" do not leak to the
|
||||
outer context:
|
||||
@@ -1735,7 +1745,8 @@ defmodule Kernel.SpecialForms do
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
value #=> unbound variable value
|
||||
value
|
||||
#=> unbound variable value
|
||||
|
||||
However, variables explicitly bound in the clause "body" are
|
||||
accessible from the outer context:
|
||||
@@ -1744,12 +1755,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
case lucky? do
|
||||
false -> value = 13
|
||||
true -> true
|
||||
true -> true
|
||||
end
|
||||
|
||||
value #=> 7 or 13
|
||||
value
|
||||
#=> 7 or 13
|
||||
|
||||
In the example above, value is going to be `7` or `13` depending on
|
||||
In the example above, `value` is going to be `7` or `13` depending on
|
||||
the value of `lucky?`. In case `value` has no previous value before
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to `nil`.
|
||||
@@ -1761,7 +1773,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
case 10 do
|
||||
^x -> "Won't match"
|
||||
_ -> "Will match"
|
||||
_ -> "Will match"
|
||||
end
|
||||
#=> "Will match"
|
||||
|
||||
@@ -1807,15 +1819,15 @@ defmodule Kernel.SpecialForms do
|
||||
do_something_that_may_fail(some_arg)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
IO.puts "Invalid argument given"
|
||||
IO.puts("Invalid argument given")
|
||||
catch
|
||||
value ->
|
||||
IO.puts "Caught #{inspect(value)}"
|
||||
IO.puts("Caught #{inspect(value)}")
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was #{inspect(value)}"
|
||||
IO.puts("Success! The result was #{inspect(value)}")
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
IO.puts("This is printed regardless if it failed or succeeded")
|
||||
end
|
||||
|
||||
The `rescue` clause is used to handle exceptions while the `catch`
|
||||
@@ -1910,7 +1922,7 @@ defmodule Kernel.SpecialForms do
|
||||
throw(:some_value)
|
||||
catch
|
||||
thrown_value ->
|
||||
IO.puts "A value was thrown: #{inspect(thrown_value)}"
|
||||
IO.puts("A value was thrown: #{inspect(thrown_value)}")
|
||||
end
|
||||
|
||||
### Catching values of any kind
|
||||
@@ -1922,15 +1934,15 @@ defmodule Kernel.SpecialForms do
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, value
|
||||
IO.puts "Exited with value #{inspect(value)}"
|
||||
: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)}"
|
||||
IO.puts("Caught exit or throw with value #{inspect(value)}")
|
||||
end
|
||||
|
||||
The `catch` clause also supports `:error` alongside `:exit` and `:throw` as
|
||||
@@ -2068,7 +2080,8 @@ defmodule Kernel.SpecialForms do
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
x #=> unbound variable "x"
|
||||
x
|
||||
#=> unbound variable "x"
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause. A common practice to address this issue
|
||||
@@ -2101,7 +2114,7 @@ defmodule Kernel.SpecialForms do
|
||||
name when is_atom(name) ->
|
||||
name
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
IO.puts(:stderr, "Unexpected message received")
|
||||
end
|
||||
|
||||
An optional `after` clause can be given in case the message was not
|
||||
@@ -2113,10 +2126,10 @@ defmodule Kernel.SpecialForms do
|
||||
name when is_atom(name) ->
|
||||
name
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
IO.puts(:stderr, "Unexpected message received")
|
||||
after
|
||||
5000 ->
|
||||
IO.puts :stderr, "No message in 5 seconds"
|
||||
IO.puts(:stderr, "No message in 5 seconds")
|
||||
end
|
||||
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
@@ -2133,7 +2146,7 @@ defmodule Kernel.SpecialForms do
|
||||
in hexadecimal notation) - it should be possible to represent the timeout
|
||||
value as an unsigned 32-bit integer.
|
||||
|
||||
## Variables handling
|
||||
## Variable handling
|
||||
|
||||
The `receive/1` special form handles variables exactly as the `case/2`
|
||||
special macro. For more information, check the docs for `case/2`.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
defmodule Kernel.Typespec do
|
||||
# TODO: Remove deprecated code on 2.0 and move this module to Module.Typespec.
|
||||
@moduledoc false
|
||||
|
||||
## Deprecated API moved to Code.Typespec
|
||||
@@ -76,12 +75,21 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def spec_to_callback(module, {name, arity} = signature)
|
||||
when is_atom(module) and is_atom(name) and arity in 0..255 do
|
||||
{_set, bag} = :elixir_module.data_tables(module)
|
||||
{set, bag} = :elixir_module.data_tables(module)
|
||||
|
||||
filter = fn {:spec, expr, pos} ->
|
||||
if spec_to_signature(expr) == signature do
|
||||
delete_typespec(bag, :spec, expr, pos)
|
||||
store_typespec(bag, :callback, expr, pos)
|
||||
kind = :callback
|
||||
store_typespec(bag, kind, expr, pos)
|
||||
|
||||
case :ets.lookup(set, {:function, name, arity}) do
|
||||
[{{:function, ^name, ^arity}, line, _, doc, doc_meta}] ->
|
||||
store_doc(set, kind, name, arity, line, :doc, doc, doc_meta)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
true
|
||||
else
|
||||
false
|
||||
@@ -109,8 +117,11 @@ defmodule Kernel.Typespec do
|
||||
|
||||
case spec_to_signature(expr) do
|
||||
{name, arity} ->
|
||||
{line, doc} = get_doc_info(set, :doc, line)
|
||||
store_doc(set, kind, name, arity, line, :doc, doc, %{})
|
||||
# store doc only once in case callback has multiple clauses
|
||||
unless :ets.member(set, {kind, name, arity}) do
|
||||
{line, doc} = get_doc_info(set, :doc, line)
|
||||
store_doc(set, kind, name, arity, line, :doc, doc, %{})
|
||||
end
|
||||
|
||||
:error ->
|
||||
:error
|
||||
@@ -131,7 +142,7 @@ defmodule Kernel.Typespec do
|
||||
warning =
|
||||
"type #{name}/#{arity} is private, @typedoc's are always discarded for private types"
|
||||
|
||||
:elixir_errors.warn(line, file, warning)
|
||||
:elixir_errors.erl_warn(line, file, warning)
|
||||
end
|
||||
|
||||
{name, arity} ->
|
||||
@@ -169,11 +180,6 @@ defmodule Kernel.Typespec do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_typespec(bag, key, expr, pos) do
|
||||
:ets.delete_object(bag, {{:accumulate, key}, {key, expr, pos}})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp store_doc(set, kind, name, arity, line, doc_kind, doc, spec_meta) do
|
||||
doc_meta = get_doc_meta(spec_meta, doc_kind, set)
|
||||
:ets.insert(set, {{kind, name, arity}, line, doc, doc_meta})
|
||||
@@ -196,11 +202,11 @@ defmodule Kernel.Typespec do
|
||||
defp spec_to_signature({:when, _, [spec, _]}), do: type_to_signature(spec)
|
||||
defp spec_to_signature(other), do: type_to_signature(other)
|
||||
|
||||
defp type_to_signature({:::, _, [{name, _, context}, _]})
|
||||
when is_atom(name) and name != ::: and is_atom(context),
|
||||
defp type_to_signature({:"::", _, [{name, _, context}, _]})
|
||||
when is_atom(name) and name != :"::" and is_atom(context),
|
||||
do: {name, 0}
|
||||
|
||||
defp type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name) and name != :::,
|
||||
defp type_to_signature({:"::", _, [{name, _, args}, _]}) when is_atom(name) and name != :"::",
|
||||
do: {name, length(args)}
|
||||
|
||||
defp type_to_signature(_), do: :error
|
||||
@@ -261,7 +267,7 @@ defmodule Kernel.Typespec do
|
||||
fun = fn {_kind, {name, arity} = type_pair, _line, _type, export} ->
|
||||
if not export and not :lists.member(type_pair, state.used_type_pairs) do
|
||||
%{^type_pair => {file, line}} = state.defined_type_pairs
|
||||
:elixir_errors.warn(line, file, "type #{name}/#{arity} is unused")
|
||||
:elixir_errors.erl_warn(line, file, "type #{name}/#{arity} is unused")
|
||||
false
|
||||
else
|
||||
true
|
||||
@@ -271,7 +277,7 @@ defmodule Kernel.Typespec do
|
||||
:lists.filter(fun, types)
|
||||
end
|
||||
|
||||
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos}, state) do
|
||||
defp translate_type({kind, {:"::", _, [{name, _, args}, definition]}, pos}, state) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
state = clean_local_state(state)
|
||||
|
||||
@@ -289,6 +295,7 @@ defmodule Kernel.Typespec do
|
||||
type = {name, spec, vars}
|
||||
arity = length(args)
|
||||
|
||||
ensure_no_underscore_local_vars!(caller, var_names)
|
||||
ensure_no_unused_local_vars!(caller, state.local_vars)
|
||||
|
||||
{kind, export} =
|
||||
@@ -312,7 +319,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
if underspecified?(kind, arity, spec) do
|
||||
message = "@#{kind} type #{name}/#{arity} is underspecified and therefore meaningless"
|
||||
:elixir_errors.warn(caller.line, caller.file, message)
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, message)
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, caller.line, type, export}, state}
|
||||
@@ -337,13 +344,13 @@ defmodule Kernel.Typespec do
|
||||
translate_spec(kind, spec, [], caller, state)
|
||||
end
|
||||
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller, state)
|
||||
when is_atom(name) and name != ::: do
|
||||
defp translate_spec(kind, {:"::", meta, [{name, _, args}, return]}, guard, caller, state)
|
||||
when is_atom(name) and name != :"::" do
|
||||
translate_spec(kind, meta, name, args, return, guard, caller, state)
|
||||
end
|
||||
|
||||
defp translate_spec(_kind, {name, _meta, _args} = spec, _guard, caller, _state)
|
||||
when is_atom(name) and name != ::: do
|
||||
when is_atom(name) and name != :"::" do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error(caller, "type specification missing return type: #{spec}")
|
||||
end
|
||||
@@ -382,7 +389,7 @@ defmodule Kernel.Typespec do
|
||||
{{kind, {name, arity}, caller.line, spec}, state}
|
||||
end
|
||||
|
||||
# TODO: Remove char_list type by 2.0
|
||||
# TODO: Remove char_list type by v2.0
|
||||
defp built_in_type?(:char_list, 0), do: true
|
||||
defp built_in_type?(:charlist, 0), do: true
|
||||
defp built_in_type?(:as_boolean, 1), do: true
|
||||
@@ -395,7 +402,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
fun = fn
|
||||
{:::, _, [left, right]} ->
|
||||
{:"::", _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
@@ -452,7 +459,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(
|
||||
{:<<>>, meta, [{:::, unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]},
|
||||
{:<<>>, meta, [{:"::", unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]},
|
||||
_,
|
||||
_,
|
||||
state
|
||||
@@ -462,7 +469,7 @@ defmodule Kernel.Typespec do
|
||||
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line(unit_meta), unit}]}, state}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
|
||||
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
|
||||
when is_atom(ctx) and is_integer(size) and size >= 0 do
|
||||
line = line(meta)
|
||||
{{:type, line, :binary, [{:integer, line(size_meta), size}, {:integer, line, 0}]}, state}
|
||||
@@ -473,8 +480,8 @@ defmodule Kernel.Typespec do
|
||||
:<<>>,
|
||||
meta,
|
||||
[
|
||||
{:::, size_meta, [{:_, _, ctx1}, size]},
|
||||
{:::, unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}
|
||||
{:"::", size_meta, [{:_, _, ctx1}, size]},
|
||||
{:"::", unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}
|
||||
]
|
||||
},
|
||||
_,
|
||||
@@ -502,11 +509,6 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec({:%{}, meta, fields} = map, vars, caller, state) do
|
||||
fun = fn
|
||||
{k, v}, state when is_atom(k) ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
|
||||
|
||||
{{:required, meta2, [k]}, v}, state ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
@@ -518,15 +520,9 @@ defmodule Kernel.Typespec do
|
||||
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
|
||||
|
||||
{k, v}, state ->
|
||||
# TODO: Warn on Elixir v1.8 (since v1.6 is the first version to drop support for 18 and
|
||||
# older)
|
||||
# warning =
|
||||
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
|
||||
# "%{required(foo) => bar} and %{optional(foo) => bar}."
|
||||
# :elixir_errors.warn(caller.line, caller.file, warning)
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_assoc, [arg1, arg2]}, state}
|
||||
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
|
||||
|
||||
{:|, _, [_, _]}, _state ->
|
||||
error =
|
||||
@@ -561,7 +557,7 @@ defmodule Kernel.Typespec do
|
||||
types =
|
||||
:lists.map(
|
||||
fn {field, _} -> {field, Keyword.get(fields, field, quote(do: term()))} end,
|
||||
struct
|
||||
:lists.sort(struct)
|
||||
)
|
||||
|
||||
fun = fn {field, _} ->
|
||||
@@ -590,7 +586,13 @@ defmodule Kernel.Typespec do
|
||||
{_, _, [name, fields | _]} when is_list(fields) ->
|
||||
types =
|
||||
:lists.map(
|
||||
fn {field, _} -> Keyword.get(field_specs, field, quote(do: term())) end,
|
||||
fn {field, _} ->
|
||||
{:"::", [],
|
||||
[
|
||||
{field, [], nil},
|
||||
Keyword.get(field_specs, field, quote(do: term()))
|
||||
]}
|
||||
end,
|
||||
fields
|
||||
)
|
||||
|
||||
@@ -643,7 +645,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle type operator
|
||||
defp typespec(
|
||||
{:::, meta, [{var_name, var_meta, context}, expr]} = ann_type,
|
||||
{:"::", meta, [{var_name, var_meta, context}, expr]} = ann_type,
|
||||
vars,
|
||||
caller,
|
||||
state
|
||||
@@ -655,8 +657,8 @@ defmodule Kernel.Typespec do
|
||||
"invalid type annotation. Type annotations cannot be nested: " <>
|
||||
"#{Macro.to_string(ann_type)}"
|
||||
|
||||
# TODO: make this an error in elixir 2.0 and remove the code below
|
||||
:elixir_errors.warn(caller.line, caller.file, message)
|
||||
# TODO: Make this an error on v2.0 and remove the code below
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, message)
|
||||
|
||||
# This may be generating an invalid typespec but we need to generate it
|
||||
# to avoid breaking existing code that was valid but only broke dialyzer
|
||||
@@ -668,14 +670,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec({:::, meta, [left, right]} = expr, vars, caller, state) do
|
||||
defp typespec({:"::", meta, [left, right]} = expr, vars, caller, state) do
|
||||
message =
|
||||
"invalid type annotation. When using the | operator to represent the union of types, " <>
|
||||
"make sure to wrap type annotations in parentheses: #{Macro.to_string(expr)}"
|
||||
|
||||
# TODO: make this an error in Elixir 2.0, and remove the code below and the
|
||||
# :undefined_type_error_enabled? key from the state
|
||||
:elixir_errors.warn(caller.line, caller.file, message)
|
||||
# TODO: Make this an error on v2.0, and remove the code below and
|
||||
# the :undefined_type_error_enabled? key from the state
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, message)
|
||||
|
||||
# This may be generating an invalid typespec but we need to generate it
|
||||
# to avoid breaking existing code that was valid but only broke dialyzer
|
||||
@@ -769,8 +771,7 @@ defmodule Kernel.Typespec do
|
||||
"For character lists, use charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, warning)
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :string, args}, state}
|
||||
end
|
||||
@@ -781,17 +782,15 @@ defmodule Kernel.Typespec do
|
||||
"For non-empty character lists, use nonempty_charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, warning)
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :nonempty_string, args}, state}
|
||||
end
|
||||
|
||||
# TODO: Remove char_list type by 2.0
|
||||
defp typespec({type, _meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
|
||||
if type == :char_list do
|
||||
warning = "the char_list() type is deprecated, use charlist()"
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, warning)
|
||||
end
|
||||
|
||||
typespec(quote(do: :elixir.charlist()), vars, caller, state)
|
||||
@@ -962,10 +961,37 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_no_underscore_local_vars!(caller, var_names) do
|
||||
case :lists.member(:_, var_names) do
|
||||
true ->
|
||||
compile_error(caller, "type variable '_' is invalid")
|
||||
|
||||
false ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_no_unused_local_vars!(caller, local_vars) do
|
||||
fun = fn
|
||||
{name, :used_once} -> compile_error(caller, "type variable #{name} is unused")
|
||||
_ -> :ok
|
||||
fun = fn {name, used_times} ->
|
||||
case {:erlang.atom_to_list(name), used_times} do
|
||||
{[?_ | _], :used_once} ->
|
||||
:ok
|
||||
|
||||
{[?_ | _], :used_multiple} ->
|
||||
warning =
|
||||
"the underscored type variable \"#{name}\" is used more than once in the " <>
|
||||
"type specification. A leading underscore indicates that the value of the " <>
|
||||
"variable should be ignored. If this is intended please rename the variable to " <>
|
||||
"remove the underscore"
|
||||
|
||||
:elixir_errors.erl_warn(caller.line, caller.file, warning)
|
||||
|
||||
{_, :used_once} ->
|
||||
compile_error(caller, "type variable #{name} is unused")
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
:lists.foreach(fun, :maps.to_list(local_vars))
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule Kernel.Utils do
|
||||
Callback for defdelegate.
|
||||
"""
|
||||
def defdelegate(fun, opts) when is_list(opts) do
|
||||
# TODO: Remove by 2.0
|
||||
# TODO: Remove on v2.0
|
||||
append_first? = Keyword.get(opts, :append_first, false)
|
||||
|
||||
{name, args} =
|
||||
@@ -114,7 +114,7 @@ defmodule Kernel.Utils do
|
||||
def announce_struct(module) do
|
||||
case :erlang.get(:elixir_compiler_pid) do
|
||||
:undefined -> :ok
|
||||
pid -> send(pid, {:struct_available, module})
|
||||
pid -> send(pid, {:available, :struct, module})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -164,11 +164,13 @@ defmodule Kernel.Utils do
|
||||
that checks for its presence in a guard, then unquotes the variable references as
|
||||
appropriate.
|
||||
|
||||
The resulting transformation looks something like this:
|
||||
The following code
|
||||
|
||||
> expression = quote do: is_integer(value) and rem(value, 2) == 0
|
||||
> variable_references = [value: Elixir]
|
||||
> Kernel.Utils.defguard(expression, variable_references) |> Macro.to_string |> IO.puts
|
||||
expression = quote do: is_integer(value) and rem(value, 2) == 0
|
||||
variable_references = [value: Elixir]
|
||||
Kernel.Utils.defguard(expression, variable_references) |> Macro.to_string() |> IO.puts()
|
||||
|
||||
would print a code similar to:
|
||||
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
|
||||
+24
-15
@@ -2,7 +2,7 @@ defmodule Keyword do
|
||||
@moduledoc """
|
||||
A set of functions for working with keywords.
|
||||
|
||||
A keyword is a list of two-element tuples where the first
|
||||
A keyword list is a list of two-element tuples where the first
|
||||
element of the tuple is an atom and the second element
|
||||
can be any value.
|
||||
|
||||
@@ -67,6 +67,10 @@ defmodule Keyword do
|
||||
it comes to ordering. However, since a keyword list is simply a
|
||||
list, all the operations defined in `Enum` and `List` can be
|
||||
applied too, especially when ordering is required.
|
||||
|
||||
Most of the functions in this module work in linear time. This means
|
||||
that, the time it takes to perform an operation grows at the same
|
||||
rate as the length of the list.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -116,7 +120,7 @@ defmodule Keyword do
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a keyword from an enumerable.
|
||||
Creates a keyword list from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
Unlike `Enum.into(enumerable, [])`, `Keyword.new(enumerable)`
|
||||
@@ -137,7 +141,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
Creates a keyword list from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
Unlike `Enum.into(enumerable, [], fun)`,
|
||||
@@ -150,7 +154,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec new(Enum.t(), (term -> {key, value})) :: t
|
||||
def new(pairs, transform) do
|
||||
def new(pairs, transform) when is_function(transform, 1) do
|
||||
fun = fn el, acc ->
|
||||
{k, v} = transform.(el)
|
||||
put_new(acc, k, v)
|
||||
@@ -827,7 +831,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec update!(t, key, (value -> value)) :: t
|
||||
def update!(keywords, key, fun) do
|
||||
def update!(keywords, key, fun)
|
||||
when is_list(keywords) and is_atom(key) and is_function(fun, 1) do
|
||||
update!(keywords, key, fun, keywords)
|
||||
end
|
||||
|
||||
@@ -895,7 +900,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
def split(keywords, keys) when is_list(keywords) and is_list(keys) do
|
||||
fun = fn {k, v}, {take, drop} ->
|
||||
case k in keys do
|
||||
true -> {[{k, v} | take], drop}
|
||||
@@ -923,7 +928,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec take(t, [key]) :: t
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
def take(keywords, keys) when is_list(keywords) and is_list(keys) do
|
||||
:lists.filter(fn {k, _} -> k in keys end, keywords)
|
||||
end
|
||||
|
||||
@@ -941,15 +946,20 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec drop(t, [key]) :: t
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
def drop(keywords, keys) when is_list(keywords) and is_list(keys) do
|
||||
:lists.filter(fn {key, _} -> key not in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns and removes all values associated with `key` in the keyword list.
|
||||
Returns the first value for `key` and removes all associated entries in the keyword list.
|
||||
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
It returns a tuple where the first element is the first value for `key` and the
|
||||
second element is a keyword list with all entries associated with `key` removed.
|
||||
If the `key` is not present in the keyword list, `{default, keyword_list}` is
|
||||
returned.
|
||||
|
||||
If you don't want to remove all the entries associated with `key` use `pop_first/3`
|
||||
instead, that function will remove only the first entry.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -964,7 +974,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case fetch(keywords, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(keywords, key)}
|
||||
@@ -998,7 +1008,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
|
||||
def pop_lazy(keywords, key, fun)
|
||||
when is_list(keywords) and is_function(fun, 0) do
|
||||
when is_list(keywords) and is_atom(key) and is_function(fun, 0) do
|
||||
case fetch(keywords, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(keywords, key)}
|
||||
@@ -1026,7 +1036,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec pop_first(t, key, value) :: {value, t}
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keytake(key, 1, keywords) do
|
||||
{:value, {^key, value}, rest} -> {value, rest}
|
||||
false -> {default, keywords}
|
||||
@@ -1048,7 +1058,6 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use Kernel.length/1 instead"
|
||||
def size(keyword) do
|
||||
length(keyword)
|
||||
|
||||
+111
-86
@@ -63,21 +63,29 @@ defmodule List do
|
||||
iex> list ++ [4] # slow
|
||||
[1, 2, 3, 4]
|
||||
|
||||
Additionally, 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.
|
||||
Most of the functions in this module work in linear time. This means that,
|
||||
that the time it takes to perform an operation grows at the same rate as the
|
||||
length of the list. For example `length/1` and `last/1` will run in linear
|
||||
time because they need to iterate through every element of the list, but
|
||||
`first/1` will run in constant time because it only needs the first element.
|
||||
|
||||
## Charlists
|
||||
|
||||
If a list is made of non-negative integers, it can also be called
|
||||
a charlist. Elixir uses single quotes to define charlists:
|
||||
If a list is made of non-negative integers, where each integer represents a
|
||||
Unicode code point, the list can also be called a charlist. These integers
|
||||
must:
|
||||
|
||||
* be within the range `0..0x10FFFF` (`0..1_114_111`);
|
||||
* and be out of the range `0xD800..0xDFFF` (`55_296..57_343`), which is
|
||||
reserved in Unicode for UTF-16 surrogate pairs.
|
||||
|
||||
Elixir uses single quotes to define charlists:
|
||||
|
||||
iex> 'héllo'
|
||||
[104, 233, 108, 108, 111]
|
||||
|
||||
In particular, charlists may be printed back in single
|
||||
quotes if they contain only ASCII-printable codepoints:
|
||||
In particular, charlists will be printed back by default in single
|
||||
quotes if they contain only printable ASCII characters:
|
||||
|
||||
iex> 'abc'
|
||||
'abc'
|
||||
@@ -96,17 +104,19 @@ defmodule List do
|
||||
#=> {:logger, 'logger', '1.0.0'}
|
||||
#=> ]
|
||||
|
||||
A list can be checked if it is made of printable ASCII
|
||||
codepoints with `ascii_printable?/2`.
|
||||
A list can be checked if it is made of only printable ASCII
|
||||
characters with `ascii_printable?/2`.
|
||||
|
||||
Improper lists are never deemed as charlists.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Deletes the given `item` from the `list`. Returns a new list without
|
||||
the item.
|
||||
Deletes the given `element` from the `list`. Returns a new list without
|
||||
the element.
|
||||
|
||||
If the `item` occurs more than once in the `list`, just
|
||||
If the `element` occurs more than once in the `list`, just
|
||||
the first occurrence is removed.
|
||||
|
||||
## Examples
|
||||
@@ -114,29 +124,47 @@ defmodule List do
|
||||
iex> List.delete([:a, :b, :c], :a)
|
||||
[:b, :c]
|
||||
|
||||
iex> List.delete([:a, :b, :c], :d)
|
||||
[:a, :b, :c]
|
||||
|
||||
iex> List.delete([:a, :b, :b, :c], :b)
|
||||
[:a, :b, :c]
|
||||
|
||||
iex> List.delete([], :b)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec delete(list, any) :: list
|
||||
def delete(list, item)
|
||||
def delete([item | list], item), do: list
|
||||
def delete([other | list], item), do: [other | delete(list, item)]
|
||||
def delete([], _item), do: []
|
||||
@spec delete([], any) :: []
|
||||
@spec delete([...], any) :: list
|
||||
def delete(list, element)
|
||||
def delete([element | list], element), do: list
|
||||
def delete([other | list], element), do: [other | delete(list, element)]
|
||||
def delete([], _element), do: []
|
||||
|
||||
@doc """
|
||||
Duplicates the given element `n` times in a list.
|
||||
|
||||
`n` is an integer greater than or equal to `0`.
|
||||
|
||||
If `n` is `0`, an empty list is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello", "hello", "hello"]
|
||||
iex> List.duplicate("hello", 0)
|
||||
[]
|
||||
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1, 2], [1, 2]]
|
||||
iex> List.duplicate("hi", 1)
|
||||
["hi"]
|
||||
|
||||
iex> List.duplicate("bye", 2)
|
||||
["bye", "bye"]
|
||||
|
||||
iex> List.duplicate([1, 2], 3)
|
||||
[[1, 2], [1, 2], [1, 2]]
|
||||
|
||||
"""
|
||||
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
|
||||
@spec duplicate(any, 0) :: []
|
||||
@spec duplicate(elem, pos_integer) :: [elem, ...] when elem: var
|
||||
def duplicate(elem, n) do
|
||||
:lists.duplicate(n, elem)
|
||||
end
|
||||
@@ -144,11 +172,16 @@ defmodule List do
|
||||
@doc """
|
||||
Flattens the given `list` of nested lists.
|
||||
|
||||
Empty list elements are discarded.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1, [[2], 3]])
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.flatten([[], [[], []]])
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
|
||||
def flatten(list) do
|
||||
@@ -160,11 +193,17 @@ defmodule List do
|
||||
The list `tail` will be added at the end of
|
||||
the flattened list.
|
||||
|
||||
Empty list elements from `list` are discarded,
|
||||
but not the ones from `tail`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1, 2, 3, 4, 5]
|
||||
|
||||
iex> List.flatten([1, [], 2], [3, [], 4])
|
||||
[1, 2, 3, [], 4]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
|
||||
def flatten(list, tail) do
|
||||
@@ -219,7 +258,8 @@ defmodule List do
|
||||
1
|
||||
|
||||
"""
|
||||
@spec first([elem]) :: nil | elem when elem: var
|
||||
@spec first([]) :: nil
|
||||
@spec first([elem, ...]) :: elem when elem: var
|
||||
def first([]), do: nil
|
||||
def first([head | _]), do: head
|
||||
|
||||
@@ -238,16 +278,19 @@ defmodule List do
|
||||
3
|
||||
|
||||
"""
|
||||
@spec last([elem]) :: nil | elem when elem: var
|
||||
@spec last([]) :: nil
|
||||
@spec last([elem, ...]) :: elem when elem: var
|
||||
def last([]), do: nil
|
||||
def last([head]), do: head
|
||||
def last([_ | tail]), do: last(tail)
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at `position` in the tuple matches the
|
||||
where the element at `position` in the tuple matches the
|
||||
given `key`.
|
||||
|
||||
If no matching tuple is found, `default` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], :a, 0)
|
||||
@@ -267,7 +310,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns `true` if there is
|
||||
a tuple where the item at `position` in the tuple matches
|
||||
a tuple where the element at `position` in the tuple matches
|
||||
the given `key`.
|
||||
|
||||
## Examples
|
||||
@@ -288,7 +331,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and if the identified item by `key` at `position`
|
||||
Receives a list of tuples and if the identified element by `key` at `position`
|
||||
exists, it is replaced with `new_tuple`.
|
||||
|
||||
## Examples
|
||||
@@ -306,7 +349,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and sorts the items
|
||||
Receives a list of tuples and sorts the elements
|
||||
at `position` of the tuples. The sort is stable.
|
||||
|
||||
## Examples
|
||||
@@ -324,10 +367,10 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a `list` of tuples and replaces the item
|
||||
Receives a `list` of tuples and replaces the element
|
||||
identified by `key` at `position` with `new_tuple`.
|
||||
|
||||
If the item does not exist, it is added to the end of the `list`.
|
||||
If the element does not exist, it is added to the end of the `list`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -345,7 +388,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a `list` of tuples and deletes the first tuple
|
||||
where the item at `position` matches the
|
||||
where the element at `position` matches the
|
||||
given `key`. Returns the new list.
|
||||
|
||||
## Examples
|
||||
@@ -387,7 +430,7 @@ defmodule List do
|
||||
@spec keytake([tuple], any, non_neg_integer) :: {tuple, [tuple]} | nil
|
||||
def keytake(list, key, position) do
|
||||
case :lists.keytake(key, position + 1, list) do
|
||||
{:value, item, list} -> {item, list}
|
||||
{:value, element, list} -> {element, list}
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
@@ -410,9 +453,7 @@ defmodule List do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec wrap(nil) :: []
|
||||
@spec wrap(list) :: list when list: maybe_improper_list()
|
||||
@spec wrap(term) :: nonempty_list(term) when term: any()
|
||||
@spec wrap(term) :: maybe_improper_list()
|
||||
def wrap(term)
|
||||
|
||||
def wrap(list) when is_list(list) do
|
||||
@@ -488,8 +529,11 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec ascii_printable?(list, limit) :: boolean
|
||||
when limit: :infinity | non_neg_integer
|
||||
@spec ascii_printable?(list, 0) :: true
|
||||
@spec ascii_printable?([], limit) :: true
|
||||
when limit: :infinity | pos_integer
|
||||
@spec ascii_printable?([...], limit) :: boolean
|
||||
when limit: :infinity | pos_integer
|
||||
def ascii_printable?(list, limit \\ :infinity)
|
||||
when is_list(list) and (limit == :infinity or (is_integer(limit) and limit >= 0)) do
|
||||
ascii_printable_guarded?(list, limit)
|
||||
@@ -500,39 +544,9 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([char | rest], counter)
|
||||
when is_integer(char) and char >= 32 and char <= 126 do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\n | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\r | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\t | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\v | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\b | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\f | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\e | rest], counter) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
defp ascii_printable_guarded?([?\a | rest], counter) do
|
||||
# 7..13 is the range '\a\b\t\n\v\f\r'. 32..126 are ASCII printables.
|
||||
when is_integer(char) and
|
||||
((char >= 7 and char <= 13) or char == ?\e or (char >= 32 and char <= 126)) do
|
||||
ascii_printable_guarded?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
@@ -548,11 +562,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.improper?([1, 2 | 3])
|
||||
true
|
||||
iex> List.improper?([1, 2 | 3])
|
||||
true
|
||||
|
||||
iex> List.improper?([1, 2, 3])
|
||||
false
|
||||
iex> List.improper?([1, 2, 3])
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@@ -736,7 +750,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec starts_with?(list, list) :: boolean
|
||||
@spec starts_with?(nonempty_list, nonempty_list) :: boolean
|
||||
@spec starts_with?(list, []) :: true
|
||||
@spec starts_with?([], nonempty_list) :: false
|
||||
def starts_with?(list, prefix)
|
||||
@@ -749,7 +763,7 @@ defmodule List do
|
||||
Converts a charlist to an atom.
|
||||
|
||||
Elixir supports conversions from charlists which contains any Unicode
|
||||
codepoint.
|
||||
code point.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -772,7 +786,7 @@ defmodule List do
|
||||
if the atom does not exist.
|
||||
|
||||
Elixir supports conversions from charlists which contains any Unicode
|
||||
codepoint.
|
||||
code point.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -860,11 +874,18 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integers representing codepoints, lists or
|
||||
Converts a list of integers representing code points, lists or
|
||||
strings into a string.
|
||||
|
||||
To be converted to a string, a list must either be empty or only
|
||||
contain the following elements:
|
||||
|
||||
* strings
|
||||
* integers representing Unicode code points
|
||||
* a list containing one of these three elements
|
||||
|
||||
Notice that this function expects a list of integers representing
|
||||
UTF-8 codepoints. If you have a list of bytes, you must instead use
|
||||
UTF-8 code points. If you have a list of bytes, you must instead use
|
||||
the [`:binary` module](http://www.erlang.org/doc/man/binary.html).
|
||||
|
||||
## Examples
|
||||
@@ -878,6 +899,9 @@ defmodule List do
|
||||
iex> List.to_string([0x0064, "ee", ['p']])
|
||||
"deep"
|
||||
|
||||
iex> List.to_string([])
|
||||
""
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.charlist()) :: String.t()
|
||||
def to_string(list) when is_list(list) do
|
||||
@@ -888,11 +912,12 @@ defmodule List do
|
||||
raise ArgumentError, """
|
||||
cannot convert the given list to a string.
|
||||
|
||||
To be converted to a string, a list must contain only:
|
||||
To be converted to a string, a list must either be empty or only
|
||||
contain the following elements:
|
||||
|
||||
* strings
|
||||
* integers representing Unicode codepoints
|
||||
* or a list containing one of these three elements
|
||||
* integers representing Unicode code points
|
||||
* a list containing one of these three elements
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
@@ -911,11 +936,11 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integers representing codepoints, lists or
|
||||
Converts a list of integers representing code points, lists or
|
||||
strings into a charlist.
|
||||
|
||||
Notice that this function expects a list of integers representing
|
||||
UTF-8 codepoints. If you have a list of bytes, you must instead use
|
||||
UTF-8 code points. If you have a list of bytes, you must instead use
|
||||
the [`:binary` module](http://www.erlang.org/doc/man/binary.html).
|
||||
|
||||
## Examples
|
||||
@@ -943,7 +968,7 @@ defmodule List do
|
||||
To be converted to a charlist, a list must contain only:
|
||||
|
||||
* strings
|
||||
* integers representing Unicode codepoints
|
||||
* integers representing Unicode code points
|
||||
* or a list containing one of these three elements
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
@@ -17,7 +17,6 @@ defprotocol List.Chars do
|
||||
def to_charlist(term)
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use List.Chars.to_charlist/1 instead"
|
||||
Kernel.def to_char_list(term) do
|
||||
__MODULE__.to_charlist(term)
|
||||
|
||||
+29
-19
@@ -206,7 +206,11 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
def generate_arguments(0, _), do: []
|
||||
@spec generate_arguments(0, context :: atom) :: []
|
||||
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
|
||||
def generate_arguments(amount, context)
|
||||
|
||||
def generate_arguments(0, context) when is_atom(context), do: []
|
||||
|
||||
def generate_arguments(amount, context)
|
||||
when is_integer(amount) and amount > 0 and is_atom(context) do
|
||||
@@ -502,7 +506,7 @@ defmodule Macro do
|
||||
|
||||
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Bytes can be
|
||||
given as hexadecimals via `\xNN` and Unicode Codepoints as
|
||||
given as hexadecimals via `\xNN` and Unicode code points as
|
||||
`\uNNNN` escapes.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
@@ -531,7 +535,7 @@ defmodule Macro do
|
||||
## Map
|
||||
|
||||
The map must be a function. The function receives an integer
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
representing the code point of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(unicode), do: true
|
||||
@@ -552,8 +556,8 @@ defmodule Macro do
|
||||
If the `unescape_map/1` function returns `false`, the char is
|
||||
not escaped and the backslash is kept in the string.
|
||||
|
||||
Hexadecimals and Unicode codepoints will be escaped if the map
|
||||
function returns `true` for `?x`. Unicode codepoints if the map
|
||||
Hexadecimals and Unicode code points will be escaped if the map
|
||||
function returns `true` for `?x`. Unicode code points if the map
|
||||
function returns `true` for `?u`.
|
||||
|
||||
## Examples
|
||||
@@ -613,7 +617,7 @@ defmodule Macro do
|
||||
def to_string(tree, fun \\ fn _ast, string -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
|
||||
def to_string({var, _, context} = ast, fun) when is_atom(var) and is_atom(context) do
|
||||
fun.(ast, Atom.to_string(var))
|
||||
end
|
||||
|
||||
@@ -808,9 +812,10 @@ defmodule Macro do
|
||||
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
|
||||
end
|
||||
|
||||
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
|
||||
defp bitpart_to_string({:"::", _, [left, right]} = ast, fun) do
|
||||
result =
|
||||
op_to_string(left, fun, :::, :left) <> "::" <> bitmods_to_string(right, fun, :::, :right)
|
||||
op_to_string(left, fun, :"::", :left) <>
|
||||
"::" <> bitmods_to_string(right, fun, :"::", :right)
|
||||
|
||||
fun.(ast, result)
|
||||
end
|
||||
@@ -843,7 +848,7 @@ defmodule Macro do
|
||||
# Check if we have an interpolated string.
|
||||
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
|
||||
Enum.all?(parts, fn
|
||||
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
_ -> false
|
||||
end)
|
||||
@@ -856,7 +861,7 @@ defmodule Macro do
|
||||
defp interpolate({:<<>>, _, parts}, fun) do
|
||||
parts =
|
||||
Enum.map_join(parts, "", fn
|
||||
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [arg]}, {:binary, _, _}]} ->
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [arg]}, {:binary, _, _}]} ->
|
||||
"\#{" <> to_string(arg, fun) <> "}"
|
||||
|
||||
binary when is_binary(binary) ->
|
||||
@@ -1137,9 +1142,11 @@ defmodule Macro do
|
||||
* Module attributes reader (`@foo`)
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `expand/2` for expansion
|
||||
until the node can no longer be expanded.
|
||||
itself. This function does not traverse the AST, only the root
|
||||
node is expanded.
|
||||
|
||||
`expand_once/2` performs the expansion just once. Check `expand/2`
|
||||
to perform expansion until the node can no longer be expanded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1358,19 +1365,22 @@ defmodule Macro do
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
|
||||
Note this function does not traverse the AST, only the root
|
||||
node is expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
it out for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
def expand(ast, env) do
|
||||
expand_until({ast, true}, env)
|
||||
end
|
||||
|
||||
defp expand_until({tree, true}, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
defp expand_until({ast, true}, env) do
|
||||
expand_until(do_expand_once(ast, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
defp expand_until({ast, false}, _env) do
|
||||
ast
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -69,8 +69,8 @@ defmodule Macro.Env do
|
||||
@typep vars :: [variable]
|
||||
@typep var_type :: :term
|
||||
@typep var_version :: non_neg_integer
|
||||
@typep unused_vars :: %{{variable, var_version} => non_neg_integer | false}
|
||||
@typep current_vars :: %{variable => {var_version, var_type}}
|
||||
@typep unused_vars :: %{optional({variable, var_version}) => non_neg_integer | false}
|
||||
@typep current_vars :: %{optional(variable) => {var_version, var_type}}
|
||||
@typep prematch_vars :: current_vars | :warn | :raise | :pin | :apply
|
||||
@typep contextual_vars :: [atom]
|
||||
|
||||
@@ -85,7 +85,7 @@ defmodule Macro.Env do
|
||||
aliases: aliases,
|
||||
functions: functions,
|
||||
macros: macros,
|
||||
macro_aliases: aliases,
|
||||
macro_aliases: macro_aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
unused_vars: unused_vars,
|
||||
|
||||
+47
-20
@@ -91,6 +91,12 @@ defmodule Map do
|
||||
iex> %{map | three: 3}
|
||||
** (KeyError) key :three not found
|
||||
|
||||
The functions in this module that need to find a specific key work in logarithmic time.
|
||||
This means that the time it takes to find keys grows as the map grows, but it's not
|
||||
directly proportional to the map size. In comparison to finding an element in a list,
|
||||
it performs better because lists have a linear time complexity. Some functions,
|
||||
such as `keys/1` and `values/1`, run in linear time because they need to get to every
|
||||
element in the map.
|
||||
"""
|
||||
|
||||
@type key :: any
|
||||
@@ -206,8 +212,8 @@ defmodule Map do
|
||||
|> :maps.from_list()
|
||||
end
|
||||
|
||||
defp new_transform([item | rest], fun, acc) do
|
||||
new_transform(rest, fun, [fun.(item) | acc])
|
||||
defp new_transform([element | rest], fun, acc) do
|
||||
new_transform(rest, fun, [fun.(element) | acc])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -378,13 +384,20 @@ defmodule Map do
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec take(map, Enumerable.t()) :: map
|
||||
@spec take(map, [key]) :: map
|
||||
def take(map, keys)
|
||||
|
||||
def take(map, keys) when is_map(map) and is_list(keys) do
|
||||
take(keys, map, _acc = [])
|
||||
end
|
||||
|
||||
def take(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> take(map, [])
|
||||
IO.warn(
|
||||
"Map.take/2 with an Enumerable of keys that is not a list is deprecated. " <>
|
||||
" Use a list of keys instead."
|
||||
)
|
||||
|
||||
take(map, Enum.to_list(keys))
|
||||
end
|
||||
|
||||
def take(non_map, _keys) do
|
||||
@@ -409,8 +422,9 @@ defmodule Map do
|
||||
Gets the value for a specific `key` in `map`.
|
||||
|
||||
If `key` is present in `map` with value `value`, then `value` is
|
||||
returned. Otherwise, `default` is returned (which is `nil` unless
|
||||
specified otherwise).
|
||||
returned. Otherwise, `default` is returned.
|
||||
|
||||
If `default` is not provided, `nil` is used.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -670,23 +684,30 @@ defmodule Map do
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec drop(map, Enumerable.t()) :: map
|
||||
@spec drop(map, [key]) :: map
|
||||
def drop(map, keys)
|
||||
|
||||
def drop(map, keys) when is_map(map) and is_list(keys) do
|
||||
drop_keys(keys, map)
|
||||
end
|
||||
|
||||
def drop(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> drop_list(map)
|
||||
IO.warn(
|
||||
"Map.drop/2 with an Enumerable of keys that is not a list is deprecated. " <>
|
||||
" Use a list of keys instead."
|
||||
)
|
||||
|
||||
drop(map, Enum.to_list(keys))
|
||||
end
|
||||
|
||||
def drop(non_map, keys) do
|
||||
:erlang.error({:badmap, non_map}, [non_map, keys])
|
||||
end
|
||||
|
||||
defp drop_list([], acc), do: acc
|
||||
defp drop_keys([], acc), do: acc
|
||||
|
||||
defp drop_list([key | rest], acc) do
|
||||
drop_list(rest, delete(acc, key))
|
||||
defp drop_keys([key | rest], acc) do
|
||||
drop_keys(rest, delete(acc, key))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -703,13 +724,20 @@ defmodule Map do
|
||||
{%{a: 1, c: 3}, %{b: 2}}
|
||||
|
||||
"""
|
||||
@spec split(map, Enumerable.t()) :: {map, map}
|
||||
@spec split(map, [key]) :: {map, map}
|
||||
def split(map, keys)
|
||||
|
||||
def split(map, keys) when is_map(map) and is_list(keys) do
|
||||
split(keys, [], map)
|
||||
end
|
||||
|
||||
def split(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> split([], map)
|
||||
IO.warn(
|
||||
"Map.split/2 with an Enumerable of keys that is not a list is deprecated. " <>
|
||||
" Use a list of keys instead."
|
||||
)
|
||||
|
||||
split(map, Enum.to_list(keys))
|
||||
end
|
||||
|
||||
def split(non_map, keys) do
|
||||
@@ -892,7 +920,6 @@ defmodule Map do
|
||||
def equal?(term, other), do: :erlang.error({:badmap, term}, [term, other])
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use Kernel.map_size/1 instead"
|
||||
def size(map) do
|
||||
map_size(map)
|
||||
|
||||
@@ -30,6 +30,10 @@ defmodule MapSet do
|
||||
|
||||
`MapSet`s can also be constructed starting from other collection-type data
|
||||
structures: for example, see `MapSet.new/1` or `Enum.into/2`.
|
||||
|
||||
`MapSet` is built on top of `Map`, this means that they share many properties,
|
||||
including logarithmic time complexity. See the documentation for `Map` for more
|
||||
information on its execution time complexity.
|
||||
"""
|
||||
|
||||
# MapSets have an underlying Map. MapSet elements are keys of said map,
|
||||
@@ -41,7 +45,7 @@ defmodule MapSet do
|
||||
@opaque t(value) :: %__MODULE__{map: %{optional(value) => []}}
|
||||
@type t :: t(term)
|
||||
|
||||
# TODO: Remove version key on Elixir 2.0
|
||||
# TODO: Remove version key on v2.0
|
||||
defstruct map: %{}, version: 2
|
||||
|
||||
@doc """
|
||||
@@ -104,16 +108,16 @@ defmodule MapSet do
|
||||
:maps.from_list(acc)
|
||||
end
|
||||
|
||||
defp new_from_list([item | rest], acc) do
|
||||
new_from_list(rest, [{item, @dummy_value} | acc])
|
||||
defp new_from_list([element | rest], acc) do
|
||||
new_from_list(rest, [{element, @dummy_value} | acc])
|
||||
end
|
||||
|
||||
defp new_from_list_transform([], _fun, acc) do
|
||||
:maps.from_list(acc)
|
||||
end
|
||||
|
||||
defp new_from_list_transform([item | rest], fun, acc) do
|
||||
new_from_list_transform(rest, fun, [{fun.(item), @dummy_value} | acc])
|
||||
defp new_from_list_transform([element | rest], fun, acc) do
|
||||
new_from_list_transform(rest, fun, [{fun.(element), @dummy_value} | acc])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -148,7 +152,7 @@ defmodule MapSet do
|
||||
def difference(map_set1, map_set2)
|
||||
|
||||
# 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
|
||||
# it is fastest to re-accumulate elements 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
|
||||
@@ -161,7 +165,7 @@ defmodule MapSet do
|
||||
end
|
||||
|
||||
# If the second set is less than half the size of the first set, it's fastest
|
||||
# to simply iterate through each item in the second set, deleting them from
|
||||
# to simply iterate through each element in the second set, deleting them from
|
||||
# the first set.
|
||||
def difference(%MapSet{map: map1} = map_set, %MapSet{map: map2}) do
|
||||
%{map_set | map: Map.drop(map1, Map.keys(map2))}
|
||||
|
||||
+142
-78
@@ -8,7 +8,8 @@ defmodule Module do
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their scope
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__/1` function attached to each compiled module.
|
||||
via the [`__info__/1`](`c:Module.__info__/1`) function attached to
|
||||
each compiled module.
|
||||
|
||||
## Module attributes
|
||||
|
||||
@@ -174,7 +175,7 @@ defmodule Module do
|
||||
|
||||
Accepts a string (often a heredoc) or `false` where `@doc false` will
|
||||
make the entity invisible to documentation extraction tools like
|
||||
ExDoc. For example:
|
||||
[`ExDoc`](https://hexdocs.pm/ex_doc/). For example:
|
||||
|
||||
defmodule MyModule do
|
||||
@typedoc "This type"
|
||||
@@ -197,9 +198,10 @@ defmodule Module do
|
||||
|
||||
As can be seen in the example above, `@doc` and `@typedoc` also accept
|
||||
a keyword list that serves as a way to provide arbitrary metadata
|
||||
about the entity. Tools like ExDoc and IEx may use this information to
|
||||
display annotations. A common use case is `since` that may be used
|
||||
to annotate in which version the function was introduced.
|
||||
about the entity. Tools like [`ExDoc`](https://hexdocs.pm/ex_doc/) and
|
||||
`IEx` may use this information to display annotations. A common use
|
||||
case is `since` that may be used to annotate in which version the
|
||||
function was introduced.
|
||||
|
||||
As illustrated in the example, it is possible to use these attributes
|
||||
more than once before an entity. However, the compiler will warn if
|
||||
@@ -211,6 +213,9 @@ defmodule Module do
|
||||
there are a few reserved keys that will be ignored and warned if used.
|
||||
Currently these are: `:opaque` and `:defaults`.
|
||||
|
||||
Once this module is compiled, this information becomes available via
|
||||
the `Code.fetch_docs/1` function.
|
||||
|
||||
### `@dialyzer`
|
||||
|
||||
Defines warnings to request or suppress when using a version of
|
||||
@@ -269,12 +274,15 @@ defmodule Module do
|
||||
|
||||
Accepts a string (often a heredoc) or `false` where `@moduledoc false`
|
||||
will make the module invisible to documentation extraction tools like
|
||||
ExDoc.
|
||||
[`ExDoc`](https://hexdocs.pm/ex_doc/).
|
||||
|
||||
Similarly to `@doc` also accepts a keyword list to provide metadata
|
||||
about the module. For more details, see the documentation of `@doc`
|
||||
above.
|
||||
|
||||
Once this module is compiled, this information becomes available via
|
||||
the `Code.fetch_docs/1` function.
|
||||
|
||||
### `@on_definition`
|
||||
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
@@ -337,8 +345,9 @@ defmodule Module do
|
||||
### Custom attributes
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
`@` and followed by a valid Elixir value:
|
||||
also be added. Custom attributes are expressed using the `@/1` operator followed
|
||||
by a valid variable name. The value given to the custom attribute must be a valid
|
||||
Elixir value:
|
||||
|
||||
defmodule MyModule do
|
||||
@custom_attr [some: "stuff"]
|
||||
@@ -493,27 +502,37 @@ defmodule Module do
|
||||
@typep definition :: {atom, arity}
|
||||
@typep def_kind :: :def | :defp | :defmacro | :defmacrop
|
||||
|
||||
@extra_error_msg_defines? "Use Kernel.function_exported?/3 and Kernel.macro_exported?/3 " <>
|
||||
"to check for public functions and macros instead"
|
||||
|
||||
@extra_error_msg_definitions_in "Use the Module.__info__/1 callback to get public functions and macros instead"
|
||||
|
||||
@doc """
|
||||
Provides runtime information about functions and macros defined by the
|
||||
module, etc.
|
||||
Provides runtime information about functions, macros, and other information
|
||||
defined by the module.
|
||||
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
takes one of the following items:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:module` - the module atom name
|
||||
|
||||
* `:md5` - the MD5 of the module
|
||||
* `:attributes` - a keyword list with all persisted attributes
|
||||
|
||||
* `:compile` - a list with compiler metadata
|
||||
|
||||
* `:attributes` - a list with all persisted attributes
|
||||
* `:functions` - a keyword list of public functions and their arities
|
||||
|
||||
* `:macros` - a keyword list of public macros and their arities
|
||||
|
||||
* `:md5` - the MD5 of the module
|
||||
|
||||
* `:module` - the module atom name
|
||||
|
||||
"""
|
||||
@callback __info__(:functions | :macros | :module | :md5 | :compile | :attributes) :: term()
|
||||
@callback __info__(:attributes) :: keyword()
|
||||
@callback __info__(:compile) :: [term()]
|
||||
@callback __info__(:functions) :: keyword()
|
||||
@callback __info__(:macros) :: keyword()
|
||||
@callback __info__(:md5) :: binary()
|
||||
@callback __info__(:module) :: module()
|
||||
|
||||
@doc """
|
||||
Checks if a module is open.
|
||||
@@ -584,7 +603,7 @@ defmodule Module do
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts)
|
||||
when is_atom(module) and is_list(binding) and is_list(opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
:elixir_def.reset_last(module)
|
||||
|
||||
{value, binding, _env, _scope} =
|
||||
@@ -900,7 +919,13 @@ defmodule Module do
|
||||
Use `defines?/3` to assert for a specific type.
|
||||
|
||||
This function can only be used on modules that have not yet been compiled.
|
||||
Use `Kernel.function_exported?/3` to check compiled modules.
|
||||
Use `Kernel.function_exported?/3` and `Kernel.macro_exported?/3` to check for
|
||||
public functions and macros respectively in compiled modules.
|
||||
|
||||
Note that `defines?` returns false for functions and macros that have
|
||||
been defined but then marked as overridable and no other implementation
|
||||
has been provided. You can check the overridable status by calling
|
||||
`overridable?/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -914,7 +939,7 @@ defmodule Module do
|
||||
@spec defines?(module, definition) :: boolean
|
||||
def defines?(module, {name, arity} = tuple)
|
||||
when is_atom(module) and is_atom(name) and is_integer(arity) and arity >= 0 and arity <= 255 do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_defines?)
|
||||
{set, _bag} = data_tables_for(module)
|
||||
:ets.member(set, {:def, tuple})
|
||||
end
|
||||
@@ -926,7 +951,8 @@ defmodule Module do
|
||||
`kind` can be any of `:def`, `:defp`, `:defmacro`, or `:defmacrop`.
|
||||
|
||||
This function can only be used on modules that have not yet been compiled.
|
||||
Use `Kernel.function_exported?/3` to check compiled modules.
|
||||
Use `Kernel.function_exported?/3` and `Kernel.macro_exported?/3` to check for
|
||||
public functions and macros respectively in compiled modules.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -941,7 +967,8 @@ defmodule Module do
|
||||
def defines?(module, {name, arity} = tuple, def_kind)
|
||||
when is_atom(module) and is_atom(name) and is_integer(arity) and arity >= 0 and arity <= 255 and
|
||||
def_kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_defines?)
|
||||
|
||||
{set, _bag} = data_tables_for(module)
|
||||
|
||||
case :ets.lookup(set, {:def, tuple}) do
|
||||
@@ -962,9 +989,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given spec to a callback.
|
||||
Copies the given spec as a callback.
|
||||
|
||||
Returns `true` if there is such a spec and it was converted to a callback.
|
||||
Returns `true` if there is such a spec and it was copied as a callback.
|
||||
If the function associated to the spec has documentation defined prior to
|
||||
invoking this function, the docs are copied too.
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec spec_to_callback(module, definition) :: boolean
|
||||
@@ -973,19 +1002,27 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all functions defined in `module`.
|
||||
Returns all functions and macros defined in `module`.
|
||||
|
||||
It returns a list with all defined functions and macros, public and private,
|
||||
in the shape of `[{name, arity}, ...]`.
|
||||
|
||||
This function can only be used on modules that have not yet been compiled.
|
||||
Use the `c:Module.__info__/1` callback to get the public functions and macros in
|
||||
compiled modules.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in(__MODULE__) #=> [{:version, 0}]
|
||||
defmacrop test(arg), do: arg
|
||||
Module.definitions_in(__MODULE__) #=> [{:version, 0}, {:test, 1}]
|
||||
end
|
||||
|
||||
"""
|
||||
@spec definitions_in(module) :: [definition]
|
||||
def definitions_in(module) when is_atom(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_definitions_in)
|
||||
{_, bag} = data_tables_for(module)
|
||||
bag_lookup_element(bag, :defs, 2)
|
||||
end
|
||||
@@ -994,6 +1031,10 @@ defmodule Module do
|
||||
Returns all functions defined in `module`, according
|
||||
to its kind.
|
||||
|
||||
This function can only be used on modules that have not yet been compiled.
|
||||
Use the `c:Module.__info__/1` callback to get the public functions and macros in
|
||||
compiled modules.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
@@ -1006,7 +1047,7 @@ defmodule Module do
|
||||
@spec definitions_in(module, def_kind) :: [definition]
|
||||
def definitions_in(module, def_kind)
|
||||
when is_atom(module) and def_kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_definitions_in)
|
||||
{set, _} = data_tables_for(module)
|
||||
:lists.concat(:ets.match(set, {{:def, :"$1"}, def_kind, :_, :_, :_, :_}))
|
||||
end
|
||||
@@ -1017,10 +1058,15 @@ defmodule Module do
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable/1` for
|
||||
more information and documentation.
|
||||
|
||||
Once a function or a macro is marked as overridable, it will
|
||||
no longer be listed under `definitions_in/1` or return true
|
||||
when given to `defines?/2` until another implementation is
|
||||
given.
|
||||
"""
|
||||
@spec make_overridable(module, [definition]) :: :ok
|
||||
def make_overridable(module, tuples) when is_atom(module) and is_list(tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
|
||||
func = fn
|
||||
{function_name, arity} = tuple
|
||||
@@ -1033,18 +1079,7 @@ defmodule Module do
|
||||
|
||||
clause ->
|
||||
neighbours = :elixir_locals.yank(tuple, module)
|
||||
overridable_definitions = :elixir_overridable.overridable(module)
|
||||
|
||||
count =
|
||||
case :maps.find(tuple, overridable_definitions) do
|
||||
{:ok, {count, _, _, _}} -> count + 1
|
||||
:error -> 1
|
||||
end
|
||||
|
||||
overridable_definitions =
|
||||
:maps.put(tuple, {count, clause, neighbours, false}, overridable_definitions)
|
||||
|
||||
:elixir_overridable.overridable(module, overridable_definitions)
|
||||
:elixir_overridable.record_overridable(module, tuple, clause, neighbours)
|
||||
end
|
||||
|
||||
other ->
|
||||
@@ -1129,7 +1164,7 @@ defmodule Module do
|
||||
@spec overridable?(module, definition) :: boolean
|
||||
def overridable?(module, {function_name, arity} = tuple)
|
||||
when is_atom(function_name) and is_integer(arity) and arity >= 0 and arity <= 255 do
|
||||
:maps.is_key(tuple, :elixir_overridable.overridable(module))
|
||||
:elixir_overridable.overridable_for(module, tuple) != :not_overridable
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1144,7 +1179,7 @@ defmodule Module do
|
||||
"""
|
||||
@spec put_attribute(module, atom, term) :: :ok
|
||||
def put_attribute(module, key, value) when is_atom(module) and is_atom(key) do
|
||||
put_attribute(module, key, value, nil)
|
||||
__put_attribute__(module, key, value, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1164,21 +1199,32 @@ defmodule Module do
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo)
|
||||
|
||||
This function can only be used on modules that have not yet been compiled.
|
||||
Use the `c:Module.__info__/1` callback to get all persisted attributes, or
|
||||
`Code.fetch_docs/1` to retrieve all documentation related attributes in
|
||||
compiled modules.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
Module.put_attribute(__MODULE__, :value, 1)
|
||||
Module.get_attribute(__MODULE__, :value) #=> 1
|
||||
|
||||
Module.get_attribute(__MODULE__, :value, :default) #=> 1
|
||||
Module.get_attribute(__MODULE__, :not_found, :default) #=> :default
|
||||
|
||||
Module.register_attribute(__MODULE__, :value, accumulate: true)
|
||||
Module.put_attribute(__MODULE__, :value, 1)
|
||||
Module.get_attribute(__MODULE__, :value) #=> [1]
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom) :: term
|
||||
def get_attribute(module, key) when is_atom(module) and is_atom(key) do
|
||||
get_attribute(module, key, nil)
|
||||
@spec get_attribute(module, atom, term) :: term
|
||||
def get_attribute(module, key, default \\ nil) when is_atom(module) and is_atom(key) do
|
||||
case __get_attribute__(module, key, nil) do
|
||||
nil -> default
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1196,7 +1242,7 @@ defmodule Module do
|
||||
"""
|
||||
@spec delete_attribute(module, atom) :: term
|
||||
def delete_attribute(module, key) when is_atom(module) and is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
{set, bag} = data_tables_for(module)
|
||||
|
||||
case :ets.lookup(set, key) do
|
||||
@@ -1248,7 +1294,7 @@ defmodule Module do
|
||||
@spec register_attribute(module, atom, [{:accumulate, boolean}, {:persist, boolean}]) :: :ok
|
||||
def register_attribute(module, attribute, options)
|
||||
when is_atom(module) and is_atom(attribute) and is_list(options) do
|
||||
assert_not_compiled!(:register_attribute, module)
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
{set, bag} = data_tables_for(module)
|
||||
|
||||
if Keyword.get(options, :persist) do
|
||||
@@ -1300,10 +1346,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use @doc instead"
|
||||
def add_doc(module, line, kind, {name, arity}, signature \\ [], doc) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
|
||||
if kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: {:error, :private_doc}, else: :ok
|
||||
@@ -1338,7 +1383,7 @@ defmodule Module do
|
||||
"#{kind} #{name}/#{arity} is private, " <>
|
||||
"@doc attribute is always discarded for private functions/macros/types"
|
||||
|
||||
:elixir_errors.warn(line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(%{env | line: line}))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1438,7 +1483,7 @@ defmodule Module do
|
||||
|
||||
pending_callbacks =
|
||||
if impls != [] do
|
||||
{non_implemented_callbacks, contexts} = check_impls(behaviours, callbacks, impls)
|
||||
{non_implemented_callbacks, contexts} = check_impls(env, behaviours, callbacks, impls)
|
||||
warn_missing_impls(env, non_implemented_callbacks, contexts, all_definitions)
|
||||
non_implemented_callbacks
|
||||
else
|
||||
@@ -1456,23 +1501,21 @@ defmodule Module do
|
||||
message =
|
||||
"@behaviour #{inspect(behaviour)} must be an atom (in module #{inspect(env.module)})"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
acc
|
||||
|
||||
not Code.ensure_compiled?(behaviour) ->
|
||||
message =
|
||||
"@behaviour #{inspect(behaviour)} does not exist (in module #{inspect(env.module)})"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
acc
|
||||
|
||||
not function_exported?(behaviour, :behaviour_info, 1) ->
|
||||
message =
|
||||
"module #{inspect(behaviour)} is not a behaviour (in module #{inspect(env.module)})"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
acc
|
||||
|
||||
true ->
|
||||
@@ -1490,10 +1533,15 @@ defmodule Module do
|
||||
case acc do
|
||||
%{^callback => {_kind, conflict, _optional?}} ->
|
||||
message =
|
||||
"conflicting behaviours found. #{format_definition(kind, callback)} is required by " <>
|
||||
"#{inspect(conflict)} and #{inspect(behaviour)} (in module #{inspect(env.module)})"
|
||||
if conflict == behaviour do
|
||||
"the behavior #{inspect(conflict)} has been declared twice " <>
|
||||
"(conflict in #{format_definition(kind, callback)} in module #{inspect(env.module)})"
|
||||
else
|
||||
"conflicting behaviours found. #{format_definition(kind, callback)} is required by " <>
|
||||
"#{inspect(conflict)} and #{inspect(behaviour)} (in module #{inspect(env.module)})"
|
||||
end
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
|
||||
%{} ->
|
||||
:ok
|
||||
@@ -1510,7 +1558,7 @@ defmodule Module do
|
||||
format_callback(callback, kind, behaviour) <>
|
||||
" is not implemented (in module #{inspect(env.module)})"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
|
||||
{_, wrong_kind, _, _} when kind != wrong_kind ->
|
||||
message =
|
||||
@@ -1518,7 +1566,7 @@ defmodule Module do
|
||||
" was implemented as \"#{wrong_kind}\" but should have been \"#{kind}\" " <>
|
||||
"(in module #{inspect(env.module)})"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
@@ -1540,7 +1588,7 @@ defmodule Module do
|
||||
module.__protocol__(:module) == module
|
||||
end
|
||||
|
||||
defp check_impls(behaviours, callbacks, impls) do
|
||||
defp check_impls(env, behaviours, callbacks, impls) do
|
||||
acc = {callbacks, %{}}
|
||||
|
||||
Enum.reduce(impls, acc, fn {fa, context, defaults, kind, line, file, value}, acc ->
|
||||
@@ -1553,7 +1601,8 @@ defmodule Module do
|
||||
end)
|
||||
|
||||
{:error, message} ->
|
||||
:elixir_errors.warn(line, file, format_impl_warning(fa, kind, message))
|
||||
formatted = format_impl_warning(fa, kind, message)
|
||||
IO.warn(formatted, Macro.Env.stacktrace(%{env | line: line, file: file}))
|
||||
acc
|
||||
end
|
||||
end)
|
||||
@@ -1669,7 +1718,7 @@ defmodule Module do
|
||||
"This either means you forgot to add the \"@impl true\" annotation before the " <>
|
||||
"definition or that you are accidentally overriding this callback"
|
||||
|
||||
:elixir_errors.warn(:elixir_utils.get_line(meta), env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(%{env | line: :elixir_utils.get_line(meta)}))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1709,8 +1758,13 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally by Kernel's @.
|
||||
# This function is private and must be used only internally.
|
||||
def get_attribute(module, key, line) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
def __get_attribute__(module, key, line) when is_atom(key) do
|
||||
assert_not_compiled!(
|
||||
{:get_attribute, 2},
|
||||
module,
|
||||
"Use the Module.__info__/1 callback or Code.fetch_docs/1 instead"
|
||||
)
|
||||
|
||||
{set, bag} = data_tables_for(module)
|
||||
|
||||
case :ets.lookup(set, key) do
|
||||
@@ -1741,8 +1795,8 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally by Kernel's @.
|
||||
# This function is private and must be used only internally.
|
||||
def put_attribute(module, key, value, line) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
def __put_attribute__(module, key, value, line) when is_atom(key) do
|
||||
assert_not_compiled!(__ENV__.function, module)
|
||||
{set, bag} = data_tables_for(module)
|
||||
value = preprocess_attribute(key, value)
|
||||
put_attribute(module, key, value, line, set, bag)
|
||||
@@ -1820,7 +1874,7 @@ defmodule Module do
|
||||
|
||||
defp preprocess_attribute(key, value) when key in [:moduledoc, :typedoc, :doc] do
|
||||
case value do
|
||||
{line, doc} when is_integer(line) and (is_binary(doc) or is_boolean(doc) or is_nil(doc)) ->
|
||||
{line, doc} when is_integer(line) and (is_binary(doc) or doc == false or is_nil(doc)) ->
|
||||
value
|
||||
|
||||
{line, [{key, _} | _]} when is_integer(line) and is_atom(key) ->
|
||||
@@ -1828,13 +1882,13 @@ defmodule Module do
|
||||
|
||||
{line, doc} when is_integer(line) ->
|
||||
raise ArgumentError,
|
||||
"@#{key} is a built-in module attribute for documentation. It should be " <>
|
||||
"a string, boolean, keyword list, or nil, got: #{inspect(doc)}"
|
||||
"@#{key} is a built-in module attribute for documentation. It should be either " <>
|
||||
"false, nil, a string, or a keyword list, got: #{inspect(doc)}"
|
||||
|
||||
_other ->
|
||||
raise ArgumentError,
|
||||
"@#{key} is a built-in module attribute for documentation. When set dynamically, " <>
|
||||
"it should be {line, doc} (where \"doc\" is a string, boolean, keyword list, or nil), " <>
|
||||
"it should be {line, doc} (where \"doc\" is either false, nil, a string, or a keyword list), " <>
|
||||
"got: #{inspect(value)}"
|
||||
end
|
||||
end
|
||||
@@ -1981,9 +2035,19 @@ defmodule Module do
|
||||
:error, :badarg -> []
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
defp assert_not_compiled!(function_name_arity, module, extra_msg \\ "") do
|
||||
open?(module) ||
|
||||
raise ArgumentError,
|
||||
"could not call #{fun} with argument #{inspect(module)} because the module is already compiled"
|
||||
assert_not_compiled_message(function_name_arity, module, extra_msg)
|
||||
end
|
||||
|
||||
defp assert_not_compiled_message({function_name, arity}, module, extra_msg) do
|
||||
mfa = "Module.#{function_name}/#{arity}"
|
||||
|
||||
"could not call #{mfa} because the module #{inspect(module)} is already compiled" <>
|
||||
case extra_msg do
|
||||
"" -> ""
|
||||
_ -> ". " <> extra_msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,19 +8,21 @@
|
||||
# resembling a graph. The keys and what they point to are:
|
||||
#
|
||||
# * `:reattach` points to `{name, arity}`
|
||||
# * `{:local, {name, arity}}` points to `{name, arity}`
|
||||
# * `{:local, {name, arity}}` points to `{{name, arity}, line, macro_dispatch?}`
|
||||
# * `{:import, {name, arity}}` points to `Module`
|
||||
#
|
||||
# This is built on top of the internal module tables.
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@defmacros [:defmacro, :defmacrop]
|
||||
|
||||
@doc """
|
||||
Adds and tracks defaults for a definition into the tracker.
|
||||
"""
|
||||
def add_defaults({_set, bag}, _kind, {name, arity} = pair, defaults, meta) do
|
||||
def add_defaults({_set, bag}, kind, {name, arity} = pair, defaults, meta) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
put_edge(bag, {:local, {name, i}}, {pair, get_line(meta)})
|
||||
put_edge(bag, {:local, {name, i}}, {pair, get_line(meta), kind in @defmacros})
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -29,11 +31,9 @@ defmodule Module.LocalsTracker do
|
||||
@doc """
|
||||
Adds a local dispatch from-to the given target.
|
||||
"""
|
||||
def add_local({_set, bag}, from, to, meta) when is_tuple(from) and is_tuple(to) do
|
||||
if from != to do
|
||||
put_edge(bag, {:local, from}, {to, get_line(meta)})
|
||||
end
|
||||
|
||||
def add_local({_set, bag}, from, to, meta, macro_dispatch?)
|
||||
when is_tuple(from) and is_tuple(to) and is_boolean(macro_dispatch?) do
|
||||
put_edge(bag, {:local, from}, {to, get_line(meta), macro_dispatch?})
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -56,14 +56,14 @@ defmodule Module.LocalsTracker do
|
||||
@doc """
|
||||
Reattach a previously yanked node.
|
||||
"""
|
||||
def reattach({_set, bag}, tuple, _kind, function, out_neighbours, meta) do
|
||||
def reattach({_set, bag}, tuple, kind, function, out_neighbours, meta) do
|
||||
for out_neighbour <- out_neighbours do
|
||||
put_edge(bag, {:local, function}, out_neighbour)
|
||||
end
|
||||
|
||||
# Make a call from the old function to the new one
|
||||
if function != tuple do
|
||||
put_edge(bag, {:local, function}, {tuple, get_line(meta)})
|
||||
put_edge(bag, {:local, function}, {tuple, get_line(meta), kind in @defmacros})
|
||||
end
|
||||
|
||||
# Finally marked the new one as reattached
|
||||
@@ -99,18 +99,37 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Collect undefined functions based on local calls and existing definitions
|
||||
Collect undefined functions based on local calls and existing definitions.
|
||||
"""
|
||||
def collect_undefined_locals({set, bag}, all_defined) do
|
||||
undefined =
|
||||
for {pair, _, _, _} <- all_defined,
|
||||
{local, line} <- out_neighbours(bag, {:local, pair}),
|
||||
not :ets.member(set, {:def, local}),
|
||||
do: {build_meta(line), local}
|
||||
for {pair, _, meta, _} <- all_defined,
|
||||
{local, line, macro_dispatch?} <- out_neighbours(bag, {:local, pair}),
|
||||
error = undefined_local_error(set, local, macro_dispatch?),
|
||||
do: {build_meta(line, meta), local, error}
|
||||
|
||||
:lists.usort(undefined)
|
||||
end
|
||||
|
||||
defp undefined_local_error(set, local, true) do
|
||||
case :ets.member(set, {:def, local}) do
|
||||
true -> false
|
||||
false -> :undefined_function
|
||||
end
|
||||
end
|
||||
|
||||
defp undefined_local_error(set, local, false) do
|
||||
try do
|
||||
if :ets.lookup_element(set, {:def, local}, 2) in @defmacros do
|
||||
:incorrect_dispatch
|
||||
else
|
||||
false
|
||||
end
|
||||
catch
|
||||
_, _ -> :undefined_function
|
||||
end
|
||||
end
|
||||
|
||||
defp unreachable(reachable, reattached, private) do
|
||||
for {tuple, kind, _, _} <- private,
|
||||
not reachable?(tuple, kind, reachable, reattached),
|
||||
@@ -183,7 +202,7 @@ defmodule Module.LocalsTracker do
|
||||
defp reachable_from(bag, local, vertices) do
|
||||
vertices = Map.put(vertices, local, true)
|
||||
|
||||
Enum.reduce(out_neighbours(bag, {:local, local}), vertices, fn {local, _line}, acc ->
|
||||
Enum.reduce(out_neighbours(bag, {:local, local}), vertices, fn {local, _line, _}, acc ->
|
||||
case acc do
|
||||
%{^local => true} -> acc
|
||||
_ -> reachable_from(bag, local, acc)
|
||||
@@ -193,8 +212,17 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
defp get_line(meta), do: Keyword.get(meta, :line)
|
||||
|
||||
defp build_meta(nil), do: []
|
||||
defp build_meta(line), do: [line: line]
|
||||
defp build_meta(nil, _meta), do: []
|
||||
|
||||
# We need to transform any file annotation in the function
|
||||
# definition into a keep annotation that is used by the
|
||||
# error handling system in order to respect line/file.
|
||||
defp build_meta(line, meta) do
|
||||
case Keyword.get(meta, :file) do
|
||||
{file, _} -> [keep: {file, line}]
|
||||
_ -> [line: line]
|
||||
end
|
||||
end
|
||||
|
||||
## Lightweight digraph implementation
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ defmodule Node do
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
@spec set_cookie(t, atom) :: true
|
||||
def set_cookie(node \\ Node.self(), cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
@@ -259,6 +260,7 @@ defmodule Node do
|
||||
|
||||
Returns the cookie if the node is alive, otherwise `:nocookie`.
|
||||
"""
|
||||
@spec get_cookie() :: atom
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
end
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
defmodule OptionParser do
|
||||
@moduledoc """
|
||||
Functions for parsing command line options.
|
||||
Functions for parsing command line arguments.
|
||||
|
||||
The main function in this module is `parse/2`, which allows
|
||||
developers to parse a list of arguments into options:
|
||||
When calling a command, it's possible to pass command line options
|
||||
to modify what the command does. In this documentation, those are
|
||||
called "switches", in other situations they may be called "flags"
|
||||
or simply "options". A switch can be given a value, also called an
|
||||
"argument".
|
||||
|
||||
The main function in this module is `parse/2`, which parses a list
|
||||
of command line options and arguments into a keyword list:
|
||||
|
||||
iex> OptionParser.parse(["--debug"], strict: [debug: :boolean])
|
||||
{[debug: true], [], []}
|
||||
@@ -77,9 +83,11 @@ defmodule OptionParser do
|
||||
|
||||
Switches can be specified via one of two options:
|
||||
|
||||
* `: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
|
||||
* `:strict` - defines strict switches and their types. Any switch
|
||||
in `argv` that is not specified in the list is returned in the
|
||||
invalid options list. This is the preferred way to parse options.
|
||||
|
||||
* `:switches` - defines switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
|
||||
Both these options accept a keyword list where the key is an atom
|
||||
@@ -113,7 +121,7 @@ defmodule OptionParser do
|
||||
Switches can be specified with modifiers, which change how
|
||||
they behave. The following modifiers are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items instead of overriding them;
|
||||
* `:keep` - keeps duplicated elements instead of overriding them;
|
||||
works with all types except `:count`. Specifying `switch_name: :keep`
|
||||
assumes the type of `:switch_name` will be `:string`.
|
||||
|
||||
@@ -157,16 +165,17 @@ defmodule OptionParser do
|
||||
# The :option_parser_example atom is not used anywhere below
|
||||
|
||||
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**:
|
||||
used at some point later (or earlier) **in the same module**. For example:
|
||||
|
||||
{opts, _, _} = OptionParser.parse(["--option-parser-example"], switches: [debug: :boolean])
|
||||
# ... then somewhere in the same module you access it ...
|
||||
opts[:option_parser_example]
|
||||
|
||||
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
|
||||
In other words, Elixir will 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
|
||||
@@ -443,7 +452,6 @@ defmodule OptionParser do
|
||||
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
|
||||
@@ -596,7 +604,6 @@ 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
|
||||
|
||||
@@ -401,6 +401,9 @@ defmodule Path do
|
||||
iex> Path.dirname("/foo/bar/")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.dirname("bar.ex")
|
||||
"."
|
||||
|
||||
"""
|
||||
@spec dirname(t) :: binary
|
||||
def dirname(path) do
|
||||
@@ -684,14 +687,9 @@ defmodule Path do
|
||||
|
||||
defp resolve_home(rest) do
|
||||
case {rest, major_os_type()} do
|
||||
{"\\" <> _, :win32} ->
|
||||
System.user_home!() <> rest
|
||||
|
||||
{"/" <> _, _} ->
|
||||
System.user_home!() <> rest
|
||||
|
||||
_ ->
|
||||
rest
|
||||
{"\\" <> _, :win32} -> System.user_home!() <> rest
|
||||
{"/" <> _, _} -> System.user_home!() <> rest
|
||||
_ -> "~" <> rest
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ defmodule Port do
|
||||
{#Port<0.1444>, {:data, "hello\n"}}
|
||||
|
||||
`:spawn` will retrieve the program name from the argument and traverse your
|
||||
OS `$PATH` environment variable looking for a matching program.
|
||||
operating system `$PATH` environment variable looking for a matching program.
|
||||
|
||||
Although the above is handy, it means it is impossible to invoke an executable
|
||||
that has whitespaces on its name or in any of its arguments. For those reasons,
|
||||
@@ -117,7 +117,7 @@ defmodule Port do
|
||||
reimplementing core part of the Runtime System, such as the `:user` and
|
||||
`:shell` processes.
|
||||
|
||||
## Zombie OS processes
|
||||
## Zombie operating system 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
|
||||
@@ -226,6 +226,7 @@ defmodule Port do
|
||||
|
||||
For more information, see `:erlang.port_info/1`.
|
||||
"""
|
||||
@spec info(port) :: keyword | nil
|
||||
def info(port) do
|
||||
nillify(:erlang.port_info(port))
|
||||
end
|
||||
@@ -261,7 +262,7 @@ defmodule Port do
|
||||
where:
|
||||
|
||||
* `ref` is a monitor reference returned by this function;
|
||||
* `object` is either the `port` being monitored (when monitoring by port id)
|
||||
* `object` is either the `port` being monitored (when monitoring by port ID)
|
||||
or `{name, node}` (when monitoring by a port name);
|
||||
* `reason` is the exit reason.
|
||||
|
||||
|
||||
+251
-25
@@ -1,15 +1,241 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc """
|
||||
Functions for working with protocols.
|
||||
@moduledoc ~S"""
|
||||
Reference and functions for working with protocols.
|
||||
|
||||
A protocol specifies an API that should be defined by its
|
||||
implementations. A protocol is defined with `Kernel.defprotocol/2`
|
||||
and its implementations with `Kernel.defimpl/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
In Elixir, we have two verbs for checking how many items there
|
||||
are in a data structure: `length` and `size`. `length` means the
|
||||
information must be computed. For example, `length(list)` needs to
|
||||
traverse the whole list to calculate its length. On the other hand,
|
||||
`tuple_size(tuple)` and `byte_size(binary)` do not depend on the
|
||||
tuple and binary size as the size information is precomputed in
|
||||
the data structure.
|
||||
|
||||
Although Elixir includes specific functions such as `tuple_size`,
|
||||
`binary_size` and `map_size`, sometimes we want to be able to
|
||||
retrieve the size of a data structure regardless of its type.
|
||||
In Elixir we can write polymorphic code, i.e. code that works
|
||||
with different shapes/types, by using protocols. A size protocol
|
||||
could be implemented as follows:
|
||||
|
||||
defprotocol Size do
|
||||
@doc "Calculates the size (and not the length!) of a data structure"
|
||||
def size(data)
|
||||
end
|
||||
|
||||
Now that the protocol can be implemented for every data structure
|
||||
the protocol may have a compliant implementation for:
|
||||
|
||||
defimpl Size, for: BitString do
|
||||
def size(binary), do: byte_size(binary)
|
||||
end
|
||||
|
||||
defimpl Size, for: Map do
|
||||
def size(map), do: map_size(map)
|
||||
end
|
||||
|
||||
defimpl Size, for: Tuple do
|
||||
def size(tuple), do: tuple_size(tuple)
|
||||
end
|
||||
|
||||
Notice we didn't implement it for lists as we don't have the
|
||||
`size` information on lists, rather its value needs to be
|
||||
computed with `length`.
|
||||
|
||||
It is possible to implement protocols for all Elixir types:
|
||||
|
||||
* Structs (see below)
|
||||
* `Tuple`
|
||||
* `Atom`
|
||||
* `List`
|
||||
* `BitString`
|
||||
* `Integer`
|
||||
* `Float`
|
||||
* `Function`
|
||||
* `PID`
|
||||
* `Map`
|
||||
* `Port`
|
||||
* `Reference`
|
||||
* `Any` (see below)
|
||||
|
||||
## Protocols and Structs
|
||||
|
||||
The real benefit of protocols comes when mixed with structs.
|
||||
For instance, Elixir ships with many data types implemented as
|
||||
structs, like `MapSet`. We can implement the `Size` protocol
|
||||
for those types as well:
|
||||
|
||||
defimpl Size, for: MapSet do
|
||||
def size(map_set), do: MapSet.size(map_set)
|
||||
end
|
||||
|
||||
When implementing a protocol for a struct, the `:for` option can
|
||||
be omitted if the `defimpl` call is inside the module that defines
|
||||
the struct:
|
||||
|
||||
defmodule User do
|
||||
defstruct [:email, :name]
|
||||
|
||||
defimpl Size do
|
||||
# two fields
|
||||
def size(%User{}), do: 2
|
||||
end
|
||||
end
|
||||
|
||||
If a protocol implementation is not found for a given type,
|
||||
invoking the protocol will raise unless it is configured to
|
||||
fall back to `Any`. Conveniences for building implementations
|
||||
on top of existing ones are also available, look at `defstruct/1`
|
||||
for more information about deriving
|
||||
protocols.
|
||||
|
||||
## Fallback to `Any`
|
||||
|
||||
In some cases, it may be convenient to provide a default
|
||||
implementation for all types. This can be achieved by setting
|
||||
the `@fallback_to_any` attribute to `true` in the protocol
|
||||
definition:
|
||||
|
||||
defprotocol Size do
|
||||
@fallback_to_any true
|
||||
def size(data)
|
||||
end
|
||||
|
||||
The `Size` protocol can now be implemented for `Any`:
|
||||
|
||||
defimpl Size, for: Any do
|
||||
def size(_), do: 0
|
||||
end
|
||||
|
||||
Although the implementation above is arguably not a reasonable
|
||||
one. For example, it makes no sense to say a PID or an integer
|
||||
have a size of `0`. That's one of the reasons why `@fallback_to_any`
|
||||
is an opt-in behaviour. For the majority of protocols, raising
|
||||
an error when a protocol is not implemented is the proper behaviour.
|
||||
|
||||
## Multiple implementations
|
||||
|
||||
Protocols can also be implemented for multiple types at once:
|
||||
|
||||
defprotocol Reversible do
|
||||
def reverse(term)
|
||||
end
|
||||
|
||||
defimpl Reversible, for: [Map, List] do
|
||||
def reverse(term), do: Enum.reverse(term)
|
||||
end
|
||||
|
||||
Inside `defimpl/2`, you can use `@protocol` to access the protocol
|
||||
being implemented and `@for` to access the module it is being
|
||||
defined for.
|
||||
|
||||
## Types
|
||||
|
||||
Defining a protocol automatically defines a type named `t`, which
|
||||
can be used as follows:
|
||||
|
||||
@spec print_size(Size.t()) :: :ok
|
||||
def print_size(data) do
|
||||
result =
|
||||
case Size.size(data) do
|
||||
0 -> "data has no items"
|
||||
1 -> "data has one item"
|
||||
n -> "data has #{n} items"
|
||||
end
|
||||
|
||||
IO.puts(result)
|
||||
end
|
||||
|
||||
The `@spec` above expresses that all types allowed to implement the
|
||||
given protocol are valid argument types for the given function.
|
||||
|
||||
## Reflection
|
||||
|
||||
Any protocol module contains three extra functions:
|
||||
|
||||
* `__protocol__/1` - returns the protocol information. The function takes
|
||||
one of the following atoms:
|
||||
|
||||
* `:consolidated?` - returns whether the protocol is consolidated
|
||||
|
||||
* `:functions` - returns keyword list of protocol functions and their arities
|
||||
|
||||
* `:impls` - if consolidated, returns `{:consolidated, modules}` with the list of modules
|
||||
implementing the protocol, otherwise `:not_consolidated`
|
||||
|
||||
* `:module` - the protocol module atom name
|
||||
|
||||
* `impl_for/1` - receives a structure and returns the module that
|
||||
implements the protocol for the structure, `nil` otherwise
|
||||
|
||||
* `impl_for!/1` - same as above but raises an error if an implementation is
|
||||
not found
|
||||
|
||||
For example, for the `Enumerable` protocol we have:
|
||||
|
||||
iex> Enumerable.__protocol__(:functions)
|
||||
[count: 1, member?: 2, reduce: 3, slice: 1]
|
||||
|
||||
iex> Enumerable.impl_for([])
|
||||
Enumerable.List
|
||||
|
||||
iex> Enumerable.impl_for(42)
|
||||
nil
|
||||
|
||||
## Consolidation
|
||||
|
||||
In order to cope with code loading in development, protocols in
|
||||
Elixir provide a slow implementation of protocol dispatching specific
|
||||
to development.
|
||||
|
||||
In order to speed up dispatching in production environments, where
|
||||
all implementations are known up-front, Elixir provides a feature
|
||||
called protocol consolidation. Consolidation directly links protocols
|
||||
to their implementations in a way that invoking a function from a
|
||||
consolidated protocol is equivalent to invoking two remote functions.
|
||||
|
||||
Protocol consolidation is applied by default to all Mix projects during
|
||||
compilation. This may be an issue during test. For instance, if you want
|
||||
to implement a protocol during test, the implementation will have no
|
||||
effect, as the protocol has already been consolidated. One possible
|
||||
solution is to include compilation directories that are specific to your
|
||||
test environment in your mix.exs:
|
||||
|
||||
def project do
|
||||
...
|
||||
elixirc_paths: elixirc_paths(Mix.env())
|
||||
...
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
And then you can define the implementations specific to the test environment
|
||||
inside `test/support/some_file.ex`.
|
||||
|
||||
Another approach is to disable protocol consolidation during tests in your
|
||||
mix.exs:
|
||||
|
||||
def project do
|
||||
...
|
||||
consolidate_protocols: Mix.env() != :test
|
||||
...
|
||||
end
|
||||
|
||||
Although doing so is not recommended as it may affect your test suite
|
||||
performance.
|
||||
|
||||
Finally note all protocols are compiled with `debug_info` set to `true`,
|
||||
regardless of the option set by `elixirc` compiler. The debug info is
|
||||
used for consolidation and it may be removed after consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a new protocol function.
|
||||
|
||||
Protocols do not allow functions to be defined directly, instead, the
|
||||
regular `Kernel.def/*` macros are replaced by this macro which
|
||||
defines the protocol functions with the appropriate callbacks.
|
||||
"""
|
||||
@doc false
|
||||
defmacro def(signature)
|
||||
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
@@ -42,7 +268,7 @@ defmodule Protocol do
|
||||
impl_for!(term).unquote(name)(unquote_splicing(call_args))
|
||||
end
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# Copy spec as callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Module.spec_to_callback(__MODULE__, {name, arity}) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
@@ -125,7 +351,7 @@ defmodule Protocol do
|
||||
## Examples
|
||||
|
||||
defprotocol Derivable do
|
||||
def ok(a)
|
||||
def ok(arg)
|
||||
end
|
||||
|
||||
defimpl Derivable, for: Any do
|
||||
@@ -147,14 +373,11 @@ defmodule Protocol do
|
||||
defmodule ImplStruct do
|
||||
@derive [Derivable]
|
||||
defstruct a: 0, b: 0
|
||||
|
||||
defimpl Sample do
|
||||
def ok(struct) do
|
||||
Unknown.undefined(struct)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Derivable.ok(%ImplStruct{})
|
||||
{:ok, %ImplStruct{a: 0, b: 0}, %ImplStruct{a: 0, b: 0}, []}
|
||||
|
||||
Explicit derivations can now be called via `__deriving__`:
|
||||
|
||||
# Explicitly derived via `__deriving__`
|
||||
@@ -497,10 +720,11 @@ defmodule Protocol do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case Code.ensure_compiled?(unquote(target)) and
|
||||
function_exported?(unquote(target), :__impl__, 1) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> unquote(any_impl_for)
|
||||
try do
|
||||
unquote(target).__impl__(:target)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
unquote(any_impl_for)
|
||||
end
|
||||
end
|
||||
end,
|
||||
@@ -533,9 +757,11 @@ defmodule Protocol do
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
|
||||
case Code.ensure_compiled?(target) and function_exported?(target, :__impl__, 1) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> unquote(any_impl_for)
|
||||
try do
|
||||
target.__impl__(:target)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
unquote(any_impl_for)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -676,7 +902,7 @@ defmodule Protocol do
|
||||
"implement protocols after compilation or during tests, check the " <>
|
||||
"\"Consolidation\" section in the documentation for Kernel.defprotocol/2"
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
IO.warn(message, Macro.Env.stacktrace(env))
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
@@ -78,7 +78,7 @@ defmodule Range do
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(first1..last1, first2..last2) do
|
||||
def disjoint?(first1..last1 = _range1, first2..last2 = _range2) do
|
||||
{first1, last1} = normalize(first1, last1)
|
||||
{first2, last2} = normalize(first2, last2)
|
||||
last2 < first1 or last1 < first2
|
||||
@@ -88,7 +88,6 @@ defmodule Range do
|
||||
defp normalize(first, last) when first > last, do: {last, first}
|
||||
defp normalize(first, last), do: {first, last}
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Pattern match on first..last instead"
|
||||
def range?(term)
|
||||
|
||||
+82
-25
@@ -33,19 +33,6 @@ defmodule Regex do
|
||||
|
||||
~r/(?<foo>.)(?<bar>.)/.source == ~r/(?<foo>.)(?<bar>.)/.source
|
||||
|
||||
## Precompilation
|
||||
|
||||
Regular expressions built with sigil are precompiled and stored in `.beam`
|
||||
files. This may be a problem if you are precompiling Elixir to run in
|
||||
different OTP releases, as OTP releases may update the underlying regular
|
||||
expression engine at any time.
|
||||
|
||||
For such reasons, we always recommend precompiling Elixir projects using
|
||||
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
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
@@ -103,6 +90,56 @@ defmodule Regex do
|
||||
|
||||
* `list(binary)` - a list of named captures to capture
|
||||
|
||||
## Character classes
|
||||
|
||||
Regex supports several built in named character classes. These are used by
|
||||
enclosing the class name in `[: :]` inside a group. For example:
|
||||
|
||||
iex> String.match?("123", ~r/^[[:alnum:]]+$/)
|
||||
true
|
||||
iex> String.match?("123 456", ~r/^[[:alnum:][:blank:]]+$/)
|
||||
true
|
||||
|
||||
The supported class names are:
|
||||
|
||||
* alnum - Letters and digits
|
||||
* alpha - Letters
|
||||
* ascii - Character codes 0-127
|
||||
* blank - Space or tab only
|
||||
* cntrl - Control characters
|
||||
* digit - Decimal digits (same as \\d)
|
||||
* graph - Printing characters, excluding space
|
||||
* lower - Lowercase letters
|
||||
* print - Printing characters, including space
|
||||
* punct - Printing characters, excluding letters, digits, and space
|
||||
* space - Whitespace (the same as \s from PCRE 8.34)
|
||||
* upper - Uppercase letters
|
||||
* word - "Word" characters (same as \w)
|
||||
* xdigit - Hexadecimal digits
|
||||
|
||||
Note the behaviour of those classes may change according to the Unicode
|
||||
and other modifiers:
|
||||
|
||||
iex> String.match?("josé", ~r/^[[:lower:]]+$/)
|
||||
false
|
||||
iex> String.match?("josé", ~r/^[[:lower:]]+$/u)
|
||||
true
|
||||
|
||||
## Precompilation
|
||||
|
||||
Regular expressions built with sigil are precompiled and stored in `.beam`
|
||||
files. Precompiled regexes will be checked in runtime and may work slower
|
||||
between operating systems and OTP releases. This is rarely a problem, as most Elixir code
|
||||
shared during development is compiled on the target (such as dependencies,
|
||||
archives, and escripts) and, when running in production, the code must either
|
||||
be compiled on the target (via `mix compile` or similar) or released on the
|
||||
host (via `mix releases` or similar) with a matching OTP, OS and architecture
|
||||
as as the target.
|
||||
|
||||
If you know you are running on a different system that the current one and
|
||||
you are doing multiple matches with the regex, you can manually invoke
|
||||
`Regex.recompile/1` or `Regex.recompile!/1` to perform a runtime version
|
||||
check and recompile the regex if necessary.
|
||||
"""
|
||||
|
||||
defstruct re_pattern: nil, source: "", opts: "", re_version: ""
|
||||
@@ -228,8 +265,8 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t()) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
def match?(%Regex{} = regex, string) when is_binary(string) do
|
||||
safe_run(regex, string, [{:capture, :none}]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -276,11 +313,11 @@ defmodule Regex do
|
||||
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
def run(%Regex{} = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
|
||||
case :re.run(string, compiled, [{:capture, captures, return}]) do
|
||||
case safe_run(regex, string, [{:capture, captures, return}]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{:match, results} -> results
|
||||
@@ -361,7 +398,17 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t()]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
def names(%Regex{re_pattern: compiled, re_version: version, source: source}) do
|
||||
re_pattern =
|
||||
case version() do
|
||||
^version ->
|
||||
compiled
|
||||
|
||||
_ ->
|
||||
{:ok, recompiled} = :re.compile(source)
|
||||
recompiled
|
||||
end
|
||||
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
end
|
||||
@@ -401,18 +448,29 @@ defmodule Regex do
|
||||
@spec scan(t, String.t(), [term]) :: [[String.t()]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
def scan(%Regex{} = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
options = [{:capture, captures, return}, :global]
|
||||
|
||||
case :re.run(string, compiled, options) do
|
||||
case safe_run(regex, string, options) do
|
||||
:match -> []
|
||||
:nomatch -> []
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
|
||||
defp safe_run(
|
||||
%Regex{re_pattern: compiled, source: source, re_version: version, opts: compile_opts},
|
||||
string,
|
||||
options
|
||||
) do
|
||||
case version() do
|
||||
^version -> :re.run(string, compiled, options)
|
||||
_ -> :re.run(string, source, translate_options(compile_opts, options))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given target based on the given pattern and in the given number of
|
||||
parts.
|
||||
@@ -472,11 +530,11 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, opts)
|
||||
def split(%Regex{} = regex, 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
|
||||
case safe_run(regex, string, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
index = parts_to_index(Keyword.get(opts, :parts, :infinity))
|
||||
trim = Keyword.get(opts, :trim, false)
|
||||
@@ -598,11 +656,11 @@ defmodule Regex do
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
|
||||
defp do_replace(%Regex{re_pattern: compiled}, string, replacement, options) do
|
||||
defp do_replace(%Regex{} = regex, string, replacement, options) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{:capture, :all, :index} | opts]
|
||||
|
||||
case :re.run(string, compiled, opts) do
|
||||
case safe_run(regex, string, opts) do
|
||||
:nomatch ->
|
||||
string
|
||||
|
||||
@@ -786,7 +844,6 @@ defmodule Regex do
|
||||
|
||||
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline | acc])
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
defp translate_options(<<?r, t::binary>>, acc) do
|
||||
IO.warn("the /r modifier in regular expressions is deprecated, please use /U instead")
|
||||
translate_options(t, [:ungreedy | acc])
|
||||
|
||||
+136
-29
@@ -78,14 +78,14 @@ defmodule Registry do
|
||||
Now, an entity interested in dispatching events for a given key may call
|
||||
`dispatch/3` passing in the key and a callback. This callback will be invoked
|
||||
with a list of all the values registered under the requested key, alongside
|
||||
the pid of the process that registered each value, in the form of `{pid,
|
||||
the PID of the process that registered each value, in the form of `{pid,
|
||||
value}` tuples. In our example, `value` will be the `{module, function}` tuple
|
||||
in the code above:
|
||||
|
||||
Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
|
||||
for {pid, {module, function}} <- entries, do: apply(module, function, [pid])
|
||||
end)
|
||||
# Prints #PID<...> where the pid is for the process that called register/3 above
|
||||
# Prints #PID<...> where the PID is for the process that called register/3 above
|
||||
#=> :ok
|
||||
|
||||
Dispatching happens in the process that calls `dispatch/3` either serially or
|
||||
@@ -161,7 +161,7 @@ defmodule Registry do
|
||||
in the function documentation.
|
||||
|
||||
However, keep in mind those cases are typically not an issue. After all, a
|
||||
process referenced by a pid may crash at any time, including between getting
|
||||
process referenced by a PID may crash at any time, including between getting
|
||||
the value from the registry and sending it a message. Many parts of the standard
|
||||
library are designed to cope with that, such as `Process.monitor/1` which will
|
||||
deliver the `:DOWN` message immediately if the monitored process is already dead
|
||||
@@ -203,6 +203,12 @@ defmodule Registry do
|
||||
@typedoc "A list of guards to be evaluated when matching on objects in a registry"
|
||||
@type guards :: [guard] | []
|
||||
|
||||
@typedoc "A pattern used to representing the output format part of a match spec"
|
||||
@type body :: [atom | tuple]
|
||||
|
||||
@typedoc "A full match spec used when selecting objects in the registry"
|
||||
@type spec :: [{match_pattern, guards, body}]
|
||||
|
||||
## Via callbacks
|
||||
|
||||
@doc false
|
||||
@@ -316,11 +322,17 @@ defmodule Registry do
|
||||
"expected :keys to be given and be one of :unique or :duplicate, got: #{inspect(keys)}"
|
||||
end
|
||||
|
||||
name = Keyword.get(options, :name)
|
||||
name =
|
||||
case Keyword.fetch(options, :name) do
|
||||
{:ok, name} when is_atom(name) ->
|
||||
name
|
||||
|
||||
unless is_atom(name) do
|
||||
raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect(name)}"
|
||||
end
|
||||
{:ok, other} ->
|
||||
raise ArgumentError, "expected :name to be an atom, got: #{inspect(other)}"
|
||||
|
||||
:error ->
|
||||
raise ArgumentError, "expected :name option to be present"
|
||||
end
|
||||
|
||||
meta = Keyword.get(options, :meta, [])
|
||||
|
||||
@@ -423,8 +435,8 @@ defmodule Registry do
|
||||
for the given `registry`.
|
||||
|
||||
The list of `entries` is a non-empty list of two-element tuples where
|
||||
the first element is the pid and the second element is the value
|
||||
associated to the pid. If there are no entries for the given key,
|
||||
the first element is the PID and the second element is the value
|
||||
associated to the PID. If there are no entries for the given key,
|
||||
the callback is never invoked.
|
||||
|
||||
If the registry is partitioned, the callback is invoked multiple times
|
||||
@@ -679,7 +691,7 @@ defmodule Registry do
|
||||
|
||||
keys =
|
||||
try do
|
||||
spec = [{{pid, :"$1", :"$2"}, [], [{{:"$1", :"$2"}}]}]
|
||||
spec = [{{pid, :"$1", :"$2", :_}, [], [{{:"$1", :"$2"}}]}]
|
||||
:ets.select(pid_ets, spec)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
@@ -756,8 +768,8 @@ defmodule Registry do
|
||||
# Remove first from the key_ets because in case of crashes
|
||||
# the pid_ets will still be able to clean up. The last step is
|
||||
# to clean if we have no more entries.
|
||||
true = :ets.match_delete(key_ets, {key, {self, :_}})
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets})
|
||||
true = __unregister__(key_ets, {key, {self, :_}}, 1)
|
||||
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
|
||||
|
||||
unlink_if_unregistered(pid_server, pid_ets, self)
|
||||
|
||||
@@ -806,6 +818,7 @@ defmodule Registry do
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec unregister_match(registry, key, match_pattern, guards) :: :ok
|
||||
def unregister_match(registry, key, pattern, guards \\ []) when is_list(guards) do
|
||||
self = self()
|
||||
|
||||
@@ -818,8 +831,7 @@ defmodule Registry do
|
||||
# the pid_ets will still be able to clean up. The last step is
|
||||
# to clean if we have no more entries.
|
||||
|
||||
# Here we want to count all entries for this pid under this key, regardless
|
||||
# of pattern.
|
||||
# Here we want to count all entries for this pid under this key, regardless of pattern.
|
||||
underscore_guard = {:"=:=", {:element, 1, :"$_"}, {:const, key}}
|
||||
total_spec = [{{:_, {self, :_}}, [underscore_guard], [true]}]
|
||||
total = :ets.select_count(key_ets, total_spec)
|
||||
@@ -830,8 +842,7 @@ defmodule Registry do
|
||||
case :ets.select_delete(key_ets, delete_spec) do
|
||||
# We deleted everything, we can just delete the object
|
||||
^total ->
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets})
|
||||
|
||||
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
|
||||
unlink_if_unregistered(pid_server, pid_ets, self)
|
||||
|
||||
for listener <- listeners do
|
||||
@@ -846,11 +857,12 @@ defmodule Registry do
|
||||
# duplicate_bag tables will remove every entry, but we only want to
|
||||
# remove those we have deleted. The solution is to introduce a temp_entry
|
||||
# that indicates how many keys WILL be remaining after the delete operation.
|
||||
counter = System.unique_integer()
|
||||
remaining = total - deleted
|
||||
temp_entry = {self, key, {key_ets, remaining}}
|
||||
temp_entry = {self, key, {key_ets, remaining}, counter}
|
||||
true = :ets.insert(pid_ets, temp_entry)
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets})
|
||||
real_keys = List.duplicate({self, key, key_ets}, remaining)
|
||||
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
|
||||
real_keys = List.duplicate({self, key, key_ets, counter}, remaining)
|
||||
true = :ets.insert(pid_ets, real_keys)
|
||||
# We've recreated the real remaining key entries, so we can now delete
|
||||
# our temporary entry.
|
||||
@@ -868,11 +880,11 @@ defmodule Registry do
|
||||
lookup.
|
||||
|
||||
This function returns `{:ok, owner}` or `{:error, reason}`.
|
||||
The `owner` is the pid in the registry partition responsible for
|
||||
the pid. The owner is automatically linked to the caller.
|
||||
The `owner` is the PID in the registry partition responsible for
|
||||
the PID. The owner is automatically linked to the caller.
|
||||
|
||||
If the registry has unique keys, it will return `{:ok, owner}` unless
|
||||
the key is already associated to a pid, in which case it returns
|
||||
the key is already associated to a PID, in which case it returns
|
||||
`{:error, {:already_registered, pid}}`.
|
||||
|
||||
If the registry has duplicate keys, multiple registrations from the
|
||||
@@ -911,7 +923,9 @@ defmodule Registry do
|
||||
# always be able to do the cleanup. If we register first to the
|
||||
# key one and the process crashes, the key will stay there forever.
|
||||
Process.link(pid_server)
|
||||
true = :ets.insert(pid_ets, {self, key, key_ets})
|
||||
|
||||
counter = System.unique_integer()
|
||||
true = :ets.insert(pid_ets, {self, key, key_ets, counter})
|
||||
|
||||
case register_key(kind, pid_server, key_ets, key, {key, {self, value}}) do
|
||||
{:ok, _} = ok ->
|
||||
@@ -922,10 +936,11 @@ defmodule Registry do
|
||||
ok
|
||||
|
||||
{:error, {:already_registered, ^self}} = error ->
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets, counter})
|
||||
error
|
||||
|
||||
{:error, _} = error ->
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets})
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets, counter})
|
||||
unlink_if_unregistered(pid_server, pid_ets, self)
|
||||
error
|
||||
end
|
||||
@@ -958,7 +973,7 @@ defmodule Registry do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads registry metadata given on `start_link/3`.
|
||||
Reads registry metadata given on `start_link/1`.
|
||||
|
||||
Atoms and tuples are allowed as keys.
|
||||
|
||||
@@ -1129,6 +1144,80 @@ defmodule Registry do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Select key, pid, and values registered using full match specs.
|
||||
|
||||
The `spec` consists of a list of three part tuples, in the shape of `[{match_pattern, guards, body}]`.
|
||||
|
||||
The first part, the match pattern, must be a tuple that will match the structure of the
|
||||
the data stored in the registry, which is `{key, pid, value}`. 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. This can be combined
|
||||
like `{:"$1", :_, :_}`.
|
||||
|
||||
The second part, the guards, is a list of conditions that allow filtering the results.
|
||||
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.
|
||||
|
||||
The third part, the body, is a list of shapes of the returned entries. Like guards, you have access to
|
||||
assigned variables like `:"$1"`, which you can combine with hardcoded values to freely shape entries
|
||||
Note that tuples have to be wrapped in an additional tuple. To get a result format like
|
||||
`%{key: key, pid: pid, value: value}`, assuming you bound those variables in order in the match part,
|
||||
you would provide a body like `[%{key: :"$1", pid: :"$2", value: :"$3"}]`. Like guards, you can use
|
||||
some operations like `:element` to modify the output format.
|
||||
|
||||
Do not use special match variables `:"$_"` and `:"$$"`, because they might not work as expected.
|
||||
|
||||
Note that for large registries with many partitions this will be costly as it builds the result by
|
||||
concatenating all the partitions.
|
||||
|
||||
## Examples
|
||||
|
||||
This example shows how to get everything from the registry.
|
||||
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
|
||||
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
|
||||
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}])
|
||||
[{"world", self(), :value}, {"hello", self(), :value}]
|
||||
|
||||
Get all keys in the registry.
|
||||
|
||||
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
|
||||
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
|
||||
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
|
||||
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}])
|
||||
["world", "hello"]
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec select(registry, spec) :: [term]
|
||||
def select(registry, spec)
|
||||
when is_atom(registry) and is_list(spec) do
|
||||
spec =
|
||||
for part <- spec do
|
||||
case part do
|
||||
{{key, pid, value}, guards, select} ->
|
||||
{{key, {pid, value}}, guards, select}
|
||||
|
||||
_ ->
|
||||
raise ArgumentError,
|
||||
"invalid match specification in Registry.select/2: #{inspect(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
case key_info!(registry) do
|
||||
{_kind, partitions, nil} ->
|
||||
Enum.flat_map(0..(partitions - 1), fn partition_index ->
|
||||
:ets.select(key_ets!(registry, partition_index), spec)
|
||||
end)
|
||||
|
||||
{_kind, 1, key_ets} ->
|
||||
:ets.select(key_ets, spec)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, hash: 2}
|
||||
@@ -1193,6 +1282,24 @@ defmodule Registry do
|
||||
Process.unlink(pid_server)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __unregister__(table, match, pos) do
|
||||
key = :erlang.element(pos, match)
|
||||
|
||||
# We need to perform an element comparison if we have an special atom key.
|
||||
if is_atom(key) and reserved_atom?(Atom.to_string(key)) do
|
||||
match = :erlang.setelement(pos, match, :_)
|
||||
guard = {:"=:=", {:element, pos, :"$_"}, {:const, key}}
|
||||
:ets.select_delete(table, [{match, [guard], [true]}]) >= 0
|
||||
else
|
||||
:ets.match_delete(table, match)
|
||||
end
|
||||
end
|
||||
|
||||
defp reserved_atom?("_"), do: true
|
||||
defp reserved_atom?("$" <> _), do: true
|
||||
defp reserved_atom?(_), do: false
|
||||
end
|
||||
|
||||
defmodule Registry.Supervisor do
|
||||
@@ -1220,12 +1327,12 @@ defmodule Registry.Supervisor do
|
||||
end
|
||||
|
||||
# Unique registries have their key partition hashed by key.
|
||||
# This means that, if a pid partition crashes, it may have
|
||||
# This means that, if a PID partition crashes, it may have
|
||||
# entries from all key partitions, so we need to crash all.
|
||||
defp strategy_for_kind(:unique), do: :one_for_all
|
||||
|
||||
# Duplicate registries have both key and pid partitions hashed
|
||||
# by pid. This means that, if a pid partition crashes, all of
|
||||
# by pid. This means that, if a PID partition crashes, all of
|
||||
# its associated entries are in its sibling table, so we crash one.
|
||||
defp strategy_for_kind(:duplicate), do: :one_for_one
|
||||
end
|
||||
@@ -1322,7 +1429,7 @@ defmodule Registry.Partition do
|
||||
def handle_info({:EXIT, pid, _reason}, ets) do
|
||||
entries = :ets.take(ets, pid)
|
||||
|
||||
for {_pid, key, key_ets} <- entries do
|
||||
for {_pid, key, key_ets, _counter} <- entries do
|
||||
key_ets =
|
||||
case key_ets do
|
||||
# In case the fake key_ets is being used. See unregister_match/2.
|
||||
@@ -1334,7 +1441,7 @@ defmodule Registry.Partition do
|
||||
end
|
||||
|
||||
try do
|
||||
:ets.match_delete(key_ets, {key, {pid, :_}})
|
||||
Registry.__unregister__(key_ets, {key, {pid, :_}}, 1)
|
||||
catch
|
||||
:error, :badarg -> :badarg
|
||||
end
|
||||
|
||||
@@ -11,7 +11,6 @@ defmodule Set do
|
||||
@type values :: [value]
|
||||
@type t :: map
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
message = "Use the MapSet module for working with sets"
|
||||
|
||||
defmacrop target(set) do
|
||||
|
||||
+91
-76
@@ -4,7 +4,7 @@ defmodule Stream do
|
||||
|
||||
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,
|
||||
elements one by one during enumeration is called a stream. For example,
|
||||
Elixir's `Range` is a stream:
|
||||
|
||||
iex> range = 1..5
|
||||
@@ -22,9 +22,9 @@ defmodule Stream do
|
||||
[3, 5, 7]
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
meant to multiply each item in the range by 2. At this point, no
|
||||
meant to multiply each element in the range by 2. At this point, no
|
||||
computation was done. Only when `Enum.map/2` is called we actually
|
||||
enumerate over each item in the range, multiplying it by 2 and adding 1.
|
||||
enumerate over each element in the range, multiplying it by 2 and adding 1.
|
||||
We say the functions in `Stream` are *lazy* and the functions in `Enum`
|
||||
are *eager*.
|
||||
|
||||
@@ -46,7 +46,7 @@ defmodule Stream do
|
||||
6
|
||||
#=> [2, 4, 6]
|
||||
|
||||
Notice that we first printed each item in the list, then multiplied each
|
||||
Notice that we first printed each element in the list, then multiplied each
|
||||
element by 2 and finally printed each new value. In this example, the list
|
||||
was enumerated three times. Let's see an example with streams:
|
||||
|
||||
@@ -63,8 +63,8 @@ defmodule Stream do
|
||||
6
|
||||
#=> [2, 4, 6]
|
||||
|
||||
Although the end result is the same, the order in which the items were
|
||||
printed changed! With streams, we print the first item and then print
|
||||
Although the end result is the same, the order in which the elements were
|
||||
printed changed! With streams, we print the first element and then print
|
||||
its double. In this example, the list was enumerated just once!
|
||||
|
||||
That's what we meant when we said earlier that streams are composable,
|
||||
@@ -72,6 +72,13 @@ defmodule Stream do
|
||||
effectively composing the streams and keeping them lazy. The computations
|
||||
are only performed when you call a function from the `Enum` module.
|
||||
|
||||
Like with `Enum`, the functions in this module work in linear time. This
|
||||
means that, the time it takes to perform an operation grows at the same
|
||||
rate as the length of the list. This is expected on operations such as
|
||||
`Stream.map/2`. After all, if we want to traverse every element on a
|
||||
stream, the longer the stream, the more elements we need to traverse,
|
||||
and the longer it will take.
|
||||
|
||||
## Creating Streams
|
||||
|
||||
There are many functions in Elixir's standard library that return
|
||||
@@ -125,19 +132,16 @@ defmodule Stream do
|
||||
|
||||
## Transformers
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@deprecated "Use Stream.chunk_every/2 instead"
|
||||
def chunk(enum, n), do: chunk(enum, n, n, nil)
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@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)
|
||||
@@ -153,7 +157,7 @@ defmodule Stream do
|
||||
def chunk_every(enum, count), do: chunk_every(enum, count, count, [])
|
||||
|
||||
@doc """
|
||||
Streams the enumerable in chunks, containing `count` items each,
|
||||
Streams the enumerable in chunks, containing `count` elements each,
|
||||
where each new chunk starts `step` elements into the enumerable.
|
||||
|
||||
`step` is optional and, if not passed, defaults to `count`, i.e.
|
||||
@@ -203,7 +207,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec chunk_by(Enumerable.t(), (element -> any)) :: Enumerable.t()
|
||||
def chunk_by(enum, fun) do
|
||||
def chunk_by(enum, fun) when is_function(fun, 1) do
|
||||
R.chunk_by(&chunk_while/4, enum, fun)
|
||||
end
|
||||
|
||||
@@ -220,11 +224,11 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> chunk_fun = fn item, acc ->
|
||||
...> if rem(item, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([item | acc]), []}
|
||||
iex> chunk_fun = fn element, acc ->
|
||||
...> if rem(element, 2) == 0 do
|
||||
...> {:cont, Enum.reverse([element | acc]), []}
|
||||
...> else
|
||||
...> {:cont, [item | acc]}
|
||||
...> {:cont, [element | acc]}
|
||||
...> end
|
||||
...> end
|
||||
iex> after_fun = fn
|
||||
@@ -244,7 +248,8 @@ defmodule Stream do
|
||||
(acc -> {:cont, chunk, acc} | {:cont, acc})
|
||||
) :: Enumerable.t()
|
||||
when chunk: any
|
||||
def chunk_while(enum, acc, chunk_fun, after_fun) do
|
||||
def chunk_while(enum, acc, chunk_fun, after_fun)
|
||||
when is_function(chunk_fun, 2) and is_function(after_fun, 1) do
|
||||
lazy(
|
||||
enum,
|
||||
[acc | after_fun],
|
||||
@@ -257,9 +262,9 @@ defmodule Stream do
|
||||
fn entry, acc(head, [acc | after_fun], tail) ->
|
||||
case callback.(entry, acc) do
|
||||
{:cont, emit, acc} ->
|
||||
# If we emit an item and then we have to halt,
|
||||
# If we emit an element and then we have to halt,
|
||||
# we need to disable the after_fun callback to
|
||||
# avoid emitting even more items.
|
||||
# avoid emitting even more elements.
|
||||
case next(fun, emit, [head | tail]) do
|
||||
{:halt, [head | tail]} -> {:halt, acc(head, [acc | &{:cont, &1}], tail)}
|
||||
{command, [head | tail]} -> {command, acc(head, [acc | after_fun], tail)}
|
||||
@@ -310,16 +315,16 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec dedup_by(Enumerable.t(), (element -> term)) :: Enumerable.t()
|
||||
def dedup_by(enum, fun) do
|
||||
def dedup_by(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, nil, fn f1 -> R.dedup(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily drops the next `n` items from the enumerable.
|
||||
Lazily drops the next `n` elements from the enumerable.
|
||||
|
||||
If a negative `n` is given, it will drop the last `n` items from
|
||||
If a negative `n` is given, it will drop the last `n` elements from
|
||||
the collection. Note that the mechanism by which this is implemented
|
||||
will delay the emission of any item until `n` additional items have
|
||||
will delay the emission of any element until `n` additional elements have
|
||||
been emitted by the enum.
|
||||
|
||||
## Examples
|
||||
@@ -333,7 +338,7 @@ defmodule Stream do
|
||||
[1, 2, 3, 4, 5]
|
||||
|
||||
"""
|
||||
@spec drop(Enumerable.t(), non_neg_integer) :: Enumerable.t()
|
||||
@spec drop(Enumerable.t(), integer) :: Enumerable.t()
|
||||
def drop(enum, n) when is_integer(n) and n >= 0 do
|
||||
lazy(enum, n, fn f1 -> R.drop(f1) end)
|
||||
end
|
||||
@@ -365,9 +370,9 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that drops every `nth` item from the enumerable.
|
||||
Creates a stream that drops every `nth` element from the enumerable.
|
||||
|
||||
The first item is always dropped, unless `nth` is 0.
|
||||
The first element is always dropped, unless `nth` is 0.
|
||||
|
||||
`nth` must be a non-negative integer.
|
||||
|
||||
@@ -407,12 +412,12 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec drop_while(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
|
||||
def drop_while(enum, fun) do
|
||||
def drop_while(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, true, fn f1 -> R.drop_while(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Executes the given function for each item.
|
||||
Executes the given function for each element.
|
||||
|
||||
Useful for adding side effects (like printing) to a stream.
|
||||
|
||||
@@ -429,7 +434,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec each(Enumerable.t(), (element -> term)) :: Enumerable.t()
|
||||
def each(enum, fun) do
|
||||
def each(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, fn f1 ->
|
||||
fn x, acc ->
|
||||
fun.(x)
|
||||
@@ -456,7 +461,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec flat_map(Enumerable.t(), (element -> Enumerable.t())) :: Enumerable.t()
|
||||
def flat_map(enum, mapper) do
|
||||
def flat_map(enum, mapper) when is_function(mapper, 1) do
|
||||
transform(enum, nil, fn val, nil -> {mapper.(val), nil} end)
|
||||
end
|
||||
|
||||
@@ -472,12 +477,11 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec filter(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
|
||||
def filter(enum, fun) do
|
||||
def filter(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, fn f1 -> R.filter(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@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)
|
||||
@@ -489,7 +493,7 @@ defmodule Stream do
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
This operation will block the caller by the given interval
|
||||
every time a new item is streamed.
|
||||
every time a new element is streamed.
|
||||
|
||||
Do not use this function to generate a sequence of numbers.
|
||||
If blocking the caller process is not necessary, use
|
||||
@@ -502,7 +506,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t()
|
||||
def interval(n) do
|
||||
def interval(n) when is_integer(n) and n >= 0 do
|
||||
unfold(0, fn count ->
|
||||
Process.sleep(n)
|
||||
{count, count + 1}
|
||||
@@ -516,7 +520,7 @@ defmodule Stream do
|
||||
is delayed until the stream is executed. See `run/1` for an example.
|
||||
"""
|
||||
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Enumerable.t()
|
||||
def into(enum, collectable, transform \\ fn x -> x end) do
|
||||
def into(enum, collectable, transform \\ fn x -> x end) when is_function(transform, 1) do
|
||||
&do_into(enum, collectable, transform, &1, &2)
|
||||
end
|
||||
|
||||
@@ -561,15 +565,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec map(Enumerable.t(), (element -> any)) :: Enumerable.t()
|
||||
def map(enum, fun) do
|
||||
def map(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, fn f1 -> R.map(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
every `nth` item from the enumerable.
|
||||
every `nth` element from the enumerable.
|
||||
|
||||
The first item is always passed to the given function.
|
||||
The first element is always passed to the given function.
|
||||
|
||||
`nth` must be a non-negative integer.
|
||||
|
||||
@@ -590,13 +594,15 @@ defmodule Stream do
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec map_every(Enumerable.t(), non_neg_integer, (element -> any)) :: Enumerable.t()
|
||||
def map_every(enum, nth, fun)
|
||||
def map_every(enum, nth, fun) when is_integer(nth) and nth >= 0 and is_function(fun, 1) do
|
||||
map_every_after_guards(enum, nth, fun)
|
||||
end
|
||||
|
||||
def map_every(enum, 1, fun), do: map(enum, fun)
|
||||
def map_every(enum, 0, _fun), do: %Stream{enum: enum}
|
||||
def map_every([], _nth, _fun), do: %Stream{enum: []}
|
||||
defp map_every_after_guards(enum, 1, fun), do: map(enum, fun)
|
||||
defp map_every_after_guards(enum, 0, _fun), do: %Stream{enum: enum}
|
||||
defp map_every_after_guards([], _nth, _fun), do: %Stream{enum: []}
|
||||
|
||||
def map_every(enum, nth, fun) when is_integer(nth) and nth > 0 do
|
||||
defp map_every_after_guards(enum, nth, fun) do
|
||||
lazy(enum, nth, fn f1 -> R.map_every(nth, fun, f1) end)
|
||||
end
|
||||
|
||||
@@ -612,7 +618,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec reject(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
|
||||
def reject(enum, fun) do
|
||||
def reject(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, fn f1 -> R.reject(fun, f1) end)
|
||||
end
|
||||
|
||||
@@ -655,7 +661,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t(), (element, acc -> any)) :: Enumerable.t()
|
||||
def scan(enum, fun) do
|
||||
def scan(enum, fun) when is_function(fun, 2) do
|
||||
lazy(enum, :first, fn f1 -> R.scan2(fun, f1) end)
|
||||
end
|
||||
|
||||
@@ -672,12 +678,12 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t(), acc, (element, acc -> any)) :: Enumerable.t()
|
||||
def scan(enum, acc, fun) do
|
||||
def scan(enum, acc, fun) when is_function(fun, 2) do
|
||||
lazy(enum, acc, fn f1 -> R.scan3(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily takes the next `count` items from the enumerable and stops
|
||||
Lazily takes the next `count` elements from the enumerable and stops
|
||||
enumeration.
|
||||
|
||||
If a negative `count` is given, the last `count` values will be taken.
|
||||
@@ -702,21 +708,26 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t(), integer) :: Enumerable.t()
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
def take([], _count), do: %Stream{enum: []}
|
||||
def take(enum, count) when is_integer(count) do
|
||||
take_after_guards(enum, count)
|
||||
end
|
||||
|
||||
def take(enum, count) when is_integer(count) and count > 0 do
|
||||
defp take_after_guards(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
defp take_after_guards([], _count), do: %Stream{enum: []}
|
||||
|
||||
defp take_after_guards(enum, count) when count > 0 do
|
||||
lazy(enum, count, fn f1 -> R.take(f1) end)
|
||||
end
|
||||
|
||||
def take(enum, count) when is_integer(count) and count < 0 do
|
||||
defp take_after_guards(enum, count) when count < 0 do
|
||||
&Enumerable.reduce(Enum.take(enum, count), &1, &2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that takes every `nth` item from the enumerable.
|
||||
Creates a stream that takes every `nth` element from the enumerable.
|
||||
|
||||
The first item is always included, unless `nth` is 0.
|
||||
The first element is always included, unless `nth` is 0.
|
||||
|
||||
`nth` must be a non-negative integer.
|
||||
|
||||
@@ -736,11 +747,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take_every(Enumerable.t(), non_neg_integer) :: Enumerable.t()
|
||||
def take_every(enum, nth)
|
||||
def take_every(_enum, 0), do: %Stream{enum: []}
|
||||
def take_every([], _nth), do: %Stream{enum: []}
|
||||
def take_every(enum, nth) when is_integer(nth) and nth >= 0 do
|
||||
take_every_after_guards(enum, nth)
|
||||
end
|
||||
|
||||
def take_every(enum, nth) when is_integer(nth) and nth > 0 do
|
||||
defp take_every_after_guards(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
defp take_every_after_guards([], _nth), do: %Stream{enum: []}
|
||||
|
||||
defp take_every_after_guards(enum, nth) do
|
||||
lazy(enum, nth, fn f1 -> R.take_every(nth, f1) end)
|
||||
end
|
||||
|
||||
@@ -756,7 +771,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take_while(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
|
||||
def take_while(enum, fun) do
|
||||
def take_while(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, fn f1 -> R.take_while(fun, f1) end)
|
||||
end
|
||||
|
||||
@@ -764,7 +779,7 @@ defmodule Stream do
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`. This operation will block the caller by
|
||||
the given time until the item is streamed.
|
||||
the given time until the element is streamed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -773,14 +788,14 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t()
|
||||
def timer(n) do
|
||||
def timer(n) when is_integer(n) and n >= 0 do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
It expects an accumulator and a function that receives each stream item
|
||||
It expects an accumulator and a function that receives each stream element
|
||||
and an accumulator, and must return a tuple containing a new stream
|
||||
(often a list) with the new accumulator or a tuple with `:halt` as first
|
||||
element and the accumulator as second.
|
||||
@@ -807,7 +822,7 @@ defmodule Stream do
|
||||
@spec transform(Enumerable.t(), acc, fun) :: Enumerable.t()
|
||||
when fun: (element, acc -> {Enumerable.t(), acc} | {:halt, acc}),
|
||||
acc: any
|
||||
def transform(enum, acc, reducer) do
|
||||
def transform(enum, acc, reducer) when is_function(reducer, 2) do
|
||||
&do_transform(enum, fn -> acc end, reducer, &1, &2, nil)
|
||||
end
|
||||
|
||||
@@ -824,7 +839,8 @@ defmodule Stream do
|
||||
@spec transform(Enumerable.t(), (() -> acc), fun, (acc -> term)) :: Enumerable.t()
|
||||
when fun: (element, acc -> {Enumerable.t(), acc} | {:halt, acc}),
|
||||
acc: any
|
||||
def transform(enum, start_fun, reducer, after_fun) do
|
||||
def transform(enum, start_fun, reducer, after_fun)
|
||||
when is_function(start_fun, 0) and is_function(reducer, 2) and is_function(after_fun, 1) do
|
||||
&do_transform(enum, start_fun, reducer, &1, &2, after_fun)
|
||||
end
|
||||
|
||||
@@ -979,7 +995,7 @@ 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 elements stored will grow infinitely, never being garbage-collected.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -993,7 +1009,6 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use Stream.uniq_by/2 instead"
|
||||
def uniq(enum, fun) do
|
||||
uniq_by(enum, fun)
|
||||
@@ -1001,7 +1016,7 @@ defmodule Stream do
|
||||
|
||||
@doc """
|
||||
Creates a stream that only emits elements if they are unique, by removing the
|
||||
elements for which function `fun` returned duplicate items.
|
||||
elements for which function `fun` returned duplicate elements.
|
||||
|
||||
The function `fun` maps every element to a term which is used to
|
||||
determine if two elements are duplicates.
|
||||
@@ -1009,7 +1024,7 @@ 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 elements stored will grow infinitely, never being garbage-collected.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -1021,12 +1036,12 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec uniq_by(Enumerable.t(), (element -> term)) :: Enumerable.t()
|
||||
def uniq_by(enum, fun) do
|
||||
def uniq_by(enum, fun) when is_function(fun, 1) do
|
||||
lazy(enum, %{}, fn f1 -> R.uniq_by(fun, f1) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream where each item in the enumerable will
|
||||
Creates a stream where each element in the enumerable will
|
||||
be wrapped in a tuple alongside its index.
|
||||
|
||||
If an `offset` is given, we will index from the given offset instead of from zero.
|
||||
@@ -1043,7 +1058,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec with_index(Enumerable.t(), integer) :: Enumerable.t()
|
||||
def with_index(enum, offset \\ 0) do
|
||||
def with_index(enum, offset \\ 0) when is_integer(offset) do
|
||||
lazy(enum, offset, fn f1 -> R.with_index(f1) end)
|
||||
end
|
||||
|
||||
@@ -1116,8 +1131,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec zip([Enumerable.t()]) :: Enumerable.t()
|
||||
@spec zip(Enumerable.t()) :: Enumerable.t()
|
||||
@spec zip(enumerables) :: Enumerable.t() when enumerables: [Enumerable.t()] | Enumerable.t()
|
||||
def zip(enumerables) do
|
||||
&prepare_zip(enumerables, &1, &2)
|
||||
end
|
||||
@@ -1302,7 +1316,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec iterate(element, (element -> element)) :: Enumerable.t()
|
||||
def iterate(start_value, next_fun) do
|
||||
def iterate(start_value, next_fun) when is_function(next_fun, 1) do
|
||||
unfold({:ok, start_value}, fn
|
||||
{:ok, value} ->
|
||||
{value, {:next, value}}
|
||||
@@ -1325,7 +1339,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec repeatedly((() -> element)) :: Enumerable.t()
|
||||
def repeatedly(generator_fun) do
|
||||
def repeatedly(generator_fun) when is_function(generator_fun, 0) do
|
||||
&do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
@@ -1351,7 +1365,7 @@ defmodule Stream do
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
of elements to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
@@ -1373,7 +1387,8 @@ defmodule Stream do
|
||||
"""
|
||||
@spec resource((() -> acc), (acc -> {[element], acc} | {:halt, acc}), (acc -> term)) ::
|
||||
Enumerable.t()
|
||||
def resource(start_fun, next_fun, after_fun) do
|
||||
def resource(start_fun, next_fun, after_fun)
|
||||
when is_function(start_fun, 0) and is_function(next_fun, 1) and is_function(after_fun, 1) do
|
||||
&do_resource(start_fun.(), next_fun, &1, &2, after_fun)
|
||||
end
|
||||
|
||||
@@ -1481,7 +1496,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec unfold(acc, (acc -> {element, acc} | nil)) :: Enumerable.t()
|
||||
def unfold(next_acc, next_fun) do
|
||||
def unfold(next_acc, next_fun) when is_function(next_fun, 1) do
|
||||
&do_unfold(next_acc, next_fun, &1, &2)
|
||||
end
|
||||
|
||||
|
||||
+109
-84
@@ -4,15 +4,15 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## Codepoints and grapheme cluster
|
||||
## Code points and grapheme cluster
|
||||
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 11.0.0.
|
||||
|
||||
As per the standard, a codepoint is a single Unicode Character,
|
||||
As per the standard, a code point is a single Unicode Character,
|
||||
which may be represented by one or more bytes.
|
||||
|
||||
For example, the codepoint "é" is two bytes:
|
||||
For example, the code point "é" is two bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
2
|
||||
@@ -24,9 +24,9 @@ defmodule String do
|
||||
|
||||
Furthermore, this module also presents the concept of grapheme cluster
|
||||
(from now on referenced as graphemes). Graphemes can consist of multiple
|
||||
codepoints that may be perceived as a single character by readers. For
|
||||
example, "é" can be represented either as a single "e with acute" codepoint
|
||||
or as the letter "e" followed by a "combining acute accent" (two codepoints):
|
||||
code points that may be perceived as a single character by readers. For
|
||||
example, "é" can be represented either as a single "e with acute" code point
|
||||
or as the letter "e" followed by a "combining acute accent" (two code points):
|
||||
|
||||
iex> string = "\u0065\u0301"
|
||||
iex> byte_size(string)
|
||||
@@ -51,7 +51,7 @@ defmodule String do
|
||||
Standard, but do not contain any of the locale specific behaviour.
|
||||
|
||||
More information about graphemes can be found in the [Unicode
|
||||
Standard Annex #29](http://www.unicode.org/reports/tr29/).
|
||||
Standard Annex #29](https://www.unicode.org/reports/tr29/).
|
||||
The current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
@@ -62,7 +62,7 @@ defmodule String do
|
||||
|
||||
To act according to the Unicode Standard, many functions
|
||||
in this module run in linear time, as they need to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
the whole string considering the proper Unicode code points.
|
||||
|
||||
For example, `String.length/1` will take longer as
|
||||
the input grows. On the other hand, `Kernel.byte_size/1` always runs
|
||||
@@ -110,7 +110,7 @@ defmodule String do
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `Kernel.byte_size/1`
|
||||
and `Kernel.binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
the middle of a code point made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
@@ -132,18 +132,18 @@ defmodule String do
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint into multiple bytes.
|
||||
code point into multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
## Integer code points
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
Although code points could be represented as integers, this
|
||||
module represents all code points as strings. For example:
|
||||
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` construct:
|
||||
code point. One may use the `?` construct:
|
||||
|
||||
iex> ?o
|
||||
111
|
||||
@@ -157,7 +157,7 @@ defmodule String do
|
||||
iex> aacute
|
||||
225
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
As we have seen above, code points can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
"ol\u0061\u0301" #=>
|
||||
@@ -168,11 +168,11 @@ defmodule String do
|
||||
The UTF-8 encoding is self-synchronizing. This means that
|
||||
if malformed data (i.e., data that is not possible according
|
||||
to the definition of the encoding) is encountered, only one
|
||||
codepoint needs to be rejected.
|
||||
code point needs to be rejected.
|
||||
|
||||
This module relies on this behaviour to ignore such invalid
|
||||
characters. For example, `length/1` will return
|
||||
a correct result even if an invalid codepoint is fed into it.
|
||||
a correct result even if an invalid code point is fed into it.
|
||||
|
||||
In other words, this module expects invalid data to be detected
|
||||
elsewhere, usually when retrieving data from the external source.
|
||||
@@ -212,10 +212,10 @@ defmodule String do
|
||||
"""
|
||||
@type t :: binary
|
||||
|
||||
@typedoc "A UTF-8 codepoint. It may be one or more bytes."
|
||||
@typedoc "A UTF-8 code point. It may be one or more bytes."
|
||||
@type codepoint :: t
|
||||
|
||||
@typedoc "Multiple codepoints that may be perceived as a single character by readers"
|
||||
@typedoc "Multiple code points that may be perceived as a single character by readers"
|
||||
@type grapheme :: t
|
||||
|
||||
@typedoc "Pattern used in functions like `replace/3` and `split/2`"
|
||||
@@ -798,12 +798,10 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.trim_trailing/1 instead"
|
||||
defdelegate rstrip(binary), to: String.Break, as: :trim_trailing
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@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>>, "")
|
||||
@@ -1013,26 +1011,22 @@ defmodule String do
|
||||
defp append_unless_empty(prefix, suffix), do: prefix <> suffix
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.trim_leading/1 instead"
|
||||
defdelegate lstrip(binary), to: String.Break, as: :trim_leading
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@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
|
||||
@deprecated "Use String.trim/1 instead"
|
||||
def strip(string) do
|
||||
trim(string)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use String.trim/2 with a binary second argument instead"
|
||||
def strip(string, char) do
|
||||
trim(string, <<char::utf8>>)
|
||||
@@ -1052,7 +1046,7 @@ defmodule String do
|
||||
defdelegate trim_leading(string), to: String.Break
|
||||
|
||||
@doc """
|
||||
Returns a string where all leading `to_trim`s have been removed.
|
||||
Returns a string where all leading `to_trim` characters have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1082,7 +1076,7 @@ defmodule String do
|
||||
defdelegate trim_trailing(string), to: String.Break
|
||||
|
||||
@doc """
|
||||
Returns a string where all trailing `to_trim`s have been removed.
|
||||
Returns a string where all trailing `to_trim` characters have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1116,7 +1110,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where all leading and trailing `to_trim`s have been
|
||||
Returns a string where all leading and trailing `to_trim` characters have been
|
||||
removed.
|
||||
|
||||
## Examples
|
||||
@@ -1257,28 +1251,24 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove by 2.0
|
||||
@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
|
||||
@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>>])
|
||||
@@ -1288,8 +1278,13 @@ defmodule String do
|
||||
Returns a new string created by replacing occurrences of `pattern` in
|
||||
`subject` with `replacement`.
|
||||
|
||||
The `subject` is always a string.
|
||||
|
||||
The `pattern` may be a string, a regular expression, or a compiled pattern.
|
||||
|
||||
The `replacement` may be a string or a function that receives the matched
|
||||
pattern and must return the replacement as a string or iodata.
|
||||
|
||||
By default it replaces all occurrences but this behaviour can be controlled
|
||||
through the `:global` option; see the "Options" section below.
|
||||
|
||||
@@ -1299,12 +1294,6 @@ defmodule String do
|
||||
with `replacement`, otherwise only the first occurrence is
|
||||
replaced. Defaults to `true`
|
||||
|
||||
* `:insert_replaced` - (integer or list of integers) specifies the position
|
||||
where to insert the replaced part inside the `replacement`. If any
|
||||
position given in the `:insert_replaced` option is larger than the
|
||||
replacement string, or is negative, an `ArgumentError` is raised. See the
|
||||
examples below
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
@@ -1313,6 +1302,12 @@ defmodule String do
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
The pattern may also be a list of strings and the replacement may also
|
||||
be a function that receives the matched patterns:
|
||||
|
||||
iex> String.replace("a,b,c", ["a", "c"], fn <<char>> -> <<char + 1>> end)
|
||||
"b,b,d"
|
||||
|
||||
When the pattern is a regular expression, one can give `\N` or
|
||||
`\g{N}` in the `replacement` string to access a specific capture in the
|
||||
regular expression:
|
||||
@@ -1325,25 +1320,11 @@ defmodule String do
|
||||
giving `\0`, one can inject the whole matched pattern in the replacement
|
||||
string.
|
||||
|
||||
When the pattern is a string, a developer can use the replaced part inside
|
||||
the `replacement` by using the `:insert_replaced` option and specifying the
|
||||
position(s) inside the `replacement` where the string pattern will be
|
||||
inserted:
|
||||
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
|
||||
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"
|
||||
iex> String.replace("a,b,c", pattern, "[]")
|
||||
"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
|
||||
@@ -1357,43 +1338,89 @@ defmodule String do
|
||||
"ELIXIR"
|
||||
|
||||
"""
|
||||
@spec replace(t, pattern | Regex.t(), t, keyword) :: t
|
||||
@spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), keyword) :: t
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
def replace(subject, "", "", _), do: subject
|
||||
|
||||
def replace(subject, "", replacement, options) do
|
||||
def replace(subject, %{__struct__: Regex} = regex, replacement, options)
|
||||
when is_binary(replacement) or is_function(replacement, 1) do
|
||||
Regex.replace(regex, subject, replacement, options)
|
||||
end
|
||||
|
||||
def replace(subject, "", "", _) when is_binary(subject) do
|
||||
subject
|
||||
end
|
||||
|
||||
def replace(subject, "", replacement, options)
|
||||
when is_binary(subject) and is_binary(replacement) do
|
||||
if Keyword.get(options, :global, true) do
|
||||
IO.iodata_to_binary([replacement | intersperse(subject, replacement)])
|
||||
IO.iodata_to_binary([replacement | intersperse_bin(subject, replacement)])
|
||||
else
|
||||
replacement <> subject
|
||||
end
|
||||
end
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
def replace(subject, "", replacement, options)
|
||||
when is_binary(subject) and is_function(replacement, 1) do
|
||||
if Keyword.get(options, :global, true) do
|
||||
IO.iodata_to_binary([replacement.("") | intersperse_fun(subject, replacement)])
|
||||
else
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
IO.iodata_to_binary([replacement.("") | subject])
|
||||
end
|
||||
end
|
||||
|
||||
defp intersperse(subject, replacement) do
|
||||
def replace(subject, pattern, replacement, options) when is_binary(subject) do
|
||||
if insert = Keyword.get(options, :insert_replaced) do
|
||||
IO.warn(
|
||||
"String.replace/4 with :insert_replaced option is deprecated. " <>
|
||||
"Please use :binary.replace/4 instead or pass an anonymous function as replacement"
|
||||
)
|
||||
|
||||
binary_options = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
:binary.replace(subject, pattern, replacement, [insert_replaced: insert] ++ binary_options)
|
||||
else
|
||||
matches =
|
||||
if Keyword.get(options, :global, true) do
|
||||
:binary.matches(subject, pattern)
|
||||
else
|
||||
case :binary.match(subject, pattern) do
|
||||
:nomatch -> []
|
||||
match -> [match]
|
||||
end
|
||||
end
|
||||
|
||||
IO.iodata_to_binary(do_replace(subject, matches, replacement, 0))
|
||||
end
|
||||
end
|
||||
|
||||
defp intersperse_bin(subject, replacement) do
|
||||
case next_grapheme(subject) do
|
||||
{current, rest} -> [current, replacement | intersperse(rest, replacement)]
|
||||
{current, rest} -> [current, replacement | intersperse_bin(rest, replacement)]
|
||||
nil -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
global = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
defp intersperse_fun(subject, replacement) do
|
||||
case next_grapheme(subject) do
|
||||
{current, rest} -> [current, replacement.("") | intersperse_fun(rest, replacement)]
|
||||
nil -> []
|
||||
end
|
||||
end
|
||||
|
||||
insert =
|
||||
if insert = Keyword.get(options, :insert_replaced),
|
||||
do: [{:insert_replaced, insert}],
|
||||
else: []
|
||||
defp do_replace(subject, [], _, n) do
|
||||
[binary_part(subject, n, byte_size(subject) - n)]
|
||||
end
|
||||
|
||||
global ++ insert
|
||||
defp do_replace(subject, [{start, length} | matches], replacement, n) do
|
||||
prefix = binary_part(subject, n, start - n)
|
||||
|
||||
middle =
|
||||
if is_binary(replacement) do
|
||||
replacement
|
||||
else
|
||||
replacement.(binary_part(subject, start, length))
|
||||
end
|
||||
|
||||
[prefix, middle | do_replace(subject, matches, replacement, start + length)]
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -1462,9 +1489,9 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all codepoints in the string.
|
||||
Returns all code points in the string.
|
||||
|
||||
For details about codepoints and graphemes, see the `String` module documentation.
|
||||
For details about code points and graphemes, see the `String` module documentation.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1488,9 +1515,9 @@ defmodule String do
|
||||
defdelegate codepoints(string), to: String.Unicode
|
||||
|
||||
@doc ~S"""
|
||||
Returns the next codepoint in a string.
|
||||
Returns the next code point in a string.
|
||||
|
||||
The result is a tuple with the codepoint and the
|
||||
The result is a tuple with the code point and the
|
||||
remainder of the string or `nil` in case
|
||||
the string reached its end.
|
||||
|
||||
@@ -1561,7 +1588,6 @@ defmodule String do
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Use String.valid?/1 instead"
|
||||
def valid_character?(string) do
|
||||
case string do
|
||||
@@ -1629,14 +1655,14 @@ defmodule String do
|
||||
defp make_chunk_pred(:valid), do: &valid?/1
|
||||
defp make_chunk_pred(:printable), do: &printable?/1
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Returns Unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm.
|
||||
|
||||
The algorithm is outlined in the [Unicode Standard Annex #29,
|
||||
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
|
||||
Unicode Text Segmentation](https://www.unicode.org/reports/tr29/).
|
||||
|
||||
For details about codepoints and graphemes, see the `String` module documentation.
|
||||
For details about code points and graphemes, see the `String` module documentation.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2132,7 +2158,7 @@ defmodule String do
|
||||
Converts a string into a charlist.
|
||||
|
||||
Specifically, this function takes a UTF-8 encoded binary and returns a list of its integer
|
||||
codepoints. It is similar to `codepoints/1` except that the latter returns a list of codepoints as
|
||||
code points. It is similar to `codepoints/1` except that the latter returns a list of code points as
|
||||
strings.
|
||||
|
||||
In case you need to work with bytes, take a look at the
|
||||
@@ -2169,7 +2195,7 @@ defmodule String do
|
||||
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 Unicode codepoints.
|
||||
The maximum atom size is of 255 Unicode code points.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -2187,7 +2213,7 @@ defmodule String do
|
||||
@doc """
|
||||
Converts a string to an existing atom.
|
||||
|
||||
The maximum atom size is of 255 Unicode codepoints.
|
||||
The maximum atom size is of 255 Unicode code points.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -2466,7 +2492,6 @@ defmodule String do
|
||||
end
|
||||
|
||||
@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)
|
||||
|
||||
@@ -306,7 +306,6 @@ defmodule Supervisor do
|
||||
following options:
|
||||
|
||||
* `: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`
|
||||
|
||||
The `@doc` annotation immediately preceding `use Supervisor` will be
|
||||
@@ -391,7 +390,7 @@ defmodule Supervisor do
|
||||
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
|
||||
process identifier of a new process that is linked to the supervisor.
|
||||
The child process usually starts its work by executing the `init/1`
|
||||
The child process usually starts its work by executing the `c:init/1`
|
||||
callback. Generally speaking, the `init` callback is where we initialize
|
||||
and configure the child process.
|
||||
|
||||
@@ -558,7 +557,8 @@ defmodule Supervisor do
|
||||
process and exits not only on crashes but also if the parent process exits
|
||||
with `:normal` reason.
|
||||
"""
|
||||
@spec start_link([:supervisor.child_spec() | {module, term} | module], options) :: on_start
|
||||
@spec start_link([:supervisor.child_spec() | {module, term} | module], options) ::
|
||||
{:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term}
|
||||
def start_link(children, options) when is_list(children) do
|
||||
{sup_opts, start_opts} = Keyword.split(options, [:strategy, :max_seconds, :max_restarts])
|
||||
start_link(Supervisor.Default, init(children, sup_opts), start_opts)
|
||||
@@ -602,7 +602,7 @@ defmodule Supervisor do
|
||||
description of the available strategies.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.9
|
||||
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.10
|
||||
@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
|
||||
unless strategy = options[:strategy] do
|
||||
@@ -796,7 +796,7 @@ defmodule Supervisor do
|
||||
`child_spec` should be a valid child specification. The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
If a child specification with the specified id already exists, `child_spec` is
|
||||
If a child specification with the specified ID already exists, `child_spec` is
|
||||
discarded and this function returns an error with `:already_started` or
|
||||
`:already_present` if the corresponding child process is running or not,
|
||||
respectively.
|
||||
@@ -820,7 +820,7 @@ defmodule Supervisor do
|
||||
call(supervisor, {:start_child, child_spec})
|
||||
end
|
||||
|
||||
# TODO: Deprecate this on Elixir v1.9. Remove and update typespec on v2.0.
|
||||
# TODO: Deprecate this clause on Elixir v1.10
|
||||
def start_child(supervisor, args) when is_list(args) do
|
||||
call(supervisor, {:start_child, args})
|
||||
end
|
||||
@@ -830,7 +830,7 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child identified by child id.
|
||||
Terminates the given child identified by `child_id`.
|
||||
|
||||
The process is terminated, if there's one. The child specification is
|
||||
kept unless the child is temporary.
|
||||
@@ -840,14 +840,14 @@ defmodule Supervisor do
|
||||
Use `delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no child
|
||||
specification for the given child id, this function returns
|
||||
specification for the given child ID, this function returns
|
||||
`{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, term()) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
def terminate_child(supervisor, child_id)
|
||||
|
||||
# TODO: Deprecate this clause on Elixir v1.9
|
||||
# TODO: Deprecate this clause on Elixir v1.10
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
call(supervisor, {:terminate_child, pid})
|
||||
end
|
||||
|
||||
@@ -127,7 +127,7 @@ defmodule Supervisor.Spec do
|
||||
@typedoc "Supported module values"
|
||||
@type modules :: :dynamic | [module]
|
||||
|
||||
@typedoc "Supported id values"
|
||||
@typedoc "Supported ID values"
|
||||
@type child_id :: term
|
||||
|
||||
@typedoc "The supervisor specification"
|
||||
@@ -196,7 +196,7 @@ defmodule Supervisor.Spec do
|
||||
defp assert_unique_ids([id | rest]) do
|
||||
if id in rest do
|
||||
raise ArgumentError,
|
||||
"duplicated id #{inspect(id)} found in the supervisor specification, " <>
|
||||
"duplicated ID #{inspect(id)} found in the supervisor specification, " <>
|
||||
"please explicitly pass the :id option when defining this worker/supervisor"
|
||||
else
|
||||
assert_unique_ids(rest)
|
||||
|
||||
+187
-26
@@ -36,11 +36,11 @@ defmodule System do
|
||||
|
||||
Generally speaking, the VM provides three time measurements:
|
||||
|
||||
* `os_time/0` - the time reported by the OS. This time may be
|
||||
* `os_time/0` - the time reported by the operating system (OS). This time may be
|
||||
adjusted forwards or backwards in time with no limitation;
|
||||
|
||||
* `system_time/0` - the VM view of the `os_time/0`. The system time and OS
|
||||
time may not match in case of time warps although the VM works towards
|
||||
* `system_time/0` - the VM view of the `os_time/0`. The system time and operating
|
||||
system time may not match in case of time warps although the VM works towards
|
||||
aligning them. This time is not monotonic (i.e., it may decrease)
|
||||
as its behaviour is configured [by the VM time warp
|
||||
mode](http://www.erlang.org/doc/apps/erts/time_correction.html#Time_Warp_Modes);
|
||||
@@ -49,7 +49,7 @@ defmodule System do
|
||||
by the Erlang VM.
|
||||
|
||||
The time functions in this module work in the `:native` unit
|
||||
(unless specified otherwise), which is OS dependent. Most of
|
||||
(unless specified otherwise), which is operating system dependent. Most of
|
||||
the time, all calculations are done in the `:native` unit, to
|
||||
avoid loss of precision, with `convert_time_unit/3` being
|
||||
invoked at the end to convert to a specific time unit like
|
||||
@@ -111,7 +111,7 @@ defmodule System do
|
||||
:erlang.list_to_binary(:erlang.system_info(:otp_release))
|
||||
end
|
||||
|
||||
# Tries to run "git rev-parse --short HEAD". In the case of success returns
|
||||
# Tries to run "git rev-parse --short=7 HEAD". In the case of success returns
|
||||
# the short revision hash. If that fails, returns an empty string.
|
||||
defmacrop get_revision do
|
||||
null =
|
||||
@@ -120,7 +120,7 @@ defmodule System do
|
||||
_ -> '/dev/null'
|
||||
end
|
||||
|
||||
'git rev-parse --short HEAD 2> '
|
||||
'git rev-parse --short=7 HEAD 2> '
|
||||
|> Kernel.++(null)
|
||||
|> :os.cmd()
|
||||
|> strip
|
||||
@@ -129,8 +129,21 @@ defmodule System do
|
||||
defp revision, do: get_revision()
|
||||
|
||||
# Get the date at compilation time.
|
||||
# Follows https://reproducible-builds.org/specs/source-date-epoch/
|
||||
defmacrop get_date do
|
||||
{{year, month, day}, {hour, minute, second}} = :calendar.universal_time()
|
||||
unix_epoch =
|
||||
if source_date_epoch = :os.getenv('SOURCE_DATE_EPOCH') do
|
||||
try do
|
||||
List.to_integer(source_date_epoch)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
unix_epoch = unix_epoch || :os.system_time(:second)
|
||||
|
||||
{{year, month, day}, {hour, minute, second}} =
|
||||
:calendar.gregorian_seconds_to_datetime(unix_epoch + 62_167_219_200)
|
||||
|
||||
"~4..0b-~2..0b-~2..0bT~2..0b:~2..0b:~2..0bZ"
|
||||
|> :io_lib.format([year, month, day, hour, minute, second])
|
||||
@@ -165,9 +178,42 @@ defmodule System do
|
||||
@doc """
|
||||
Elixir build information.
|
||||
|
||||
Returns a keyword list with Elixir version, Git short revision hash and compilation date.
|
||||
Returns a map with the Elixir version, the Erlang/OTP release it was compiled
|
||||
with, a short Git revision hash and the date and time it was built.
|
||||
|
||||
Every value in the map is a string, and these are:
|
||||
|
||||
* `:build` - the Elixir version, short Git revision hash and
|
||||
Erlang/OTP release it was compiled with
|
||||
* `:date` - a string representation of the ISO8601 date and time it was built
|
||||
* `:opt_release` - OTP release it was compiled with
|
||||
* `:revision` - short Git revision hash. If Git was not available at building
|
||||
time, it is set to `""`
|
||||
* `:version` - the Elixir version
|
||||
|
||||
One should not rely on the specific formats returned by each of those fields.
|
||||
Instead one should use specialized functions, such as `version/0` to retrieve
|
||||
the Elixir version and `otp_release/0` to retrieve the Erlang/OTP release.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> System.build_info()
|
||||
%{
|
||||
build: "1.9.0-dev (772a00a0c) (compiled with Erlang/OTP 21)",
|
||||
date: "2018-12-24T01:09:21Z",
|
||||
otp_release: "21",
|
||||
revision: "772a00a0c",
|
||||
version: "1.9.0-dev"
|
||||
}
|
||||
|
||||
"""
|
||||
@spec build_info() :: map
|
||||
@spec build_info() :: %{
|
||||
build: String.t(),
|
||||
date: String.t(),
|
||||
revision: String.t(),
|
||||
version: String.t(),
|
||||
otp_release: String.t()
|
||||
}
|
||||
def build_info do
|
||||
%{
|
||||
build: build(),
|
||||
@@ -209,13 +255,30 @@ defmodule System do
|
||||
:elixir_config.put(:argv, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks if the system should halt or not at the end of ARGV processing.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec no_halt(boolean) :: :ok
|
||||
def no_halt(boolean) when is_boolean(boolean) do
|
||||
:elixir_config.put(:no_halt, boolean)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the system will halt or not at the end of ARGV processing.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec no_halt() :: boolean
|
||||
def no_halt() do
|
||||
:elixir_config.get(:no_halt)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Current working directory.
|
||||
|
||||
Returns the current working directory or `nil` if one
|
||||
is not available.
|
||||
"""
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use File.cwd/0 instead"
|
||||
@spec cwd() :: String.t() | nil
|
||||
def cwd do
|
||||
@@ -230,7 +293,6 @@ defmodule System do
|
||||
|
||||
Returns the current working directory or raises `RuntimeError`.
|
||||
"""
|
||||
# TODO: Remove by 2.0
|
||||
@deprecated "Use File.cwd!/0 instead"
|
||||
@spec cwd!() :: String.t()
|
||||
def cwd! do
|
||||
@@ -351,7 +413,7 @@ defmodule System do
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
extension for each operating system, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
"""
|
||||
@spec find_executable(binary) :: binary | nil
|
||||
@@ -383,17 +445,80 @@ defmodule System do
|
||||
Returns the value of the given environment variable.
|
||||
|
||||
The returned value of the environment variable
|
||||
`varname` is a string, or `nil` if the environment
|
||||
variable is undefined.
|
||||
`varname` is a string. If the environment variable
|
||||
is not set, returns the string specified in `default` or
|
||||
`nil` if none is specified.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> System.get_env("PORT")
|
||||
"4000"
|
||||
|
||||
iex> System.get_env("NOT_SET")
|
||||
nil
|
||||
|
||||
iex> System.get_env("NOT_SET", "4001")
|
||||
"4001"
|
||||
|
||||
"""
|
||||
@spec get_env(String.t()) :: String.t() | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
@doc since: "1.9.0"
|
||||
@spec get_env(String.t(), String.t() | nil) :: String.t() | nil
|
||||
def get_env(varname, default \\ nil)
|
||||
when is_binary(varname) and
|
||||
(is_binary(default) or is_nil(default)) do
|
||||
case :os.getenv(String.to_charlist(varname)) do
|
||||
false -> nil
|
||||
false -> default
|
||||
other -> List.to_string(other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value of the given environment variable or `:error` if not found.
|
||||
|
||||
If the environment variable `varname` is set, then `{:ok, value}` is returned
|
||||
where `value` is a string. If `varname` is not set, `:error` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> System.fetch_env("PORT")
|
||||
{:ok, "4000"}
|
||||
|
||||
iex> System.fetch_env("NOT_SET")
|
||||
:error
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec fetch_env(String.t()) :: {:ok, String.t()} | :error
|
||||
def fetch_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(String.to_charlist(varname)) do
|
||||
false -> :error
|
||||
other -> {:ok, List.to_string(other)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value of the given environment variable or raises if not found.
|
||||
|
||||
Same as `get_env/1` but raises instead of returning `nil` when the variable is
|
||||
not set.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> System.fetch_env!("PORT")
|
||||
"4000"
|
||||
|
||||
iex> System.fetch_env!("NOT_SET")
|
||||
** (ArgumentError) could not fetch environment variable "NOT_SET" because it is not set
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec fetch_env!(String.t()) :: String.t()
|
||||
def fetch_env!(varname) when is_binary(varname) do
|
||||
get_env(varname) ||
|
||||
raise ArgumentError,
|
||||
"could not fetch environment variable #{inspect(varname)} because it is not set"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Erlang VM process identifier.
|
||||
|
||||
@@ -458,7 +583,7 @@ defmodule System do
|
||||
latest exception. To retrieve the stacktrace of the current process,
|
||||
use `Process.info(self(), :current_stacktrace)` instead.
|
||||
"""
|
||||
# TODO: Fully deprecate it on Elixir v1.9.
|
||||
# TODO: Fully deprecate it on Elixir v1.11 via @deprecated
|
||||
# It is currently partially deprecated in elixir_dispatch.erl
|
||||
def stacktrace do
|
||||
apply(:erlang, :get_stacktrace, [])
|
||||
@@ -505,6 +630,40 @@ defmodule System do
|
||||
:erlang.halt(String.to_charlist(status))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the operating system PID for the current Erlang runtime system instance.
|
||||
|
||||
Returns a string containing the (usually) numerical identifier for a process.
|
||||
On UNIX, this is typically the return value of the `getpid()` system call.
|
||||
On Windows, the process ID as returned by the `GetCurrentProcessId()` system
|
||||
call is used.
|
||||
|
||||
## Examples
|
||||
|
||||
System.pid()
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec pid :: String.t()
|
||||
def pid do
|
||||
List.to_string(:os.getpid())
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts all applications in the Erlang runtime system.
|
||||
|
||||
All applications are taken down smoothly, all code is unloaded, and all ports
|
||||
are closed before the system starts all applications once again.
|
||||
|
||||
## Examples
|
||||
|
||||
System.restart()
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec restart :: :ok
|
||||
defdelegate restart(), to: :init
|
||||
|
||||
@doc """
|
||||
Carefully stops the Erlang runtime system.
|
||||
|
||||
@@ -517,8 +676,6 @@ defmodule System do
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For more information, see `:init.stop/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
System.stop(0)
|
||||
@@ -753,10 +910,14 @@ defmodule System do
|
||||
`convert_time_unit/3` accepts an additional time unit (other than the
|
||||
ones in the `t:time_unit/0` type) called `:native`. `:native` is the time
|
||||
unit used by the Erlang runtime system. It's determined when the runtime
|
||||
starts and stays the same until the runtime is stopped. To determine what
|
||||
the `:native` unit amounts to in a system, you can call this function to
|
||||
convert 1 second to the `:native` time unit (i.e.,
|
||||
`System.convert_time_unit(1, :second, :native)`).
|
||||
starts and stays the same until the runtime is stopped, but could differ
|
||||
the next time the runtime is started on the same machine. For this reason,
|
||||
you should use this function to convert `:native` time units to a predictable
|
||||
unit before you display them to humans.
|
||||
|
||||
To determine how many seconds the `:native` unit represents in your current
|
||||
runtime, you can can call this function to convert 1 second to the `:native`
|
||||
time unit: `System.convert_time_unit(1, :second, :native)`.
|
||||
"""
|
||||
@spec convert_time_unit(integer, time_unit | :native, time_unit | :native) :: integer
|
||||
def convert_time_unit(time, from_unit, to_unit) do
|
||||
@@ -793,7 +954,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current OS time.
|
||||
Returns the current operating system (OS) time.
|
||||
|
||||
The result is returned in the `:native` time unit.
|
||||
|
||||
@@ -808,7 +969,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current OS time in the given time `unit`.
|
||||
Returns the current operating system (OS) time in the given time `unit`.
|
||||
|
||||
This time may be adjusted forwards or backwards in time
|
||||
with no limitation and is not monotonic.
|
||||
|
||||
+38
-9
@@ -89,7 +89,7 @@ defmodule Task do
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. `start_link/1`, unlike
|
||||
`async/1`, returns `{:ok, pid}` (which is the result expecte
|
||||
`async/1`, returns `{:ok, pid}` (which is the result expected
|
||||
by supervisors).
|
||||
|
||||
`use Task` defines a `child_spec/1` function, allowing the
|
||||
@@ -97,7 +97,6 @@ defmodule Task do
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `: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, either immediately or by giving it time to shut down
|
||||
|
||||
@@ -158,7 +157,7 @@ defmodule Task do
|
||||
## Distributed tasks
|
||||
|
||||
Since Elixir provides a `Task.Supervisor`, it is easy to use one
|
||||
to dynamically spawn tasks across nodes:
|
||||
to dynamically start tasks across nodes:
|
||||
|
||||
# On the remote node
|
||||
Task.Supervisor.start_link(name: MyApp.DistSupervisor)
|
||||
@@ -173,6 +172,37 @@ defmodule Task do
|
||||
the same module version to exist on all involved nodes. Check the `Agent` module
|
||||
documentation for more information on distributed processes as the limitations
|
||||
described there apply to the whole ecosystem.
|
||||
|
||||
## Ancestor and Caller Tracking
|
||||
|
||||
Whenever you start a new process, Elixir annotates the parent of that process
|
||||
through the `$ancestors` key in the process dictionary. This is often used to
|
||||
track the hierarchy inside a supervision tree.
|
||||
|
||||
For example, we recommend developers to always start tasks under a supervisor.
|
||||
This provides more visibility and allows you to control how those tasks are
|
||||
terminated when a node shuts down. That might look something like
|
||||
`Task.Supervisor.start_child(MySupervisor, task_specification)`. This means
|
||||
that, although your code is the one who invokes the task, the actual ancestor of
|
||||
the task is the supervisor, as the supervisor is the one effectively starting it.
|
||||
|
||||
To track the relationship between your code and the task, we use the `$callers`
|
||||
key in the process dictionary. Therefore, assuming the `Task.Supervisor` call
|
||||
above, we have:
|
||||
|
||||
[your code] -- calls --> [supervisor] ---- spawns --> [task]
|
||||
|
||||
Which means we store the following relationships:
|
||||
|
||||
[your code] [supervisor] <-- ancestor -- [task]
|
||||
^ |
|
||||
|--------------------- caller ---------------------|
|
||||
|
||||
The list of callers of the current process can be retrieved from the Process
|
||||
dictionary with `Process.get(:"$callers")`. This will return either `nil` or
|
||||
a list `[pid_n, ..., pid2, pid1]` with at least one entry Where `pid_n` is
|
||||
the PID that called the current process, `pid2` called `pid_n`, and `pid2` was
|
||||
called by `pid1`.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -397,9 +427,9 @@ defmodule Task do
|
||||
|
||||
@doc """
|
||||
Returns a stream where the given function (`module` and `function_name`)
|
||||
is mapped concurrently on each item in `enumerable`.
|
||||
is mapped concurrently on each element in `enumerable`.
|
||||
|
||||
Each item of `enumerable` will be prepended to the given `args` and
|
||||
Each element of `enumerable` will be prepended to the given `args` and
|
||||
processed by its own task. The tasks will be linked to an intermediate
|
||||
process that is then linked to the current process. This means a failure
|
||||
in a task terminates the current process and a failure in the current process
|
||||
@@ -468,18 +498,18 @@ defmodule Task do
|
||||
|
||||
@doc """
|
||||
Returns a stream that runs the given function `fun` concurrently
|
||||
on each item in `enumerable`.
|
||||
on each element in `enumerable`.
|
||||
|
||||
Works the same as `async_stream/5` but with an anonymous function instead of a
|
||||
module-function-arguments tuple. `fun` must be a one-arity anonymous function.
|
||||
|
||||
Each `enumerable` item is passed as argument to the given function `fun` and
|
||||
Each `enumerable` element is passed as argument to the given function `fun` and
|
||||
processed by its own task. The tasks will be linked to the current process,
|
||||
similarly to `async/1`.
|
||||
|
||||
## Example
|
||||
|
||||
Count the codepoints in each string asynchronously, then add the counts together using reduce.
|
||||
Count the code points 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)
|
||||
@@ -579,7 +609,6 @@ defmodule Task do
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on 2.0
|
||||
@deprecated "Pattern match directly on the message instead"
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value(tasks, fn
|
||||
|
||||
@@ -34,7 +34,7 @@ defmodule Task.Supervised do
|
||||
_ = if mref, do: Process.demonitor(mref, [:flush])
|
||||
send(owner_pid, {ref, invoke_mfa(owner, mfa)})
|
||||
|
||||
{:DOWN, ^mref, _, _, reason} when is_reference(mref) ->
|
||||
{:DOWN, ^mref, _, _, reason} ->
|
||||
exit({:shutdown, reason})
|
||||
after
|
||||
# There is a race condition on this operation when working across
|
||||
|
||||
@@ -78,7 +78,7 @@ defmodule Task.Supervisor do
|
||||
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.9.
|
||||
# TODO: Deprecate passing restart and shutdown here on Elixir v1.10.
|
||||
def start_link(options \\ []) do
|
||||
{restart, options} = Keyword.pop(options, :restart, :temporary)
|
||||
{shutdown, options} = Keyword.pop(options, :shutdown, 5000)
|
||||
@@ -182,12 +182,12 @@ defmodule Task.Supervisor do
|
||||
end
|
||||
|
||||
# In this case the task is already running, so we just return :ok.
|
||||
def handle_call(:start_task, %{ref: ref} = state) when is_reference(ref) do
|
||||
def handle_call(:start_task, _from, %{ref: ref} = state) when is_reference(ref) do
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
# The task is not running yet, so let's start it.
|
||||
def handle_cast(:start_task, %{ref: nil} = state) do
|
||||
def handle_call(:start_task, _from, %{ref: nil} = state) do
|
||||
task =
|
||||
Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn ->
|
||||
...
|
||||
@@ -239,9 +239,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
@doc """
|
||||
Returns a stream where the given function (`module` and `function`)
|
||||
is mapped concurrently on each item in `enumerable`.
|
||||
is mapped concurrently on each element in `enumerable`.
|
||||
|
||||
Each item will be prepended to the given `args` and processed by its
|
||||
Each element will be prepended to the given `args` and processed by its
|
||||
own task. The tasks will be spawned under the given `supervisor` and
|
||||
linked to the current process, similarly to `async/4`.
|
||||
|
||||
@@ -298,9 +298,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
@doc """
|
||||
Returns a stream that runs the given function `fun` concurrently
|
||||
on each item in `enumerable`.
|
||||
on each element in `enumerable`.
|
||||
|
||||
Each item in `enumerable` is passed as argument to the given function `fun`
|
||||
Each element in `enumerable` is passed as argument to the given function `fun`
|
||||
and processed by its own task. The tasks will be spawned under the given
|
||||
`supervisor` and linked to the current process, similarly to `async/2`.
|
||||
|
||||
@@ -315,9 +315,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
@doc """
|
||||
Returns a stream where the given function (`module` and `function`)
|
||||
is mapped concurrently on each item in `enumerable`.
|
||||
is mapped concurrently on each element in `enumerable`.
|
||||
|
||||
Each item in `enumerable` will be prepended to the given `args` and processed
|
||||
Each element in `enumerable` will be prepended to the given `args` and processed
|
||||
by its own task. The tasks will be spawned under the given `supervisor` and
|
||||
will not be linked to the current process, similarly to `async_nolink/4`.
|
||||
|
||||
@@ -339,9 +339,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
@doc """
|
||||
Returns a stream that runs the given `function` concurrently on each
|
||||
item in `enumerable`.
|
||||
element in `enumerable`.
|
||||
|
||||
Each item in `enumerable` is passed as argument to the given function `fun`
|
||||
Each element in `enumerable` is passed as argument to the given function `fun`
|
||||
and processed by its own task. The tasks will be spawned under the given
|
||||
`supervisor` and will not be linked to the current process, similarly to `async_nolink/2`.
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ defmodule Tuple do
|
||||
|
||||
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
|
||||
* `elem/2` - accesses a tuple by index
|
||||
* `put_elem/3` - inserts a value into a tuple by index
|
||||
* `tuple_size/1` - gets 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`
|
||||
@@ -31,16 +31,17 @@ defmodule Tuple do
|
||||
|
||||
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 preferable to use pattern matching:
|
||||
collections. To append to a tuple, it is preferable to extract the elements
|
||||
from the old tuple with pattern matching, and then create a new tuple:
|
||||
|
||||
tuple = {:ok, :example}
|
||||
|
||||
# Avoid
|
||||
Tuple.insert_at(tuple, 2, %{})
|
||||
result = Tuple.insert_at(tuple, 2, %{})
|
||||
|
||||
# Prefer
|
||||
{:ok, atom} = tuple
|
||||
{:ok, atom, %{}}
|
||||
result = {:ok, atom, %{}}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
+49
-44
@@ -29,8 +29,11 @@ defmodule URI do
|
||||
|
||||
import Bitwise
|
||||
|
||||
@reserved_characters ':/?#[]@!$&\'()*+,;='
|
||||
@formatted_reserved_characters Enum.map_join(@reserved_characters, ", ", &<<?`, &1, ?`>>)
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
Returns the default port for a given `scheme`.
|
||||
|
||||
If the scheme is unknown to the `URI` module, this function returns
|
||||
`nil`. The default port for any scheme can be configured globally
|
||||
@@ -76,7 +79,7 @@ defmodule URI do
|
||||
values are URL encoded as per `encode_www_form/1`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
protocol with the exception of lists, which are explicitly forbidden.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -92,7 +95,7 @@ defmodule URI do
|
||||
** (ArgumentError) encode_query/1 values cannot be lists, got: [:a, :list]
|
||||
|
||||
"""
|
||||
@spec encode_query(term) :: binary
|
||||
@spec encode_query(Enum.t()) :: binary
|
||||
def encode_query(enumerable) do
|
||||
Enum.map_join(enumerable, "&", &encode_kv_pair/1)
|
||||
end
|
||||
@@ -112,7 +115,7 @@ defmodule URI do
|
||||
@doc """
|
||||
Decodes a query string into a map.
|
||||
|
||||
Given a query string of the form of `key1=value1&key2=value2...`, this
|
||||
Given a query string in the form of `key1=value1&key2=value2...`, this
|
||||
function inserts each key-value pair in the query string as one entry in the
|
||||
given `map`. Keys and values in the resulting map will be binaries. Keys and
|
||||
values will be percent-unescaped.
|
||||
@@ -128,10 +131,9 @@ defmodule URI do
|
||||
%{"percent" => "oh yes!", "starting" => "map"}
|
||||
|
||||
"""
|
||||
@spec decode_query(binary, %{binary => binary}) :: %{binary => binary}
|
||||
@spec decode_query(binary, %{optional(binary) => binary}) :: %{optional(binary) => binary}
|
||||
def decode_query(query, map \\ %{})
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
def decode_query(query, %_{} = dict) when is_binary(query) do
|
||||
IO.warn("URI.decode_query/2 is deprecated, please use URI.decode_query/1")
|
||||
decode_query_into_dict(query, dict)
|
||||
@@ -141,7 +143,6 @@ defmodule URI do
|
||||
decode_query_into_map(query, map)
|
||||
end
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
def decode_query(query, dict) when is_binary(query) do
|
||||
IO.warn("URI.decode_query/2 is deprecated, please use URI.decode_query/1")
|
||||
decode_query_into_dict(query, dict)
|
||||
@@ -180,6 +181,9 @@ defmodule URI do
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.to_list()
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
iex> URI.query_decoder("food=bread%26butter&drinks=tap%20water") |> Enum.to_list()
|
||||
[{"food", "bread&butter"}, {"drinks", "tap water"}]
|
||||
|
||||
"""
|
||||
@spec query_decoder(binary) :: Enumerable.t()
|
||||
def query_decoder(query) when is_binary(query) do
|
||||
@@ -206,11 +210,11 @@ defmodule URI do
|
||||
{next_pair, rest}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "reserved" character in a URI.
|
||||
@doc ~s"""
|
||||
Checks if `character` is a reserved one in a URI.
|
||||
|
||||
Reserved characters are specified in
|
||||
[RFC 3986, section 2.2](https://tools.ietf.org/html/rfc3986#section-2.2).
|
||||
As specified in [RFC 3986, section 2.2](https://tools.ietf.org/html/rfc3986#section-2.2),
|
||||
the following characters are reserved: #{@formatted_reserved_characters}
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -218,16 +222,19 @@ defmodule URI do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec char_reserved?(char) :: boolean
|
||||
def char_reserved?(char) when char in 0..0x10FFFF do
|
||||
char in ':/?#[]@!$&\'()*+,;='
|
||||
@spec char_reserved?(byte) :: boolean
|
||||
def char_reserved?(character) do
|
||||
character in @reserved_characters
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "unreserved" character in a URI.
|
||||
Checks if `character` is an unreserved one in a URI.
|
||||
|
||||
Unreserved characters are specified in
|
||||
[RFC 3986, section 2.3](https://tools.ietf.org/html/rfc3986#section-2.3).
|
||||
As specified in [RFC 3986, section 2.3](https://tools.ietf.org/html/rfc3986#section-2.3),
|
||||
the following characters are unreserved:
|
||||
|
||||
* Alphanumeric characters: `A-Z`, `a-z`, `0-9`
|
||||
* `~`, `_`, `-`
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -235,13 +242,13 @@ defmodule URI do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec char_unreserved?(char) :: boolean
|
||||
def char_unreserved?(char) when char in 0..0x10FFFF do
|
||||
char in ?0..?9 or char in ?a..?z or char in ?A..?Z or char in '~_-.'
|
||||
@spec char_unreserved?(byte) :: boolean
|
||||
def char_unreserved?(character) do
|
||||
character in ?0..?9 or character in ?a..?z or character in ?A..?Z or character in '~_-.'
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is allowed unescaped in a URI.
|
||||
Checks if `character` is allowed unescaped in a URI.
|
||||
|
||||
This is the default used by `URI.encode/2` where both
|
||||
reserved and unreserved characters are kept unescaped.
|
||||
@@ -252,16 +259,16 @@ defmodule URI do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec char_unescaped?(char) :: boolean
|
||||
def char_unescaped?(char) when char in 0..0x10FFFF do
|
||||
char_reserved?(char) or char_unreserved?(char)
|
||||
@spec char_unescaped?(byte) :: boolean
|
||||
def char_unescaped?(character) do
|
||||
char_reserved?(character) or char_unreserved?(character)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent-escapes all characters that require escaping in a string.
|
||||
Percent-escapes all characters that require escaping in `string`.
|
||||
|
||||
This means reserved characters, such as `:` and `/`, and the so-
|
||||
called unreserved characters, which have the same meaning both
|
||||
This means reserved characters, such as `:` and `/`, and the
|
||||
so-called unreserved characters, which have the same meaning both
|
||||
escaped and unescaped, won't be escaped by default.
|
||||
|
||||
See `encode_www_form` if you are interested in escaping reserved
|
||||
@@ -269,8 +276,9 @@ defmodule URI do
|
||||
|
||||
This function also accepts a `predicate` function as an optional
|
||||
argument. If passed, this function will be called with each byte
|
||||
in `string` as its argument and should return `true` if the given
|
||||
byte should be left as is.
|
||||
in `string` as its argument and should return a truthy value (anything other
|
||||
than `false` or `nil`) if the given byte should be left as is, or return a
|
||||
falsy value (`false` or `nil`) if the character should be escaped.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -281,14 +289,14 @@ defmodule URI do
|
||||
"a str%69ng"
|
||||
|
||||
"""
|
||||
@spec encode(binary, (byte -> boolean)) :: binary
|
||||
@spec encode(binary, (byte -> as_boolean(term))) :: binary
|
||||
def encode(string, predicate \\ &char_unescaped?/1)
|
||||
when is_binary(string) and is_function(predicate, 1) do
|
||||
for <<char <- string>>, into: "", do: percent(char, predicate)
|
||||
for <<byte <- string>>, into: "", do: percent(byte, predicate)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a string as "x-www-form-urlencoded".
|
||||
Encodes `string` as "x-www-form-urlencoded".
|
||||
|
||||
## Example
|
||||
|
||||
@@ -298,8 +306,8 @@ defmodule URI do
|
||||
"""
|
||||
@spec encode_www_form(binary) :: binary
|
||||
def encode_www_form(string) when is_binary(string) do
|
||||
for <<char <- string>>, into: "" do
|
||||
case percent(char, &char_unreserved?/1) do
|
||||
for <<byte <- string>>, into: "" do
|
||||
case percent(byte, &char_unreserved?/1) do
|
||||
"%20" -> "+"
|
||||
percent -> percent
|
||||
end
|
||||
@@ -335,7 +343,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a string as "x-www-form-urlencoded".
|
||||
Decodes `string` as "x-www-form-urlencoded".
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -344,7 +352,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
@spec decode_www_form(binary) :: binary
|
||||
def decode_www_form(string) do
|
||||
def decode_www_form(string) when is_binary(string) do
|
||||
unpercent(string, "", true)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
@@ -440,14 +448,11 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
@spec parse(t | binary) :: t
|
||||
def parse(uri)
|
||||
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(string) when is_binary(string) do
|
||||
# From https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex =
|
||||
Regex.recompile!(~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i)
|
||||
regex = ~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i
|
||||
|
||||
parts = Regex.run(regex, string)
|
||||
|
||||
@@ -480,7 +485,7 @@ defmodule URI do
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(string) do
|
||||
regex = Regex.recompile!(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/)
|
||||
regex = ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/
|
||||
components = Regex.run(regex, string || "")
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], components
|
||||
@@ -497,7 +502,7 @@ defmodule URI do
|
||||
defp nillify(other), do: other
|
||||
|
||||
@doc """
|
||||
Returns the string representation of the given `URI` struct.
|
||||
Returns the string representation of the given [URI struct](`t:t/0`).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -507,8 +512,8 @@ defmodule URI do
|
||||
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
|
||||
Note that when creating this string representation, the `:authority` value 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"})
|
||||
|
||||
+48
-14
@@ -6,7 +6,7 @@ defmodule Version do
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/).
|
||||
[SemVer 2.0 schema](https://semver.org/).
|
||||
|
||||
## Versions
|
||||
|
||||
@@ -107,9 +107,46 @@ defmodule Version do
|
||||
@type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
|
||||
|
||||
defmodule Requirement do
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
A struct that holds version requirement information.
|
||||
|
||||
The struct fields are private and should not be accessed.
|
||||
|
||||
See the "Requirements" section in the `Version` module
|
||||
for more information.
|
||||
"""
|
||||
|
||||
defstruct [:source, :matchspec, :compiled]
|
||||
@type t :: %__MODULE__{source: String.t(), matchspec: :ets.match_spec(), compiled: boolean}
|
||||
|
||||
@opaque t :: %__MODULE__{
|
||||
source: String.t(),
|
||||
matchspec: :ets.match_spec() | :ets.comp_match_spec(),
|
||||
compiled: boolean
|
||||
}
|
||||
|
||||
@doc false
|
||||
@spec new(String.t(), :ets.match_spec()) :: t
|
||||
def new(source, spec) do
|
||||
%__MODULE__{source: source, matchspec: spec, compiled: false}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec compile(t) :: t
|
||||
def compile(%__MODULE__{matchspec: spec} = requirement) do
|
||||
%{requirement | matchspec: :ets.match_spec_compile(spec), compiled: true}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec match?(t, tuple) :: boolean
|
||||
def match?(%__MODULE__{matchspec: spec, compiled: true}, matchable_pattern) do
|
||||
matches = :ets.match_spec_run([matchable_pattern], spec)
|
||||
matches != []
|
||||
end
|
||||
|
||||
def match?(%__MODULE__{matchspec: spec, compiled: false}, matchable_pattern) do
|
||||
{:ok, result} = :ets.test_ms(matchable_pattern, spec)
|
||||
result != false
|
||||
end
|
||||
end
|
||||
|
||||
defmodule InvalidRequirementError do
|
||||
@@ -183,15 +220,11 @@ defmodule Version do
|
||||
match?(version, parse_requirement!(requirement), opts)
|
||||
end
|
||||
|
||||
def match?(version, %Requirement{matchspec: spec, compiled: false}, opts) do
|
||||
def match?(version, requirement, opts) do
|
||||
allow_pre = Keyword.get(opts, :allow_pre, true)
|
||||
{:ok, result} = :ets.test_ms(to_matchable(version, allow_pre), spec)
|
||||
result != false
|
||||
end
|
||||
matchable_pattern = to_matchable(version, allow_pre)
|
||||
|
||||
def match?(version, %Requirement{matchspec: spec, compiled: true}, opts) do
|
||||
allow_pre = Keyword.get(opts, :allow_pre, true)
|
||||
:ets.match_spec_run([to_matchable(version, allow_pre)], spec) != []
|
||||
Requirement.match?(requirement, matchable_pattern)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -312,7 +345,8 @@ defmodule Version do
|
||||
def parse_requirement(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{:ok, spec} ->
|
||||
{:ok, %Requirement{source: string, matchspec: spec, compiled: false}}
|
||||
requirement = Requirement.new(string, spec)
|
||||
{:ok, requirement}
|
||||
|
||||
:error ->
|
||||
:error
|
||||
@@ -338,7 +372,7 @@ defmodule Version do
|
||||
def parse_requirement!(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{:ok, spec} ->
|
||||
%Requirement{source: string, matchspec: spec, compiled: false}
|
||||
Requirement.new(string, spec)
|
||||
|
||||
:error ->
|
||||
raise InvalidRequirementError, string
|
||||
@@ -355,8 +389,8 @@ defmodule Version do
|
||||
compiled match_spec, nor can it be stored on disk).
|
||||
"""
|
||||
@spec compile_requirement(Requirement.t()) :: Requirement.t()
|
||||
def compile_requirement(%Requirement{matchspec: spec} = req) do
|
||||
%{req | matchspec: :ets.match_spec_compile(spec), compiled: true}
|
||||
def compile_requirement(requirement) do
|
||||
Requirement.compile(requirement)
|
||||
end
|
||||
|
||||
defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}, allow_pre?) do
|
||||
|
||||
@@ -4,15 +4,15 @@ 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 applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branches:
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.8 | Bug fixes and security patches
|
||||
1.9 | Bug fixes and security patches
|
||||
1.8 | Security patches only
|
||||
1.7 | Security patches only
|
||||
1.6 | Security patches only
|
||||
1.5 | Security patches only
|
||||
1.4 | Security patches only
|
||||
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
|
||||
@@ -49,8 +49,9 @@ Elixir version | Supported Erlang/OTP versions
|
||||
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
|
||||
1.8 | 20 - 21
|
||||
1.7 | 19 - 22
|
||||
1.8 | 20 - 22
|
||||
1.9 | 20 - 22
|
||||
|
||||
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.
|
||||
|
||||
@@ -62,86 +63,92 @@ 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. 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.
|
||||
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 THREE 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.4 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 | Hard-deprecated in | Replaced by (available since)
|
||||
:----------------------------------------------- | :----------------- | :----------------------------
|
||||
Passing a non-empty list to `Enum.into/2` | [v1.8] | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
|
||||
Passing a non-empty list to `:into` in `for` | [v1.8] | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
|
||||
`:seconds`, `:milliseconds`, etc. as time units | [v1.8] | `:second`, `:millisecond`, etc. (v1.4)
|
||||
`Inspect.Algebra.surround/3` | [v1.8] | `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2` (v1.0)
|
||||
`Inspect.Algebra.surround_many/6` | [v1.8] | `Inspect.Algebra.container_doc/6` (v1.6)
|
||||
`Kernel.ParallelCompiler.files/2` | [v1.8] | `Kernel.ParallelCompiler.compile/2` (v1.6)
|
||||
`Kernel.ParallelCompiler.files_to_path/2` | [v1.8] | `Kernel.ParallelCompiler.compile_to_path/2` (v1.6)
|
||||
`Kernel.ParallelRequire.files/2` | [v1.8] | `Kernel.ParallelCompiler.require/2` (v1.6)
|
||||
`System.cwd/0` and `System.cwd!/0` | [v1.8] | `File.cwd/0` and `File.cwd!/0` (v1.0)
|
||||
`mix compile.erlang` returning `{:ok, contents}` or `:error` as the callback in `Mix.Compilers.Erlang.compile/6`| [v1.8] | Return `{:ok, contents, warnings}` or `{:error, errors, warnings}`
|
||||
`Code.get_docs/2` | [v1.7] | `Code.fetch_docs/1` (v1.7)
|
||||
Calling `super/1` on GenServer callbacks | [v1.7] | Not calling `super/1` (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] | `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)
|
||||
`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`](`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) (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] | 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] | 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)
|
||||
`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)
|
||||
`Access.key/1` | [v1.4] | `Access.key/2` (v1.3)
|
||||
`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` (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` (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)
|
||||
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)
|
||||
`String.valid_character?/1` | [v1.3] | `String.valid?/1` (v1.0)
|
||||
`Task.find/2` | [v1.3] | Use direct message matching (v1.0)
|
||||
`: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] | `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*
|
||||
`?\xHEX` | [v1.1] | `0xHEX` (v1.0)
|
||||
The first column is the version the feature was hard deprecated. The second column shortly describes the deprecated feature and the third column explains the replacement and from which the version the replacement is available from.
|
||||
|
||||
Version | Deprecated feature | Replaced by (available since)
|
||||
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
|
||||
[v1.9] | Passing `:insert_replaced` to `String.replace/4` | Use `:binary.replace/4` (v1.0)
|
||||
[v1.9] | Enumerable keys in `Map.drop/2`, `Map.split/2`, and `Map.take/2` | Call `Enum.to_list/1` on the second argument before hand (v1.0)
|
||||
[v1.9] | `Mix.Project.load_paths/1` | `Mix.Project.compile_path/1` (v1.0)
|
||||
[v1.9] | `--detached` in CLI | `--erl "-detached"` (v1.0)
|
||||
[v1.8] | Passing a non-empty list to `Enum.into/2` | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
|
||||
[v1.8] | Passing a non-empty list to `:into` in `for` | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
|
||||
[v1.8] | `:seconds`, `:milliseconds`, etc. as time units | `:second`, `:millisecond`, etc. (v1.4)
|
||||
[v1.8] | `Inspect.Algebra.surround/3` | `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2` (v1.0)
|
||||
[v1.8] | `Inspect.Algebra.surround_many/6` | `Inspect.Algebra.container_doc/6` (v1.6)
|
||||
[v1.8] | `Kernel.ParallelCompiler.files/2` | `Kernel.ParallelCompiler.compile/2` (v1.6)
|
||||
[v1.8] | `Kernel.ParallelCompiler.files_to_path/2` | `Kernel.ParallelCompiler.compile_to_path/2` (v1.6)
|
||||
[v1.8] | `Kernel.ParallelRequire.files/2` | `Kernel.ParallelCompiler.require/2` (v1.6)
|
||||
[v1.8] | `System.cwd/0` and `System.cwd!/0` | `File.cwd/0` and `File.cwd!/0` (v1.0)
|
||||
[v1.8] | Returning `{:ok, contents}` or `:error` from `Mix.Compilers.Erlang.compile/6`'s callback | Return `{:ok, contents, warnings}` or `{:error, errors, warnings}` (v1.6)
|
||||
[v1.7] | `Code.get_docs/2` | `Code.fetch_docs/1` (v1.7)
|
||||
[v1.7] | Calling `super/1` on GenServer callbacks | Implenting the behaviour explicitly without calling `super/1` (v1.0)
|
||||
[v1.7] | `Enum.chunk/2`[`/3/4`](`Enum.chunk/4`) | `Enum.chunk_every/2`[`/3/4`](`Enum.chunk_every/4`) (v1.5)
|
||||
[v1.7] | `not left in right` | [`left not in right`](`Kernel.in/2`) (v1.5)
|
||||
[v1.7] | `Registry.start_link/3` | `Registry.start_link/1` (v1.5)
|
||||
[v1.7] | `Stream.chunk/2`[`/3/4`](`Stream.chunk/4`) | `Stream.chunk_every/2`[`/3/4`](`Stream.chunk_every/4`) (v1.5)
|
||||
[v1.6] | `Enum.partition/2` | `Enum.split_with/2` (v1.4)
|
||||
[v1.6] | `Keyword.replace/3` | `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
|
||||
[v1.6] | `Macro.unescape_tokens/1/2` | Use `Enum.map/2` to traverse over the arguments (v1.0)
|
||||
[v1.6] | `Module.add_doc/6` | `@doc` module attribute (v1.0)
|
||||
[v1.6] | `Map.replace/3` | `Map.fetch/2` + `Map.put/3` (v1.0)
|
||||
[v1.6] | `Range.range?/1` | Pattern match on `_.._` (v1.0)
|
||||
[v1.5] | `Atom.to_char_list/1` | `Atom.to_charlist/1` (v1.3)
|
||||
[v1.5] | `Enum.filter_map/3` | `Enum.filter/2` + `Enum.map/2` or [`for`](`Kernel.SpecialForms.for/1`) comprehensions (v1.0)
|
||||
[v1.5] | `Float.to_char_list/1` | `Float.to_charlist/1` (v1.3)
|
||||
[v1.5] | `GenEvent` module | `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)
|
||||
[v1.5] | `Integer.to_char_list/1/2` | `Integer.to_charlist/1` and `Integer.to_charlist/2` (v1.3)
|
||||
[v1.5] | `Kernel.to_char_list/1` | `Kernel.to_charlist/1` (v1.3)
|
||||
[v1.5] | `List.Chars.to_char_list/1` | `List.Chars.to_charlist/1` (v1.3)
|
||||
[v1.5] | `Stream.filter_map/3` | `Stream.filter/2` + `Stream.map/2` (v1.0)
|
||||
[v1.5] | `String.ljust/3` and `String.rjust/3` | Use `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding (v1.3)
|
||||
[v1.5] | `String.strip/1` and `String.strip/2` | `String.trim/1` and `String.trim/2` (v1.3)
|
||||
[v1.5] | `String.lstrip/1` and `String.rstrip/1` | `String.trim_leading/1` and `String.trim_trailing/1` (v1.3)
|
||||
[v1.5] | `String.lstrip/2` and `String.rstrip/2` | Use `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument (v1.3)
|
||||
[v1.5] | `String.to_char_list/1` | `String.to_charlist/1` (v1.3)
|
||||
[v1.5] | `()` to mean `nil` | `nil` (v1.0)
|
||||
[v1.5] | `char_list/0` type | `t:charlist/0` type (v1.3)
|
||||
[v1.5] | `:char_lists` key in `t:Inspect.Opts.t/0` type | `:charlists` key (v1.3)
|
||||
[v1.5] | `:as_char_lists` value in `t:Inspect.Opts.t/0` type | `:as_charlists` value (v1.3)
|
||||
[v1.5] | `@compile {:parse_transform, _}` in `Module` | *None*
|
||||
[v1.5] | EEx: `<%=` in middle and end expressions | Use `<%` (`<%=` is allowed only on start expressions) (v1.0)
|
||||
[v1.4] | `Access.key/1` | `Access.key/2` (v1.3)
|
||||
[v1.4] | `Behaviour` module | `@callback` module attribute (v1.0)
|
||||
[v1.4] | `Enum.uniq/2` | `Enum.uniq_by/2` (v1.2)
|
||||
[v1.4] | `Float.to_char_list/2` | `:erlang.float_to_list/2` (Erlang/OTP 17)
|
||||
[v1.4] | `Float.to_string/2` | `:erlang.float_to_binary/2` (Erlang/OTP 17)
|
||||
[v1.4] | `HashDict` module | `Map` (v1.2)
|
||||
[v1.4] | `HashSet` module | `MapSet` (v1.1)
|
||||
[v1.4] | Multi-letter aliases in `OptionParser` | Use single-letter aliases (v1.0)
|
||||
[v1.4] | `Set` module | `MapSet` (v1.1)
|
||||
[v1.4] | `Stream.uniq/2` | `Stream.uniq_by/2` (v1.2)
|
||||
[v1.4] | `IEx.Helpers.import_file/2` | `IEx.Helpers.import_file_if_available/1` (v1.3)
|
||||
[v1.4] | `Mix.Utils.camelize/1` | `Macro.camelize/1` (v1.2)
|
||||
[v1.4] | `Mix.Utils.underscore/1` | `Macro.underscore/1` (v1.2)
|
||||
[v1.4] | Variable used as function call | Use parentheses (v1.0)
|
||||
[v1.4] | Anonymous functions with no expression after `->` | Use an expression or explicitly return `nil` (v1.0)
|
||||
[v1.4] | Support for making private functions overridable | Use public functions (v1.0)
|
||||
[v1.3] | `Dict` module | `Keyword` (v1.0) or `Map` (v1.2)
|
||||
[v1.3] | `Keyword.size/1` | `Kernel.length/1` (v1.0)
|
||||
[v1.3] | `Map.size/1` | `Kernel.map_size/1` (v1.0)
|
||||
[v1.3] | `Set` behaviour | `MapSet` data structure (v1.1)
|
||||
[v1.3] | `String.valid_character?/1` | `String.valid?/1` (v1.0)
|
||||
[v1.3] | `Task.find/2` | Use direct message matching (v1.0)
|
||||
[v1.3] | `:append_first` option in `Kernel.defdelegate/2` | Define the function explicitly (v1.0)
|
||||
[v1.3] | `/r` option in `Regex` | `/U` (v1.1)
|
||||
[v1.3] | `\x{X*}` inside strings/sigils/charlists | `\uXXXX` or `\u{X*}` (v1.1)
|
||||
[v1.3] | Map/dictionary as 2nd argument in `Enum.group_by/3` | `Enum.reduce/3` (v1.0)
|
||||
[v1.3] | Non-map as 2nd argument in `URI.decode_query/2` | Use a map (v1.0)
|
||||
[v1.2] | `Dict` behaviour | `MapSet` data structure (v1.1)
|
||||
[v1.1] | `Access` protocol | `Access` behaviour (v1.1)
|
||||
[v1.1] | `as: true \| false` in `alias/2` and `require/2` | *None*
|
||||
[v1.1] | `?\xHEX` | `0xHEX` (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
|
||||
@@ -151,3 +158,4 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
|
||||
[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
|
||||
[v1.8]: https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.9]: https://github.com/elixir-lang/elixir/blob/v1.9/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
@@ -106,15 +106,11 @@ def my_function(number) when is_integer(number) and rem(number, 2) == 0 do
|
||||
end
|
||||
```
|
||||
|
||||
This would be repetitive to write every time we need this check, so, as mentioned at the beginning of this section, we can abstract this away using a macro. Remember that defining a function that performs this check wouldn't work because we can't use custom functions in guards. Our macro would look like this:
|
||||
This would be repetitive to write every time we need this check, so, as mentioned at the beginning of this section, we can abstract this away using a macro. Remember that defining a function that performs this check wouldn't work because we can't use custom functions in guards. Use `defguard` and `defguardp` to create guard macros. Here's an example:
|
||||
|
||||
```elixir
|
||||
defmodule MyInteger do
|
||||
defmacro is_even(number) do
|
||||
quote do
|
||||
is_integer(unquote(number)) and rem(unquote(number), 2) == 0
|
||||
end
|
||||
end
|
||||
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
|
||||
end
|
||||
```
|
||||
|
||||
@@ -128,13 +124,7 @@ 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
|
||||
```
|
||||
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.
|
||||
|
||||
## Multiple guards in the same clause
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Writing code is only the first of many steps to publish a package. We strongly r
|
||||
|
||||
* 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.
|
||||
* 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 License 2.0](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.
|
||||
|
||||
@@ -172,7 +172,7 @@ 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:
|
||||
The following code is clearer:
|
||||
|
||||
```elixir
|
||||
defmodule MyApp do
|
||||
@@ -184,7 +184,7 @@ The code above says we are only bringing in the functions from `MyLib` so we can
|
||||
|
||||
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`.
|
||||
While there are situations where `use SomeModule` is necessary, `use` should be skipped when all it does is to `import` or `alias` other modules. In a nutshell, `alias` should be preferred, as it is simpler and clearer than `import`, while `import` is simpler and clearer than `use`.
|
||||
|
||||
### Avoid macros
|
||||
|
||||
@@ -196,7 +196,7 @@ To quote [the official guide on Macros](https://elixir-lang.org/getting-started/
|
||||
>
|
||||
> 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`.
|
||||
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 `Logger.debug/2`, `Logger.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
|
||||
|
||||
@@ -256,24 +256,22 @@ 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
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
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},
|
||||
]
|
||||
def start(_type, _args) do
|
||||
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
|
||||
# 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
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user