Compare commits
367
Commits
v0.14.3
...
v1.0.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b4497fa6b | ||
|
|
612e124ec9 | ||
|
|
abd2b35738 | ||
|
|
2afb875099 | ||
|
|
88507f65f8 | ||
|
|
275d1f5494 | ||
|
|
e6420c8d13 | ||
|
|
2af00f4c33 | ||
|
|
0fe86e77c1 | ||
|
|
1ca451f779 | ||
|
|
ebe0c8cf8e | ||
|
|
de9feb26d5 | ||
|
|
b8528f6c8e | ||
|
|
c9fbbac391 | ||
|
|
98e1b4ccf8 | ||
|
|
ae19491620 | ||
|
|
64f3760b19 | ||
|
|
0b38bc050f | ||
|
|
9431338654 | ||
|
|
88be750a32 | ||
|
|
032c8b8291 | ||
|
|
2d90d8ac60 | ||
|
|
a8b2cb99ee | ||
|
|
68274311fb | ||
|
|
88f84f80b5 | ||
|
|
2bf898e8fe | ||
|
|
d45f451943 | ||
|
|
c0a075724f | ||
|
|
f1a39375c9 | ||
|
|
b9c1b22163 | ||
|
|
be75e3be17 | ||
|
|
9793caa1ef | ||
|
|
0792fe2c52 | ||
|
|
4408aa3450 | ||
|
|
e4ba8d5d21 | ||
|
|
689f14a867 | ||
|
|
aa5b414e8e | ||
|
|
07467a733b | ||
|
|
74391b793c | ||
|
|
f0b8bc1756 | ||
|
|
3e54b95bf2 | ||
|
|
313d26cb3b | ||
|
|
44b85be014 | ||
|
|
e344b1f9d8 | ||
|
|
f5f4205969 | ||
|
|
538c717ded | ||
|
|
8dd81e661a | ||
|
|
26b61b98f9 | ||
|
|
ab9450d92f | ||
|
|
a4a23b9a93 | ||
|
|
2f83ea5a7f | ||
|
|
7940d223ec | ||
|
|
c6fa951520 | ||
|
|
e5c8194a70 | ||
|
|
4a7e3dd11f | ||
|
|
638fccb79d | ||
|
|
e9d84ec43b | ||
|
|
bb35b9afb1 | ||
|
|
1496c04592 | ||
|
|
3e9725a5a8 | ||
|
|
1565ec7658 | ||
|
|
f1aa2f01c7 | ||
|
|
7284542116 | ||
|
|
4f17e8fd1c | ||
|
|
f536507b9d | ||
|
|
592300a0f0 | ||
|
|
28b1bd16de | ||
|
|
19fdd84f80 | ||
|
|
631e2a051a | ||
|
|
df0f7b5f95 | ||
|
|
a0dfc4160e | ||
|
|
ee8331023e | ||
|
|
a2ee71f5ab | ||
|
|
60163194e2 | ||
|
|
ba37682300 | ||
|
|
15265b9352 | ||
|
|
0a929310de | ||
|
|
a8670c1cfa | ||
|
|
ebd18d45fa | ||
|
|
ccbfec611a | ||
|
|
98a0999c2e | ||
|
|
e504142d3a | ||
|
|
c04e13169f | ||
|
|
96a4bff67b | ||
|
|
46de5ea462 | ||
|
|
22257b8342 | ||
|
|
a0be9fe272 | ||
|
|
1fd5a2a38e | ||
|
|
13aec18bd5 | ||
|
|
a85938d092 | ||
|
|
0d4af57abe | ||
|
|
e00db53112 | ||
|
|
b941618a10 | ||
|
|
d895c762a1 | ||
|
|
427c4b01c9 | ||
|
|
9d5a728cb8 | ||
|
|
1ed99d4ac7 | ||
|
|
ceb17fac7d | ||
|
|
c638f45542 | ||
|
|
c09905d537 | ||
|
|
117a428030 | ||
|
|
2ada7c40d7 | ||
|
|
cd162bf3d6 | ||
|
|
e6e71675ac | ||
|
|
c54e0caf6f | ||
|
|
b64552780e | ||
|
|
add8c66e82 | ||
|
|
aecbd2c16a | ||
|
|
32a649c1cd | ||
|
|
a1e657cb75 | ||
|
|
6bb4c4de03 | ||
|
|
ea5b56d434 | ||
|
|
a4082748cf | ||
|
|
4d763846c3 | ||
|
|
d56cc88f5f | ||
|
|
5e27b7b72b | ||
|
|
260d6d16ab | ||
|
|
1a82f126a4 | ||
|
|
3cf3eee2ea | ||
|
|
9e8a41b2d3 | ||
|
|
a54d9212b8 | ||
|
|
ee33608f75 | ||
|
|
6e91275782 | ||
|
|
c6f27541c1 | ||
|
|
a2662a4b51 | ||
|
|
1d29262949 | ||
|
|
1d655fa8f2 | ||
|
|
4bec785f4c | ||
|
|
e3273f731f | ||
|
|
a8564a7045 | ||
|
|
e2bac56556 | ||
|
|
a38e6b7a0d | ||
|
|
72199ca71d | ||
|
|
9420945f59 | ||
|
|
4ca1736f86 | ||
|
|
fd85ca443e | ||
|
|
187e86749e | ||
|
|
e04a7aef6a | ||
|
|
0b1c8c92c0 | ||
|
|
bf270d144f | ||
|
|
2c009d1ec3 | ||
|
|
08555f545c | ||
|
|
65d3af694b | ||
|
|
8aa43eaedd | ||
|
|
334c732691 | ||
|
|
24587110ad | ||
|
|
b98421fe51 | ||
|
|
752e3f8bc3 | ||
|
|
6c8e70b1f8 | ||
|
|
c6fbfd2a67 | ||
|
|
56ea6e52e4 | ||
|
|
4726e0af91 | ||
|
|
ee82679908 | ||
|
|
aedd5a938f | ||
|
|
2d9c3fb6f0 | ||
|
|
6f07f4af22 | ||
|
|
656bd640e6 | ||
|
|
bc2cb15a96 | ||
|
|
dbf205b311 | ||
|
|
0e0bb1c2a7 | ||
|
|
736406a9b3 | ||
|
|
886917753e | ||
|
|
f29889da99 | ||
|
|
aee33eac6d | ||
|
|
d8428ba1e6 | ||
|
|
1f725b0d2c | ||
|
|
38f895cc13 | ||
|
|
3fc07eff8e | ||
|
|
aa244c30fc | ||
|
|
f8647dae3e | ||
|
|
39fa2878ce | ||
|
|
5b5b95eb11 | ||
|
|
67caa3ebb3 | ||
|
|
d09cd173bf | ||
|
|
79fb40ab6a | ||
|
|
ae2cb6bc43 | ||
|
|
413b815ecc | ||
|
|
df89b5022e | ||
|
|
90729655ee | ||
|
|
171e9b3659 | ||
|
|
3e256fcc1e | ||
|
|
29ebbeda93 | ||
|
|
214d056911 | ||
|
|
9f065a77ca | ||
|
|
e70ac9768e | ||
|
|
ec6c98ef47 | ||
|
|
7d0f92e12a | ||
|
|
8bf6193ccc | ||
|
|
50f5dc76fb | ||
|
|
b9000999c9 | ||
|
|
52be06c314 | ||
|
|
3da560f10c | ||
|
|
f0f25300a0 | ||
|
|
c8a3a619af | ||
|
|
25f64051ba | ||
|
|
5efa6bb1c4 | ||
|
|
cb48f90f28 | ||
|
|
3ba93b1186 | ||
|
|
7babcd0ed2 | ||
|
|
b6c1f2b2e1 | ||
|
|
5f853f8e71 | ||
|
|
6cccf3ac22 | ||
|
|
5b9b186d4b | ||
|
|
57898bee03 | ||
|
|
6a6ba9f50d | ||
|
|
1e3135514f | ||
|
|
2f2a52a648 | ||
|
|
89fd8f305f | ||
|
|
353c043d8c | ||
|
|
54a40efeec | ||
|
|
f781795032 | ||
|
|
54ffb5768d | ||
|
|
8fc068821f | ||
|
|
1f91fd351e | ||
|
|
ab4399323f | ||
|
|
3b34a57bd2 | ||
|
|
62a469dc23 | ||
|
|
8b0c4127ef | ||
|
|
dc4b7c89ea | ||
|
|
b5febd991c | ||
|
|
f1323ade7c | ||
|
|
89561c321d | ||
|
|
d1b8657933 | ||
|
|
89d9cc89ca | ||
|
|
7b0cbb1632 | ||
|
|
3dc0e87c33 | ||
|
|
a19613cf19 | ||
|
|
a8e4fcdb6c | ||
|
|
a70ca83ba2 | ||
|
|
11c857a9a2 | ||
|
|
84b1f6546b | ||
|
|
c8c1757fdd | ||
|
|
9a162b5076 | ||
|
|
92d2f25048 | ||
|
|
a2ddb6abbb | ||
|
|
ac629c875b | ||
|
|
ab8ba93d1d | ||
|
|
9d62e674d7 | ||
|
|
58ca3401e4 | ||
|
|
a5926616a6 | ||
|
|
59910ae4bf | ||
|
|
69b47483c1 | ||
|
|
284e75d031 | ||
|
|
c1d63e4639 | ||
|
|
212b204af8 | ||
|
|
90f83e1b3d | ||
|
|
562af52d07 | ||
|
|
ed79d60cef | ||
|
|
42d378142b | ||
|
|
aeefd59b15 | ||
|
|
6db5091378 | ||
|
|
4e7ba9d598 | ||
|
|
60b66ac669 | ||
|
|
1fda82d9c2 | ||
|
|
55dbd617a6 | ||
|
|
841a4e0195 | ||
|
|
5d60728d4b | ||
|
|
9717a63604 | ||
|
|
fae20670ed | ||
|
|
6874eb7ce8 | ||
|
|
404e95b7ac | ||
|
|
30a5914a92 | ||
|
|
f97a36ff98 | ||
|
|
e68f617e2a | ||
|
|
3056e95ded | ||
|
|
9ba77e1ce6 | ||
|
|
84010d4467 | ||
|
|
a91aa70103 | ||
|
|
301c7fd122 | ||
|
|
8809ab2f5e | ||
|
|
71e441dcc2 | ||
|
|
f11197fd04 | ||
|
|
7ac771da67 | ||
|
|
a47751f4de | ||
|
|
c91217860c | ||
|
|
f69e9df983 | ||
|
|
2ee1d5daef | ||
|
|
d3dba63659 | ||
|
|
c016d74379 | ||
|
|
a6d887ee09 | ||
|
|
aa017a4dcb | ||
|
|
b7c7d7e72c | ||
|
|
d164a9b93f | ||
|
|
0105cf538e | ||
|
|
957e389fe7 | ||
|
|
88d4d38afa | ||
|
|
8b4b985894 | ||
|
|
986eb8bdb1 | ||
|
|
052eac333c | ||
|
|
acae645db1 | ||
|
|
7cb5f6ff3c | ||
|
|
0fe940a621 | ||
|
|
81ed03ffb2 | ||
|
|
3947f7455a | ||
|
|
406a2e6755 | ||
|
|
0b3fcb254a | ||
|
|
054843505a | ||
|
|
92016cd80e | ||
|
|
0c06e72f0c | ||
|
|
a722a4b33c | ||
|
|
4afb8a516c | ||
|
|
e9fc50bc6d | ||
|
|
0a5a980ab7 | ||
|
|
30505b7758 | ||
|
|
9f867f2ec8 | ||
|
|
5e2020804a | ||
|
|
b5cea26e57 | ||
|
|
03e6d3a5a2 | ||
|
|
89513f9d15 | ||
|
|
f20ef46e90 | ||
|
|
d7ebc46a2d | ||
|
|
bd6d4bcd6f | ||
|
|
4f8862cedd | ||
|
|
1afc5ec71b | ||
|
|
925ff6050a | ||
|
|
6ce97090f3 | ||
|
|
6538f33825 | ||
|
|
a1e4a2741f | ||
|
|
fe57a821d8 | ||
|
|
c42fe88dcc | ||
|
|
28d7fc35a6 | ||
|
|
9fa279897b | ||
|
|
6465a3a4af | ||
|
|
1582df8d70 | ||
|
|
8d9aec5a55 | ||
|
|
7f4df95922 | ||
|
|
98a5affa4a | ||
|
|
b720559d56 | ||
|
|
cd5cc91cd4 | ||
|
|
93d92d1f36 | ||
|
|
ce3674dd07 | ||
|
|
e4cfb62a74 | ||
|
|
bb51701614 | ||
|
|
6ffb248f03 | ||
|
|
50af8058ef | ||
|
|
b1d9e147b3 | ||
|
|
7ea91d2d68 | ||
|
|
c7640d3cf4 | ||
|
|
399240295b | ||
|
|
b965d8eb5d | ||
|
|
380bf1e279 | ||
|
|
6272b60d36 | ||
|
|
0c7699f21c | ||
|
|
1086a8455f | ||
|
|
d8e7578e41 | ||
|
|
2d0fbcc23c | ||
|
|
21d5d43ac5 | ||
|
|
027f90380b | ||
|
|
1a68f63525 | ||
|
|
798c53a4cb | ||
|
|
e3a89050f2 | ||
|
|
f176cb55c1 | ||
|
|
97f2e8744b | ||
|
|
c513459c10 | ||
|
|
fd6f1d47be | ||
|
|
6b8b5bfd01 | ||
|
|
e6e96165aa | ||
|
|
42875c0b72 | ||
|
|
b35e2cf82a | ||
|
|
be320880b2 | ||
|
|
fe1712d642 | ||
|
|
63d815912e | ||
|
|
cd4dc75c93 | ||
|
|
058b157b36 | ||
|
|
19c9941e7f | ||
|
|
7895ca0821 | ||
|
|
2b20bb424f |
@@ -7,3 +7,4 @@ notifications:
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0
|
||||
- 17.1
|
||||
|
||||
+111
@@ -1,5 +1,116 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.0-rc1 (2014-08-30)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add timeouts to tests (which is configurable via tags)
|
||||
* [GenEvent] Add support for `GenEvent.ack_notify/2`
|
||||
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
|
||||
* [Logger] Allow any value that implements `String.Chars` to be logger
|
||||
* [Logger] Allow `Logger.log/3` timeout to be configured
|
||||
* [Mix] Add `--force` option to `mix local.rebar`
|
||||
* [Mix] Fix `--no-color`/`--color` option for `mix test`
|
||||
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
|
||||
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
|
||||
* [Mix] Support protocol consolidation on escripts
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
|
||||
* [GenEvent] Do not allow a handler to be added more than once
|
||||
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
|
||||
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
|
||||
* [Mix] Do not pre-compile a Mix project if an alias was found
|
||||
* [Mix] Properly handle compilation errors in the Erlang compiler
|
||||
* [Mix] Always try to compile project if task cannot be found
|
||||
* [Mix] Purge in memory modules before consolidating
|
||||
* [Mix] Always compile a project if a task cannot be found
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
|
||||
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
|
||||
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
|
||||
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
|
||||
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
|
||||
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
|
||||
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
|
||||
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
|
||||
|
||||
## v0.15.1 (2014-08-10)
|
||||
|
||||
* Enhancements
|
||||
* [GenEvent] Support `:ack` mode for GenEvent streams
|
||||
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
|
||||
* [kernel] Print warnings when used ? with characters with escape codes
|
||||
* [Logger] Add SASL log forwarding option to Logger
|
||||
* [Logger] Add `$padlevel` to option Logger formatter
|
||||
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
|
||||
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
|
||||
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
|
||||
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
|
||||
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
|
||||
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
|
||||
* [Mix] Load `config/config.exs` inside escripts
|
||||
* [Mix] Store and check Elixir version requirement for generated archives
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
|
||||
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
|
||||
* [Kernel] Do not expand args for unknown functions/macros
|
||||
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
|
||||
* [Kernel] Do not raise conflicts on imports used from inside quotes
|
||||
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
|
||||
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
|
||||
* [Macro] Ensure bitstrings work with `Macro.escape/1`
|
||||
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
|
||||
* [Mix] Leave it up to the application to start the Logger after compilation
|
||||
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
|
||||
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
|
||||
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Having a function as collectable is deprecated
|
||||
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
|
||||
|
||||
## v0.15.0 (2014-08-02)
|
||||
|
||||
* Enhancements
|
||||
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
|
||||
* [IEx] Support `--werl` call on Windows
|
||||
* [Logger] Add `Logger`
|
||||
* [Map] Add `Map.from_struct/1`
|
||||
* [Mix] Allow `--app` flag to be passed to `mix new`
|
||||
* [Mix] Support lowercase `http(s)_proxy` environment variables
|
||||
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
|
||||
* [String] `String.slice/2` and `String.slice/3` have been optimized
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
|
||||
* [IEx] Ensure remote shells can be started with `--remsh`
|
||||
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
|
||||
* [Kernel] Ensure private functions are not exported
|
||||
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
|
||||
|
||||
* Deprecations
|
||||
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
|
||||
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
|
||||
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
|
||||
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
|
||||
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
|
||||
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
|
||||
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
|
||||
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
|
||||
## v0.14.3 (2014-07-12)
|
||||
|
||||
* Enhancements
|
||||
|
||||
+2
-22
@@ -10,7 +10,7 @@ Use the issues tracker for:
|
||||
* [bug reports](#bugs-reports)
|
||||
* [submitting pull requests](#pull-requests)
|
||||
|
||||
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
|
||||
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
|
||||
|
||||
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
We do our best to keep the issue tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
@@ -107,26 +107,6 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
### Building on Windows
|
||||
|
||||
There are a few extra steps you'll need to take for contributing from Windows.
|
||||
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
|
||||
you're all set. Specifically, here's what you need to do to get up and running:
|
||||
|
||||
1. Install [Git](http://www.git-scm.com/download/win),
|
||||
[Erlang](http://www.erlang.org/download.html), and the
|
||||
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
|
||||
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
|
||||
msys-grep packages.
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
to your "Path" environment variable . (This is under Control Panel > System
|
||||
and Security > System > Advanced system settings > Environment Variables >
|
||||
System variables)
|
||||
|
||||
You can now work in the Command Prompt similar to how you would on other OS'es,
|
||||
except for some things (like creating symlinks) you'll need to run the Command
|
||||
Prompt as an Administrator.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
|
||||
|
||||
@@ -68,10 +68,10 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile EEx fully
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
|
||||
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
@@ -86,10 +86,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ echo "Embedding the Unicode database... (this may take a while)"
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,logger,Logger))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
$(eval $(call APP_TEMPLATE,mix,Mix))
|
||||
$(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
@@ -133,6 +134,7 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call DOCS,Logger,logger,Logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -140,7 +142,7 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
@@ -164,7 +166,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
|
||||
$(Q) mkdir -p $(TEST_EBIN)
|
||||
$(Q) $(ERLC) -o $(TEST_EBIN) $<
|
||||
|
||||
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@@ -173,7 +175,11 @@ test_doc_test: compile
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
$(Q) if [ "$(OS)" = "Windows_NT" ]; then \
|
||||
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
else \
|
||||
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
fi
|
||||
|
||||
#==> Dialyzer tasks
|
||||
|
||||
@@ -191,4 +197,4 @@ build_plt: clean_plt $(PLT)
|
||||
|
||||
dialyze: compile $(PLT)
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
|
||||
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
|
||||
|
||||
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
$ cd elixir
|
||||
$ make clean test
|
||||
|
||||
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
|
||||
|
||||
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
+10
-10
@@ -2,30 +2,30 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1. Remove `-dev` extension from VERSION
|
||||
1. Remove all `-dev` extension from versions (see below for all files)
|
||||
|
||||
2. Ensure CHANGELOG is updated and timestamp
|
||||
2. Ensure CHANGELOG is updated and add current date
|
||||
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5. Push master and tags
|
||||
5. Push master and the new tag
|
||||
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases, name it `Precompiled.zip`
|
||||
|
||||
8. Merge master into stable branch and push it
|
||||
8. Fast-forward merge master into stable branch with `git merge master --ff` and push it
|
||||
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10. `make release_docs` once again and push it to `elixir-lang.org`
|
||||
10. `make release_docs` once again and push it to `elixir-lang/docs`
|
||||
|
||||
11. Also update `release` file in `elixir-lang.org`
|
||||
11. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
* VERSION (make sure there is no newline in this file)
|
||||
* CHANGELOG.md
|
||||
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
|
||||
|
||||
+8
-1
@@ -80,7 +80,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
if [ "$OS" != "Windows_NT" ]; then
|
||||
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
|
||||
fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
|
||||
+17
-6
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
if "%1"=="" goto documentation
|
||||
if "%1"=="--help" goto documentation
|
||||
if "%1"=="-h" goto documentation
|
||||
@@ -22,12 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
|
||||
echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --werl Uses Erlang's Windows shell GUI
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
goto :EOF
|
||||
goto end
|
||||
|
||||
:parseopts
|
||||
|
||||
@@ -46,6 +48,9 @@ set beforeExtra=
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Designates which mode / Elixir component to run as
|
||||
set runMode="elixir"
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -59,7 +64,8 @@ if "%par%"=="""" (
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
IF "%par%"==""--werl"" (Set useWerl=1)
|
||||
IF "%par%"==""+iex"" (Set runMode="iex")
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF """"=="%par:-e=%" (shift)
|
||||
@@ -88,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
IF NOT %runMode% == "iex" (
|
||||
set beforeExtra=-s elixir start_cli %beforeExtra%
|
||||
)
|
||||
IF %useWerl% EQU 1 (
|
||||
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
:end
|
||||
endlocal
|
||||
|
||||
@@ -38,4 +38,6 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env elixir
|
||||
# Reminder: apply any changes in this file to bin\mix.bat
|
||||
Mix.start
|
||||
Mix.CLI.main
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dps0\mix" %*
|
||||
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
|
||||
|
||||
+10
-14
@@ -1,27 +1,23 @@
|
||||
# Initialize with path to mix.bat relative to caller's working directory
|
||||
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
foreach ($arg in $args)
|
||||
for ($i = 0; $i -lt $args.length; $i++)
|
||||
{
|
||||
$toCmd += ' '
|
||||
|
||||
if ($arg -is [array])
|
||||
if ($args[$i] -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($i = 0; $i -lt $arg.length; $i++)
|
||||
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
|
||||
{
|
||||
$toCmd += $arg[$i]
|
||||
if ($i -ne ($arg.length - 1))
|
||||
{
|
||||
$toCmd += ', '
|
||||
}
|
||||
$newArgs += ($args[$i][$j] + ',')
|
||||
}
|
||||
$newArgs += $args[$i][-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$toCmd += $arg
|
||||
$newArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
cmd /c $toCmd
|
||||
& $mixBatPath $newArgs
|
||||
|
||||
+3
-4
@@ -34,10 +34,9 @@ defmodule EEx do
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling
|
||||
from a string.
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -1,63 +1,3 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, transform(expr))
|
||||
end
|
||||
|
||||
defp transform({a, b, c}) do
|
||||
{transform(a), b, transform(c)}
|
||||
end
|
||||
|
||||
defp transform({a, b}) do
|
||||
{transform(a), transform(b)}
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
@@ -50,7 +50,8 @@ defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
doctest EEx.Engine
|
||||
doctest EEx.SmartEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
|
||||
+89
-18
@@ -68,22 +68,22 @@ defmodule Agent do
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
provides two APIs, one that works with anonymous functions and another
|
||||
that expects explicit module, function and arguments.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them with
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
crash. That said, if you plan to run in distributed environments, agents
|
||||
should likely be avoided.
|
||||
In a distributed setup with multiple nodes, the API that accepts anonymous
|
||||
functions only works if the caller (client) and the agent have the same
|
||||
version of the caller module.
|
||||
|
||||
Note, however, that agents work fine if you want to perform hot code
|
||||
swapping, as it keeps both the old and new versions of a given module.
|
||||
We detail how to do hot code swapping with agents in the next section.
|
||||
Keep in mind this issue also shows up when performing "rolling upgrades"
|
||||
with agents. By rolling upgrades we mean the following situation: you wish
|
||||
to deploy a new version of your software by *shutting down* some of your
|
||||
nodes and replacing them with nodes running a new version of the software.
|
||||
In this setup, part of your environment will have one version of a given
|
||||
module and the other part another version (the newer one) of the same module.
|
||||
|
||||
The best solution is to simply use the explicit module, function and arguments
|
||||
APIs when working with distributed agents.
|
||||
|
||||
## Hot code swapping
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Agent do
|
||||
@type state :: term
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process.
|
||||
Starts an agent linked to the current process with the given function.
|
||||
|
||||
This is often used to start the agent as part of a supervision tree.
|
||||
|
||||
@@ -149,6 +149,18 @@ defmodule Agent do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process with the given module
|
||||
function and arguments.
|
||||
|
||||
Same as `start_link/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start_link(module, atom, [any], GenServer.options) :: on_start
|
||||
def start_link(module, fun, args, options \\ []) do
|
||||
GenServer.start_link(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent process without links (outside of a supervision tree).
|
||||
|
||||
@@ -160,7 +172,18 @@ defmodule Agent do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the agent value and executes the given function.
|
||||
Starts an agent with the given module function and arguments.
|
||||
|
||||
Similar to `start/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start(module, atom, [any], GenServer.options) :: on_start
|
||||
def start(module, fun, args, options \\ []) do
|
||||
GenServer.start(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The result of the function invocation is
|
||||
@@ -173,6 +196,18 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
Same as `get/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get(agent, module, atom, [term], timeout) :: any
|
||||
def get(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
@@ -188,6 +223,18 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get_and_update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
Same as `get_and_update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get_and_update(agent, module, atom, [term], timeout) :: any
|
||||
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
@@ -197,11 +244,23 @@ defmodule Agent do
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec update(agent, (state -> state)) :: :ok
|
||||
@spec update(agent, (state -> state), timeout) :: :ok
|
||||
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
Same as `update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec update(agent, module, atom, [term], timeout) :: :ok
|
||||
def update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
@@ -213,7 +272,19 @@ defmodule Agent do
|
||||
"""
|
||||
@spec cast(agent, (state -> state)) :: :ok
|
||||
def cast(agent, fun) when is_function(fun, 1) do
|
||||
GenServer.cast(agent, fun)
|
||||
GenServer.cast(agent, {:cast, fun})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
Same as `cast/2` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec cast(agent, module, atom, [term]) :: :ok
|
||||
def cast(agent, module, fun, args) do
|
||||
GenServer.cast(agent, {:cast, {module, fun, args}})
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -4,20 +4,20 @@ defmodule Agent.Server do
|
||||
use GenServer
|
||||
|
||||
def init(fun) do
|
||||
{:ok, fun.()}
|
||||
{:ok, run(fun, [])}
|
||||
end
|
||||
|
||||
def handle_call({:get, fun}, _from, state) do
|
||||
{:reply, fun.(state), state}
|
||||
{:reply, run(fun, [state]), state}
|
||||
end
|
||||
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
{reply, state} = fun.(state)
|
||||
{reply, state} = run(fun, [state])
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:update, fun}, _from, state) do
|
||||
{:reply, :ok, fun.(state)}
|
||||
{:reply, :ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
@@ -28,16 +28,16 @@ defmodule Agent.Server do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
def handle_cast(fun, state) when is_function(fun, 1) do
|
||||
{:noreply, fun.(state)}
|
||||
def handle_cast({:cast, fun}, state) do
|
||||
{:noreply, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
def code_change(_old, state, { m, f, a }) do
|
||||
{:ok, apply(m, f, [state|a])}
|
||||
def code_change(_old, state, fun) do
|
||||
{:ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
@@ -50,4 +50,7 @@ defmodule Agent.Server do
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
|
||||
defp run(fun, extra), do: apply(fun, extra)
|
||||
end
|
||||
|
||||
@@ -322,7 +322,7 @@ defmodule Application do
|
||||
|
||||
@doc """
|
||||
Formats the error reason returned by `start/2`,
|
||||
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
|
||||
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t
|
||||
|
||||
@@ -499,7 +499,7 @@ defmodule Base do
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
@@ -514,7 +514,7 @@ defmodule Base do
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
@@ -534,7 +534,7 @@ defmodule Base do
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
@@ -561,7 +561,7 @@ defmodule Base do
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
|
||||
@@ -8,12 +8,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
@@ -32,10 +26,6 @@ defprotocol Collectable do
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
@@ -92,16 +82,6 @@ defimpl Collectable, for: BitString do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
|
||||
+78
-78
@@ -288,8 +288,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(dict))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -304,8 +304,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(dict))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -319,8 +319,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(dict)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -334,10 +334,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(dict, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(dict, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -352,12 +352,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
iex> Dict.get(dict, :b)
|
||||
nil
|
||||
iex> Dict.get(d, :b, 3)
|
||||
iex> Dict.get(dict, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -371,10 +371,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(dict, :a)
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(d, :b)
|
||||
iex> Dict.fetch(dict, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@@ -389,8 +389,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -405,9 +405,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -421,9 +421,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put_new(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -438,13 +438,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.delete(dict, :a)
|
||||
iex> Dict.get(dict, :a)
|
||||
nil
|
||||
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
iex> dict = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(dict, :a) == dict
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -454,10 +454,10 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the dict `b` into dict `a`.
|
||||
Merges the dict `dict2` into dict `dict1`.
|
||||
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
If one of the `dict2` entries already exists in `dict1`, the
|
||||
functions in entries in `dict2` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
@@ -466,18 +466,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2)
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -502,18 +502,18 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@@ -528,9 +528,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -546,9 +546,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -566,19 +566,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
|
||||
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c])
|
||||
iex> {Dict.to_list(d1), Dict.to_list(d2)}
|
||||
iex> dict = Enum.into([], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
|
||||
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
|
||||
{[], []}
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
|
||||
{[a: 1, b: 2], []}
|
||||
|
||||
"""
|
||||
@@ -593,14 +593,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[b: 2]
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -616,12 +616,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.take(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[a: 1]
|
||||
iex> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Dict.take(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -645,14 +645,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
true
|
||||
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = []
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
false
|
||||
|
||||
"""
|
||||
|
||||
+19
-16
@@ -270,8 +270,13 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `default` if index is out of bounds.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
@@ -336,7 +341,7 @@ defmodule Enum do
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
if is_nil(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
||||
@@ -564,12 +569,17 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `{:ok, element}` if found, otherwise `:error`.
|
||||
|
||||
A negative index can be passed, which means the collection is
|
||||
enumerated once and the index is counted from the end (i.e.
|
||||
`-1` fetches the last element).
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
@@ -609,9 +619,14 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Raises `OutOfBoundsError` if the given position
|
||||
is outside the range of the collection.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch!([2, 4, 6], 0)
|
||||
@@ -1390,7 +1405,7 @@ defmodule Enum do
|
||||
result. For example, one could do the following to set a seed
|
||||
dynamically:
|
||||
|
||||
:random.seed(:erlang.now)
|
||||
:random.seed(:os.timestamp)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1783,25 +1798,13 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
@doc false
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
into(collection, apply(Collectable, :empty, [collection]), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -5,12 +5,12 @@ defmodule Exception do
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
last throw/error/exit that occurred in the current process.
|
||||
|
||||
Do not rely on the particular format returned by the `format`
|
||||
functions in this module. They may be changed in future releases
|
||||
in order to better suit Elixir's tool chain. In other words,
|
||||
by using the functions in this module it is guarantee you will
|
||||
by using the functions in this module it is guaranteed you will
|
||||
format exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@@ -613,12 +613,17 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
defexception [module: nil, function: nil, arity: nil, self: false]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
def message(%{function: function, module: module, arity: arity, self: self}) do
|
||||
if function do
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
suffix = if self or is_nil(module) or :code.is_loaded(module) do
|
||||
""
|
||||
else
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
"undefined function: #{formatted}" <> suffix
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
@@ -772,8 +777,9 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
stacktrace = stacktrace || :erlang.get_stacktrace
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
@@ -800,4 +806,7 @@ defmodule ErlangError do
|
||||
defp from_stacktrace(_) do
|
||||
{nil, nil, nil}
|
||||
end
|
||||
|
||||
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
|
||||
defp from_self(_), do: false
|
||||
end
|
||||
|
||||
@@ -126,12 +126,12 @@ defmodule File do
|
||||
Typical error reasons are:
|
||||
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
directories of `path`
|
||||
* `:eexist` - there is already a file or directory named `path`
|
||||
* `:enoent` - a component of `path` does not exist
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
on some platforms, `:enoent` is returned instead
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
@@ -679,7 +679,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp change_mode_windows(path, file_info) do
|
||||
case File.chmod(path, (elem(file_info, 7) + 0200)) do
|
||||
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
|
||||
:ok -> F.delete(path)
|
||||
{:error, _reason} = error -> error
|
||||
end
|
||||
|
||||
+695
-287
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,181 @@
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
|
||||
"""
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, _state) do
|
||||
exit({:bad_event, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _state) do
|
||||
exit({:bad_call, msg})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
{pid, ref, mon_ref} =
|
||||
try do
|
||||
pid = whereis(manager)
|
||||
ref = Process.monitor(pid)
|
||||
{:ok, msg_ref} = :gen.call(pid, self(), {:add_process_handler, self(), true}, :infinity)
|
||||
{pid, msg_ref, ref}
|
||||
catch
|
||||
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
|
||||
{pid, ref, mon_ref}
|
||||
end
|
||||
|
||||
defp whereis(pid) when is_pid(pid), do: pid
|
||||
defp whereis(atom) when is_atom(atom), do: Process.whereis(atom) || exit(:noproc)
|
||||
defp whereis({:global, name}), do: :global.whereis_name(name)
|
||||
defp whereis({:via, module, name}), do: module.whereis_name(name)
|
||||
defp whereis({atom, node}) do
|
||||
case :rpc.call(node, :erlang, :whereis, [atom]) do
|
||||
:undefined -> exit(:noproc)
|
||||
{:badrpc, :nodedown} -> exit({:nodedown, node})
|
||||
pid when is_pid(pid) -> pid
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
|
||||
receive do
|
||||
# The handler was removed. Stop iteration, resolve the
|
||||
# event later. We need to demonitor now, otherwise DOWN
|
||||
# appears with higher priority in the shutdown process.
|
||||
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, _reason} = event ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
send(self(), event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# The manager died. Stop iteration, resolve the event later.
|
||||
{:DOWN, ^mon_ref, _, _, _} = event ->
|
||||
send(self(), event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
|
||||
# Got a sync event.
|
||||
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
|
||||
{[{:sync, pid, ref, event}], acc}
|
||||
|
||||
# Got an ack event.
|
||||
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
|
||||
{[{:ack, pid, ref, event}], acc}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, we know the handler was already
|
||||
# removed, so we don't trigger a request for doing so.
|
||||
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, ref, _} = acc) do
|
||||
Task.start(fn -> GenEvent.remove_handler(pid, {GenEvent.Stream, ref}, :shutdown) end)
|
||||
stop(stream, {:removed, acc})
|
||||
end
|
||||
|
||||
defp wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
receive do
|
||||
{_from, {^pid, ^ref}, {notify, _event}} when notify in [:ack_notify, :sync_notify] ->
|
||||
send pid, {ref, :done}
|
||||
wait_for_handler_removal(pid, ref, mon_ref)
|
||||
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, reason} when reason in [:normal, :shutdown] ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {:notify, _event}} ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@ defmodule GenServer do
|
||||
## Example
|
||||
|
||||
The GenServer behaviour abstracts the common client-server interaction.
|
||||
Developer are only required to implement the callbacks and functionality they are
|
||||
Developers are only required to implement the callbacks and functionality they are
|
||||
interested in.
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
@@ -349,18 +349,57 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
try do
|
||||
:gen.call(server, :"$gen_call", request, timeout)
|
||||
catch
|
||||
:exit, reason ->
|
||||
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
|
||||
else
|
||||
{:ok, res} -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
This function returns `:ok` immediately, regardless of
|
||||
whether the destination node or server does exists, unless
|
||||
the server is specified as an atom.
|
||||
|
||||
`handle_cast/2` will be called on the server to handle
|
||||
the request. In case the server is a node which is not
|
||||
yet connected to the caller one, the call is going to
|
||||
block until a connection happens. This is different than
|
||||
the behaviour in OTP's `:gen_server` where the message
|
||||
would be sent by another process, which could cause
|
||||
messages to arrive out of order.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
def cast(server, request)
|
||||
|
||||
def cast({:global, name}, request) do
|
||||
try do
|
||||
:global.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({:via, mod, name}, request) do
|
||||
try do
|
||||
mod.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -371,8 +410,19 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
defp cast_msg(req) do
|
||||
{:"$gen_cast", req}
|
||||
end
|
||||
|
||||
defp do_send(dest, msg) do
|
||||
send(dest, msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+39
-39
@@ -212,29 +212,28 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
@doc false
|
||||
# Also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
def escape_char(char), do: hexify(char)
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
def escape_char(0) do
|
||||
<<?\\, ?0>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x10000 do
|
||||
def escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::size(8)>>
|
||||
<<?\\, ?x, to_hex(a), to_hex(b)>>
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x1000000 do
|
||||
def escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?a+c-10
|
||||
defp to_hex(c) when c in 10..15, do: ?A+c-10
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
@@ -272,25 +271,6 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
printed in keywords syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
@@ -298,9 +278,9 @@ defimpl Inspect, for: List do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &keyword/2)
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -347,7 +327,7 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -358,14 +338,14 @@ defimpl Inspect, for: Map do
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
defp traverse_fun(list) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
&Inspect.List.keyword/2
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
&to_map/2
|
||||
end
|
||||
end
|
||||
|
||||
@@ -378,8 +358,28 @@ defimpl Inspect, for: Map do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(thing, _opts) do
|
||||
Integer.to_string(thing)
|
||||
def inspect(thing, %Inspect.Opts{base: base}) do
|
||||
Integer.to_string(thing, base_to_value(base))
|
||||
|> prepend_prefix(base)
|
||||
end
|
||||
|
||||
defp base_to_value(base) do
|
||||
case base do
|
||||
:binary -> 2
|
||||
:decimal -> 10
|
||||
:octal -> 8
|
||||
:hex -> 16
|
||||
end
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, :decimal), do: value
|
||||
defp prepend_prefix(value, base) do
|
||||
prefix = case base do
|
||||
:binary -> "0b"
|
||||
:octal -> "0o"
|
||||
:hex -> "0x"
|
||||
end
|
||||
prefix <> value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,12 @@ defmodule Inspect.Opts do
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false.
|
||||
|
||||
* `:width` - defaults to the 80 characters.
|
||||
* `:width` - defaults to the 80 characters, used when pretty is true or
|
||||
when printing to IO devices.
|
||||
|
||||
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
|
||||
to :decimal
|
||||
|
||||
"""
|
||||
|
||||
defstruct structs: true,
|
||||
@@ -37,6 +42,7 @@ defmodule Inspect.Opts do
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
width: 80,
|
||||
base: :decimal,
|
||||
pretty: false
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
@@ -45,6 +51,7 @@ defmodule Inspect.Opts do
|
||||
char_lists: :infer | :as_lists | :as_char_lists,
|
||||
limit: pos_integer,
|
||||
width: pos_integer | :infinity,
|
||||
base: :decimal | :binary | :hex | :octal,
|
||||
pretty: boolean}
|
||||
end
|
||||
|
||||
@@ -66,8 +73,8 @@ defmodule Inspect.Algebra do
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"foo"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["foo"]
|
||||
|
||||
The functions `nest/2`, `space/2` and `line/2` help you put the
|
||||
document together into a rigid structure. However, the document
|
||||
@@ -77,15 +84,15 @@ defmodule Inspect.Algebra do
|
||||
render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
Finally, this module also contains Elixir related functions, a bit
|
||||
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
|
||||
@@ -190,9 +197,10 @@ defmodule Inspect.Algebra do
|
||||
try do
|
||||
Process.put(:inspect_trap, true)
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
formatted = IO.iodata_to_binary(format(res, :infinity))
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
|
||||
after
|
||||
Process.delete(:inspect_trap)
|
||||
end
|
||||
@@ -224,9 +232,9 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"TastelessArtosis"
|
||||
iex> doc = Inspect.Algebra.concat "hello", "world"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["hello", "world"]
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons
|
||||
@@ -250,8 +258,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"hello\n world"
|
||||
iex> Inspect.Algebra.format(doc, 5)
|
||||
["hello", "\n ", "world"]
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@@ -275,15 +283,15 @@ defmodule Inspect.Algebra do
|
||||
Let's glue two docs together with a break and then render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break
|
||||
@@ -326,10 +334,10 @@ defmodule Inspect.Algebra do
|
||||
...> "B"
|
||||
...> )
|
||||
...> ))
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hello, A B"
|
||||
iex> Inspect.Algebra.pretty(doc, 6)
|
||||
"Hello,\nA B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hello,", " ", "A", " ", "B"]
|
||||
iex> Inspect.Algebra.format(doc, 6)
|
||||
["Hello,", "\n", "A", " ", "B"]
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
@@ -343,8 +351,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes Wadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", " ", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons
|
||||
@@ -356,8 +364,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes\nWadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", "\n", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons
|
||||
@@ -373,8 +381,8 @@ defmodule Inspect.Algebra do
|
||||
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
|
||||
...> Inspect.Algebra.concat [x, "!", y]
|
||||
...> end)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"A!B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["A", "!", "B"]
|
||||
|
||||
"""
|
||||
@spec folddoc([t], ((t, t) -> t)) :: t
|
||||
@@ -394,8 +402,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
|
||||
iex> Inspect.Algebra.pretty(doc, 3)
|
||||
"[a\n b]"
|
||||
iex> Inspect.Algebra.format(doc, 3)
|
||||
["[", "a", "\n ", "b", "]"]
|
||||
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
@@ -404,55 +412,63 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
Maps and glues a collection of items.
|
||||
|
||||
It uses the given left and right as surrounding and a separator for
|
||||
each item. A limit can be passed which, once reached, stops gluing
|
||||
and outputs "..." instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
|
||||
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
|
||||
end
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun, sep) do
|
||||
surround(left, surround_many(docs, limit, fun, sep), right)
|
||||
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun, _sep) do
|
||||
defp do_surround_many(_, 0, _opts, _fun, _sep) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun, _sep) do
|
||||
fun.(h)
|
||||
defp do_surround_many([h], limit, opts, fun, _sep) do
|
||||
fun.(h, %{opts | limit: limit})
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), sep),
|
||||
surround_many(t, decrement(limit), fun, sep)
|
||||
concat(fun.(h, %{opts | limit: limit}), sep),
|
||||
do_surround_many(t, limit, opts, fun, sep)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun, _sep) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, _sep) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
|
||||
fun.(t, %{opts | limit: limit})
|
||||
)
|
||||
end
|
||||
|
||||
@@ -460,50 +476,45 @@ defmodule Inspect.Algebra do
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
The pretty printing function.
|
||||
The formatting function.
|
||||
|
||||
Takes the maximum width and a document to print as its arguments
|
||||
and returns the string representation of the best layout for the
|
||||
and returns an IO data representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
|
||||
render(sdoc)
|
||||
@spec format(t, non_neg_integer | :infinity) :: binary
|
||||
def format(d, w) do
|
||||
format(w, 0, [{0, default_mode(w), doc_group(d)}])
|
||||
end
|
||||
|
||||
defp default_mode(:infinity), do: :flat
|
||||
defp default_mode(_), do: :break
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
defp fits?(w, _) when w < 0, do: false
|
||||
defp fits?(_, []), do: true
|
||||
defp fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
defp format(_, _, []), do: []
|
||||
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
@@ -515,10 +526,4 @@ defmodule Inspect.Algebra do
|
||||
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
IO.iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,23 +5,35 @@ defmodule Integer do
|
||||
|
||||
import Bitwise
|
||||
|
||||
@doc false
|
||||
defmacro odd?(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro even?(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
|
||||
+11
-14
@@ -179,17 +179,13 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It sets by default pretty printing to true and returns
|
||||
the item itself.
|
||||
|
||||
Note this function does not use the IO device width
|
||||
because some IO devices does not implement the
|
||||
appropriate functions. Setting the width must be done
|
||||
explicitly by passing the `:width` option.
|
||||
It enables pretty printing by default with width of
|
||||
80 characters. Th width can be changed by explicitly
|
||||
passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
IO.inspect Process.list, width: 40
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
@@ -202,8 +198,9 @@ defmodule IO do
|
||||
"""
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
opts = struct(Inspect.Opts, opts)
|
||||
iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
|
||||
puts device, iodata
|
||||
item
|
||||
end
|
||||
|
||||
@@ -397,11 +394,11 @@ defmodule IO do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -409,11 +406,11 @@ defmodule IO do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+49
-68
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(unquote(Atom.to_char_list(name))) do
|
||||
defp format_sequence(unquote(name)) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@doc """
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
@typep ansicode :: atom()
|
||||
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
|
||||
@type ansidata :: ansilist() | ansicode() | binary()
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be displayed as intended. This is checked by sending a message to
|
||||
the group leader. In case the group leader does not support the message,
|
||||
it will likely lead to a timeout (and a slow down on execution time).
|
||||
@doc """
|
||||
Checks if ANSI coloring is supported and enabled on this machine.
|
||||
|
||||
This function simply reads the configuration value for
|
||||
`:ansi_enabled` in the `:elixir` application. The value is by
|
||||
default false unless Elixir can detect during startup that
|
||||
both `stdout` and `stderr` are terminals.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
!match?({:win32, _}, :os.type()) and
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
@spec enabled? :: boolean
|
||||
def enabled? do
|
||||
Application.get_env(:elixir, :ansi_enabled, false)
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
defp escape_sequence(other) do
|
||||
defp format_sequence(other) do
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
It will also append a `%{reset}` to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/2`.
|
||||
It will also append an `IO.ANSI.reset` to the chardata when a conversion is
|
||||
performed. If you don't want this behaviour, use `format_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
|
||||
[[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
def format(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, :maybe)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
|
||||
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, false)
|
||||
end
|
||||
|
||||
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
sequences =
|
||||
buffer
|
||||
|> Enum.reverse()
|
||||
|> :string.tokens(',')
|
||||
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|
||||
|> Enum.reverse()
|
||||
|
||||
if emit and sequences != [] do
|
||||
do_escape(t, emit, true, nil, sequences ++ acc)
|
||||
else
|
||||
do_escape(t, emit, emitted, nil, acc)
|
||||
end
|
||||
defp do_format([term | rest], rem, acc, emit, append_reset) do
|
||||
do_format(term, [rest | rem], acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
do_escape(t, emit, emitted, [h|buffer], acc)
|
||||
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
|
||||
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = IO.iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, "missing } for escape fragment #{buffer}"
|
||||
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
|
||||
do_format([], rem, acc, false, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, [], acc)
|
||||
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
|
||||
do_format([], rem, [acc | [term]], emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, nil, [h|acc])
|
||||
defp do_format([], [next | rest], acc, emit, append_reset) do
|
||||
do_format(next, rest, acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
defp do_format([], [], acc, true, true) do
|
||||
[acc | IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp do_format([], [], acc, _emit, _append_reset) do
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
+219
-116
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def default_options do
|
||||
[enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline",
|
||||
width: 80]
|
||||
[enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan, :bright],
|
||||
doc_headings: [:yellow, :bright],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow, :bright],
|
||||
doc_underline: [:underline],
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a set of options
|
||||
In addition to the printing string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
@@ -56,52 +59,63 @@ defmodule IO.ANSI.Docs do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", options)
|
||||
|> process([], "", options)
|
||||
end
|
||||
|
||||
defp process([], _indent, _options), do: nil
|
||||
defp process([], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, options) do
|
||||
defp process(["# " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h1(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, options) do
|
||||
defp process(["## " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h2(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, options) do
|
||||
defp process(["### " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h3(String.strip(heading), indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, options) do
|
||||
process(rest, indent, options)
|
||||
defp process(["" | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, options) do
|
||||
defp process([" " <> line | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process_code(rest, [line], indent, options)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, options)
|
||||
defp process(all=[line | rest], text, indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0, :infinity)
|
||||
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
|
||||
write_text(text, indent, options)
|
||||
process_table(all, indent, options)
|
||||
else
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list("• ", item, rest, count, indent, options)
|
||||
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
|
||||
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
|
||||
_ ->
|
||||
process(rest, [stripped | text], indent, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_spaces(" " <> line, acc) do
|
||||
strip_spaces(line, acc + 1)
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{rest, acc}
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, options) do
|
||||
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp write_h2(heading, options) do
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, options) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, options) do
|
||||
IO.write indent <> "• "
|
||||
{contents, rest, done} = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, options)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, options)
|
||||
defp process_list(entry, line, rest, count, indent, options) do
|
||||
# The first list always win some extra padding
|
||||
if indent == "", do: entry = " " <> entry
|
||||
new_indent = indent <> String.duplicate(" ", String.length(entry))
|
||||
|
||||
{contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
|
||||
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
|
||||
|
||||
if done, do: newline_after_block()
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
#
|
||||
# * Continuation of the list
|
||||
# * A nested list
|
||||
# * The end of the list
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> {Enum.reverse(acc), [line|rest], false}
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
defp process_list_next([line | rest], count, max, acc) do
|
||||
{stripped, next_count} = strip_spaces(line, 0, max)
|
||||
case process_list_next_kind(stripped, rest, count, next_count) do
|
||||
:next -> process_list_next(rest, count, max, [stripped | acc])
|
||||
:done -> {Enum.reverse(acc), [line | rest], true}
|
||||
:list -> {Enum.reverse(acc), [line | rest], false}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{Enum.reverse(acc), rest, false}
|
||||
defp process_list_next([], _count, _max, acc) do
|
||||
{Enum.reverse(acc), [], true}
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
process_list_next(rest, count, true, [""|acc])
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{Enum.reverse(acc), rest, done}
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
defp list_next(line, 0), do: chop(line, 2)
|
||||
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
|
||||
defp list_next(line, _acc), do: line
|
||||
|
||||
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
|
||||
defp chop(line, _acc), do: line
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
process(doc, indent, options)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, options)
|
||||
process_list(item, rest, count, indent, options)
|
||||
defp process_list_next_kind(stripped, rest, count, next_count) do
|
||||
case {stripped, rest} do
|
||||
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
|
||||
:list
|
||||
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{"", [" " <> _ | _]} ->
|
||||
:next
|
||||
{"", _} ->
|
||||
:done
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, options)
|
||||
:next
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, options) do
|
||||
process_text(rest, [line | para], indent, from_list, options)
|
||||
## Text
|
||||
|
||||
defp write_text(text, indent, options) do
|
||||
case Enum.reverse(text) do
|
||||
[:no_wrap|rest] -> write_text(rest, indent, options, true)
|
||||
rest -> write_text(rest, indent, options, false)
|
||||
end
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, options) do
|
||||
defp write_text([], _indent, _options, _no_wrap) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, options, no_wrap) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
unless no_wrap, do: newline_after_block()
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
@@ -204,28 +208,124 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, options) do
|
||||
defp process_code(["", " " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line, "" | code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, options) do
|
||||
defp process_code([" " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line|code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, options) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Tables
|
||||
|
||||
defp process_table(lines, indent, options) do
|
||||
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
|
||||
table_lines(table, options)
|
||||
newline_after_block
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp table_lines(lines, options) do
|
||||
lines = Enum.map(lines, &split_into_columns(&1, options))
|
||||
count = Enum.map(lines, &length/1) |> Enum.max
|
||||
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
|
||||
|
||||
widths = for line <- lines, do:
|
||||
(for {_col, length} <- line, do: length)
|
||||
|
||||
col_widths = Enum.reduce(widths,
|
||||
List.duplicate(0, count),
|
||||
&max_column_widths/2)
|
||||
|
||||
render_table(lines, col_widths, options)
|
||||
end
|
||||
|
||||
defp split_into_columns(line, options) do
|
||||
line
|
||||
|> String.strip(?|)
|
||||
|> String.strip()
|
||||
|> String.split(~r/\s\|\s/)
|
||||
|> Enum.map(&render_column(&1, options))
|
||||
end
|
||||
|
||||
defp render_column(col, options) do
|
||||
col = col
|
||||
|> String.replace(~r/\\ \|/x, "|")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
{col, length_without_escape(col, 0)}
|
||||
end
|
||||
|
||||
defp pad_to_number_of_columns(cols, col_count),
|
||||
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
|
||||
|
||||
defp max_column_widths(cols, widths),
|
||||
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
|
||||
|
||||
# If second line is heading separator, use the heading style on the first
|
||||
defp render_table([first, second | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
if table_header?(second) do
|
||||
draw_table_row(combined, options, :heading)
|
||||
render_table(rest, widths, options)
|
||||
else
|
||||
draw_table_row(combined, options)
|
||||
render_table([second | rest], widths, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_table([first | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
draw_table_row(combined, options)
|
||||
render_table(rest, widths, options)
|
||||
end
|
||||
|
||||
defp render_table([], _, _),
|
||||
do: nil
|
||||
|
||||
defp table_header?(row), do:
|
||||
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
|
||||
|
||||
defp draw_table_row(cols_and_widths, options, heading \\ false) do
|
||||
columns =
|
||||
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
|
||||
col <> String.duplicate(" ", width - length)
|
||||
end)
|
||||
|
||||
if heading do
|
||||
write(:doc_table_heading, columns, options)
|
||||
else
|
||||
IO.puts columns
|
||||
end
|
||||
end
|
||||
|
||||
defp is_table_line?(line) do
|
||||
Regex.match?(~r'''
|
||||
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
||||
|
|
||||
(\s \| \s)
|
||||
'''x, line)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max,
|
||||
do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max),
|
||||
do: {rest, acc}
|
||||
|
||||
defp write(style, string, options) do
|
||||
IO.puts color(style, options) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
IO.puts [color(style, options), string, IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
@@ -270,7 +370,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
true -> Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ -> text
|
||||
end
|
||||
end
|
||||
|
||||
@@ -363,14 +461,19 @@ defmodule IO.ANSI.Docs do
|
||||
[color_for(h, options)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
defp color_for(mark, colors) do
|
||||
case mark do
|
||||
"`" -> color(:doc_inline_code, colors)
|
||||
"_" -> color(:doc_underline, colors)
|
||||
"*" -> color(:doc_bold, colors)
|
||||
"**" -> color(:doc_bold, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
IO.ANSI.format_fragment(color, colors[:enabled])
|
||||
end
|
||||
|
||||
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule IO.Stream do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
|
||||
+117
-173
@@ -885,11 +885,6 @@ defmodule Kernel do
|
||||
:erlang.--(left, right)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def left xor right do
|
||||
:erlang.xor(left, right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Boolean not. Argument must be a boolean.
|
||||
|
||||
@@ -1419,14 +1414,26 @@ defmodule Kernel do
|
||||
iex> inspect [1, 2, 3, 4, 5], limit: 3
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> inspect("josé" <> <<0>>)
|
||||
"<<106, 111, 115, 195, 169, 0>>"
|
||||
iex> inspect("olá" <> <<0>>)
|
||||
"<<111, 108, 195, 161, 0>>"
|
||||
|
||||
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
|
||||
"\"josé\\000\""
|
||||
iex> inspect("olá" <> <<0>>, binaries: :as_strings)
|
||||
"\"olá\\0\""
|
||||
|
||||
iex> inspect("josé", binaries: :as_binaries)
|
||||
"<<106, 111, 115, 195, 169>>"
|
||||
iex> inspect("olá", binaries: :as_binaries)
|
||||
"<<111, 108, 195, 161>>"
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect(100, base: :octal)
|
||||
"0o144"
|
||||
|
||||
iex> inspect(100, base: :hex)
|
||||
"0x64"
|
||||
|
||||
Note that the inspect protocol does not necessarily return a valid
|
||||
representation of an Elixir term. In such cases, the inspected result
|
||||
@@ -1443,7 +1450,9 @@ defmodule Kernel do
|
||||
true -> opts.width
|
||||
false -> :infinity
|
||||
end
|
||||
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
IO.iodata_to_binary(
|
||||
Inspect.Algebra.format(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1462,18 +1471,18 @@ defmodule Kernel do
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "jose"
|
||||
defstruct name: "john"
|
||||
end
|
||||
|
||||
struct(User)
|
||||
#=> %User{name: "jose"}
|
||||
#=> %User{name: "john"}
|
||||
|
||||
opts = [name: "eric"]
|
||||
opts = [name: "meg"]
|
||||
user = struct(User, opts)
|
||||
#=> %User{name: "eric"}
|
||||
#=> %User{name: "meg"}
|
||||
|
||||
struct(user, unknown: "value")
|
||||
#=> %User{name: "eric"}
|
||||
#=> %User{name: "meg"}
|
||||
|
||||
"""
|
||||
@spec struct(module | map, Enum.t) :: map
|
||||
@@ -1513,14 +1522,14 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_in(users, ["josé", :age])
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["john", :age])
|
||||
27
|
||||
|
||||
In case any of entries in the middle returns `nil`, `nil` will be returned
|
||||
as per the Access protocol:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["unknown", :age])
|
||||
nil
|
||||
|
||||
@@ -1528,7 +1537,7 @@ defmodule Kernel do
|
||||
In the example below, we use a function to get all the maps
|
||||
inside a list:
|
||||
|
||||
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
|
||||
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
|
||||
iex> all = fn :get, data, next -> Enum.map(data, next) end
|
||||
iex> get_in(users, [all, :age])
|
||||
[27, 23]
|
||||
@@ -1565,9 +1574,9 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users, ["josé", :age], 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users, ["john", :age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`,
|
||||
an error will be raised when trying to access it next.
|
||||
@@ -1587,9 +1596,9 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users, ["josé", :age], &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users, ["john", :age], &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`,
|
||||
an error will be raised when trying to access it next.
|
||||
@@ -1624,20 +1633,20 @@ defmodule Kernel do
|
||||
update it at the same time. For example, it could be used to increase
|
||||
the age of a user by one and return the previous age in one pass:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
|
||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
|
||||
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
|
||||
|
||||
When one of the keys is a function, the function is invoked.
|
||||
In the example below, we use a function to get and increment all
|
||||
ages inside a list:
|
||||
|
||||
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
|
||||
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
|
||||
iex> all = fn :get_and_update, data, next ->
|
||||
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
|
||||
...> end
|
||||
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
|
||||
{[27, 23], [%{name: "josé", age: 28}, %{name: "eric", age: 24}]}
|
||||
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
|
||||
|
||||
If the previous value before invoking the function is nil,
|
||||
the function *will* receive `nil` as a value and must handle it
|
||||
@@ -1675,13 +1684,13 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users["josé"][:age], 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"][:age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users["josé"].age, 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"].age, 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
"""
|
||||
defmacro put_in(path, value) do
|
||||
@@ -1708,13 +1717,13 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users["josé"][:age], &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users["john"][:age], &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users["josé"].age, &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users["john"].age, &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
"""
|
||||
defmacro update_in(path, fun) do
|
||||
@@ -1740,9 +1749,9 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_and_update_in(users["josé"].age, &{&1, &1 + 1})
|
||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
|
||||
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
|
||||
|
||||
## Paths
|
||||
|
||||
@@ -1757,15 +1766,15 @@ defmodule Kernel do
|
||||
|
||||
Here are some valid paths:
|
||||
|
||||
users["josé"][:age]
|
||||
users["josé"].age
|
||||
User.all["josé"].age
|
||||
all_users()["josé"].age
|
||||
users["john"][:age]
|
||||
users["john"].age
|
||||
User.all["john"].age
|
||||
all_users()["john"].age
|
||||
|
||||
Here are some invalid ones:
|
||||
|
||||
# Does a remote call after the initial value
|
||||
users["josé"].do_something(arg1, arg2)
|
||||
users["john"].do_something(arg1, arg2)
|
||||
|
||||
# Does not access any field
|
||||
users
|
||||
@@ -1786,7 +1795,7 @@ defmodule Kernel do
|
||||
defp nest_get_and_update_in(h, [{:access, key}|t], fun) do
|
||||
quote do
|
||||
Access.get_and_update(
|
||||
case(unquote(h), do: (nil -> %{}; o -> o)),
|
||||
unquote(h),
|
||||
unquote(key),
|
||||
unquote(nest_get_and_update_in(t, fun))
|
||||
)
|
||||
@@ -1866,26 +1875,31 @@ defmodule Kernel do
|
||||
quote do: List.Chars.to_char_list(unquote(arg))
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro nil?(x) do
|
||||
quote do: unquote(x) == nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is nil or not.
|
||||
Allowed in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> nil?(1)
|
||||
iex> is_nil(1)
|
||||
false
|
||||
|
||||
iex> nil?(nil)
|
||||
iex> is_nil(nil)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro nil?(x) do
|
||||
defmacro is_nil(x) do
|
||||
quote do: unquote(x) == nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
A convenient macro that checks if the right side matches
|
||||
the left side. The left side is allowed to be a match pattern.
|
||||
A convenience macro that checks if the right side (an expression)
|
||||
matches the left side (a pattern).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2004,7 +2018,7 @@ defmodule Kernel do
|
||||
defp do_at([arg], name, function?, env) do
|
||||
case function? do
|
||||
true ->
|
||||
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function"
|
||||
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
|
||||
false ->
|
||||
case name do
|
||||
:behavior ->
|
||||
@@ -2025,7 +2039,14 @@ defmodule Kernel do
|
||||
case function? do
|
||||
true ->
|
||||
attr = Module.get_attribute(env.module, name, stack)
|
||||
:erlang.element(1, :elixir_quote.escape(attr, false))
|
||||
try do
|
||||
:elixir_quote.escape(attr, false)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "cannot inject attribute @#{name} into function/macro because " <> Exception.message(e)
|
||||
else
|
||||
{val, _} -> val
|
||||
end
|
||||
false ->
|
||||
escaped = case stack do
|
||||
[] -> []
|
||||
@@ -2057,8 +2078,9 @@ defmodule Kernel do
|
||||
defp typespec(_), do: false
|
||||
|
||||
@doc """
|
||||
Returns the binding as a keyword list where the variable name
|
||||
is the key and the variable value is the value.
|
||||
Returns the binding for the given context as a keyword list.
|
||||
|
||||
The variable name is the key and the variable value is the value.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2069,59 +2091,18 @@ defmodule Kernel do
|
||||
iex> binding()
|
||||
[x: 2]
|
||||
|
||||
"""
|
||||
defmacro binding() do
|
||||
do_binding(nil, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of atoms at compilation time and returns the
|
||||
binding of the given variables as a keyword list where the
|
||||
variable name is the key and the variable value is the value.
|
||||
|
||||
In case a variable in the list does not exist in the binding,
|
||||
it is not included in the returned result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> x = 1
|
||||
iex> binding([:x, :y])
|
||||
[x: 1]
|
||||
|
||||
"""
|
||||
defmacro binding(list) when is_list(list) do
|
||||
do_binding(list, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
defmacro binding(context) when is_atom(context) do
|
||||
do_binding(nil, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of atoms at compilation time and returns the
|
||||
binding of the given variables in the given context as a keyword
|
||||
list where the variable name is the key and the variable value
|
||||
is the value.
|
||||
|
||||
In case a variable in the list does not exist in the binding,
|
||||
it is not included in the returned result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> var!(x, :foo) = 1
|
||||
iex> binding([:x, :y])
|
||||
iex> binding(:foo)
|
||||
[]
|
||||
iex> binding([:x, :y], :foo)
|
||||
iex> var!(x, :foo) = 1
|
||||
1
|
||||
iex> binding(:foo)
|
||||
[x: 1]
|
||||
|
||||
"""
|
||||
defmacro binding(list, context) when is_list(list) and is_atom(context) do
|
||||
do_binding(list, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
defp do_binding(list, context, vars, in_match) do
|
||||
for {v, c} <- vars, c == context, list == nil or :lists.member(v, list) do
|
||||
{v, wrap_binding(in_match, {v, [], c})}
|
||||
defmacro binding(context \\ nil) do
|
||||
in_match? = Macro.Env.in_match?(__CALLER__)
|
||||
for {v, c} <- __CALLER__.vars, c == context do
|
||||
{v, wrap_binding(in_match?, {v, [], c})}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2917,7 +2898,7 @@ defmodule Kernel do
|
||||
is used:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "José", age: 25
|
||||
defstruct name: "john", age: 25
|
||||
@type t :: %User{name: String.t, age: integer}
|
||||
end
|
||||
|
||||
@@ -2932,76 +2913,39 @@ defmodule Kernel do
|
||||
structure is public should use `@type`.
|
||||
"""
|
||||
defmacro defstruct(fields) do
|
||||
{fields, types} = split_fields_and_types(fields)
|
||||
|
||||
types =
|
||||
case types do
|
||||
true ->
|
||||
stacktrace = Exception.format_stacktrace(Macro.Env.stacktrace(__CALLER__))
|
||||
IO.write :stderr, "warning: passing types to struct fields with :: is deprecated, " <>
|
||||
"please define a type explicitly instead\n#{stacktrace}"
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: %{__struct__: __MODULE__}
|
||||
end
|
||||
quote bind_quoted: [fields: fields] do
|
||||
fields = :lists.map(fn
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
|
||||
else
|
||||
_ -> {key, val}
|
||||
end
|
||||
false ->
|
||||
:ok
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
other ->
|
||||
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
|
||||
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
|
||||
|
||||
case Module.get_attribute(__MODULE__, :derive) do
|
||||
[] -> :ok
|
||||
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
fields =
|
||||
quote bind_quoted: [fields: fields] do
|
||||
fields = :lists.map(fn
|
||||
{key, _} = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> {key, nil}
|
||||
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
|
||||
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
|
||||
|
||||
case Module.get_attribute(__MODULE__, :derive) do
|
||||
[] ->
|
||||
:ok
|
||||
derive ->
|
||||
Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
@spec __struct__() :: %__MODULE__{}
|
||||
def __struct__() do
|
||||
@struct
|
||||
end
|
||||
@spec __struct__() :: %__MODULE__{}
|
||||
def __struct__() do
|
||||
@struct
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(fields)
|
||||
unquote(types)
|
||||
fields
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types(kv) do
|
||||
case Keyword.keyword?(kv) do
|
||||
true -> split_fields_and_types(kv, [], false)
|
||||
false -> {kv, false}
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, {:::, _, [default, _]}}|t], fields, _types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], true)
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], types)
|
||||
end
|
||||
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], types)
|
||||
end
|
||||
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), types}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Defines an exception.
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ defmodule Kernel.CLI do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule Kernel.ErrorHandler do
|
||||
{:error, _} ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, {:waiting, module, self(), ref, module}
|
||||
send parent, {:waiting, :module, self(), ref, module}
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{^ref, :ready} -> :ok
|
||||
|
||||
@@ -18,14 +18,16 @@ defmodule Kernel.LexicalTracker do
|
||||
Returns all remotes linked to in this lexical scope.
|
||||
"""
|
||||
def remotes(arg) do
|
||||
# If the module is compiled from a function, its lexical
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the destination the lexical scope is meant to
|
||||
compile to.
|
||||
"""
|
||||
def dest(arg) do
|
||||
:gen_server.call(to_pid(arg), :dest, @timeout)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
def start_link(dest) do
|
||||
:gen_server.start_link(__MODULE__, dest, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
||||
def init([]) do
|
||||
{:ok, :ets.new(:lexical, [:protected])}
|
||||
def init(dest) do
|
||||
{:ok, {:ets.new(:lexical, [:protected]), dest}}
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{:reply, d, d}
|
||||
@doc false
|
||||
def handle_call(:ets, _from, {d, dest}) do
|
||||
{:reply, d, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, d) do
|
||||
{:stop, {:bad_call, request}, d}
|
||||
def handle_call(:dest, _from, {d, dest}) do
|
||||
{:reply, dest, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, d) do
|
||||
def handle_call(request, _from, {d, dest}) do
|
||||
{:stop, {:bad_call, request}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, {d, dest}) do
|
||||
add_module(d, module)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:import_dispatch, module}, d) do
|
||||
def handle_cast({:import_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:alias_dispatch, module}, d) do
|
||||
def handle_cast({:alias_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, module, line, warn}, d) do
|
||||
def handle_cast({:add_import, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_alias, module, line, warn}, d) do
|
||||
def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{:stop, :normal, d}
|
||||
def handle_cast(:stop, {d, dest}) do
|
||||
{:stop, :normal, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(msg, d) do
|
||||
{:stop, {:bad_cast, msg}, d}
|
||||
def handle_cast(msg, {d, dest}) do
|
||||
{:stop, {:bad_cast, msg}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{:noreply, d}
|
||||
@doc false
|
||||
def handle_info(_msg, {d, dest}) do
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{:ok, d}
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
@@ -23,32 +23,36 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
* `:dest` - the destination directory for the beam files. When using `files/2`,
|
||||
this information is only used to properly annotate the beam files before
|
||||
they are loaded into memory. If you want a file to actually be writen to
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
def files(files, options \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
@@ -59,21 +63,21 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
@@ -90,7 +94,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
catch
|
||||
@@ -99,31 +103,31 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
spawn_compilers(t, original, output, options, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
@@ -172,11 +176,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{name: "josé", age: 27}
|
||||
%{name: "john", age: 27}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,7 +122,7 @@ defmodule Kernel.SpecialForms do
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "josé", age: 27
|
||||
defstruct name: "john", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follows:
|
||||
@@ -132,13 +132,13 @@ defmodule Kernel.SpecialForms do
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
%User{} == %{__struct__: User, name: "john", age: 27}
|
||||
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
%User{full_name: "john doe"}
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
@@ -343,28 +343,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
a remote call regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
@@ -1112,7 +1093,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
Imagine the situation you have a variable `value` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
@@ -1174,10 +1155,10 @@ defmodule Kernel.SpecialForms do
|
||||
Note generators can also be used to filter as it removes any value
|
||||
that doesn't match the left side of `<-`:
|
||||
|
||||
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
|
||||
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOSE", "ERIC"]
|
||||
["JOHN", "MEG"]
|
||||
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
@@ -1327,22 +1308,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1. The head element of aliases can be any term.
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
4. When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
@@ -122,37 +122,38 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0x10ffff`
|
||||
`number` | `integer` | `float`
|
||||
`char_list` | [char]
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom` | `tuple`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` \| `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0x10ffff`
|
||||
`number` | `integer` \| `float`
|
||||
`char_list` | `[char]`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` \| `binary`
|
||||
`iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
|
||||
`module` | `atom` \| `tuple`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` \| `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
@@ -507,9 +508,9 @@ defmodule Kernel.Typespec do
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, nil}, _]}),
|
||||
def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}),
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
@@ -782,9 +783,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
<<"_", c :: binary-size(1), rest :: binary>> ->
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
<<c :: binary-size(1), rest :: binary>> ->
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
@@ -997,7 +998,7 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
@@ -1005,6 +1006,10 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
|
||||
+45
-32
@@ -9,19 +9,19 @@ defmodule Keyword do
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
For example, `Keyword.get/3` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
a given key and `delete_first` deletes just one of the existing
|
||||
particular, `Enum.into/2` allows creating new keywords without
|
||||
removing duplicated keys, `get_values/2` returns all values for
|
||||
a given key and `delete_first/2` deletes just one of the existing
|
||||
entries.
|
||||
|
||||
Since a keyword list is simply a list, all the operations defined
|
||||
in `Enum` and `List` can also be applied.
|
||||
in `Enum` and `List` can be applied.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -34,7 +34,7 @@ defmodule Keyword do
|
||||
@type t(value) :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not.
|
||||
Checks if the given argument is a keyword list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
@@ -56,7 +56,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
Unlike `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -76,7 +76,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
Unlike `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -96,7 +96,7 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, return default value (`nil` if no default value).
|
||||
If `key` does not exist, return the default value (`nil` if no default value).
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
@@ -124,6 +124,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
@@ -144,8 +145,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific `key`. If `key` does not exist,
|
||||
a `KeyError` is raised.
|
||||
Fetches the value for specific `key`.
|
||||
|
||||
If `key` does not exist, a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -184,8 +186,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys from the keyword list. Duplicated
|
||||
keys appear duplicated in the final list of keys.
|
||||
Returns all keys from the keyword list.
|
||||
|
||||
Duplicated keys appear duplicated in the final list of keys.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -217,6 +220,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a `key` with `value`.
|
||||
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -238,8 +242,9 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
Use `delete_first/2` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
## Examples
|
||||
@@ -261,6 +266,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -319,7 +325,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two keywords are equal. I.e. they contain
|
||||
Checks if two keywords are equal.
|
||||
|
||||
Two keywords are considered to be equal if they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
@@ -334,8 +342,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the one given as second argument wins.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the one given in the second argument wins.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -350,8 +359,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the given function is invoked to solve conflicts.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the given function is invoked to solve conflicts.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -392,10 +402,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, raises `KeyError`.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -425,10 +436,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, inserts the given `initial` value.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -455,8 +467,9 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate keyword list. Returns a tuple with the new list and the old list
|
||||
with removed keys.
|
||||
separate keyword list.
|
||||
|
||||
Returns a tuple with the new list and the old list with removed keys.
|
||||
|
||||
Keys for which there are no entires in the keyword list are ignored.
|
||||
|
||||
@@ -508,7 +521,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
Drops the given keys from the keyword list.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
@@ -531,7 +544,7 @@ defmodule Keyword do
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
|
||||
All duplicated entries are removed. See `pop_first/3` for
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
@@ -558,10 +571,10 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular ocurrence
|
||||
list as well as the keyword list without that particular occurrence
|
||||
of `key`.
|
||||
|
||||
Duplicated entries are not removed.
|
||||
Duplicated keys are not removed.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+44
-15
@@ -16,7 +16,7 @@ defmodule Macro do
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in,
|
||||
:and, :or, :when, :in,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -35,7 +35,7 @@ defmodule Macro do
|
||||
::: -> {:right, 60}
|
||||
:| -> {:right, 70}
|
||||
:= -> {:right, 90}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:||, :|||, :or] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
@@ -305,6 +305,40 @@ defmodule Macro do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the given expressions are valid quoted expressions.
|
||||
|
||||
Check the `type:Macro.t` for the specification of a valid
|
||||
quoted expression.
|
||||
"""
|
||||
@spec validate(term) :: :ok | {:error, term}
|
||||
def validate(expr) do
|
||||
find_invalid(expr) || :ok
|
||||
end
|
||||
|
||||
defp find_invalid({left, right}), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid(list) when is_list(list), do:
|
||||
Enum.find_value(list, &find_invalid/1)
|
||||
|
||||
defp find_invalid(pid) when is_pid(pid), do: nil
|
||||
defp find_invalid(atom) when is_atom(atom), do: nil
|
||||
defp find_invalid(num) when is_number(num), do: nil
|
||||
defp find_invalid(bin) when is_binary(bin), do: nil
|
||||
|
||||
defp find_invalid(fun) when is_function(fun) do
|
||||
unless :erlang.fun_info(fun, :env) == {:env, []} and
|
||||
:erlang.fun_info(fun, :type) == {:type, :external} do
|
||||
{:error, fun}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_invalid(other), do: {:error, other}
|
||||
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
@@ -312,9 +346,9 @@ defmodule Macro do
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
|
||||
are also supported via `\xNN` and `\x{NN...}` syntax.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
@@ -345,6 +379,7 @@ defmodule Macro do
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?0), do: ?0
|
||||
def unescape_map(?a), do: ?\a
|
||||
def unescape_map(?b), do: ?\b
|
||||
def unescape_map(?d), do: ?\d
|
||||
@@ -355,20 +390,14 @@ defmodule Macro do
|
||||
def unescape_map(?s), do: ?\s
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(?x), do: true
|
||||
def unescape_map(e), do: e
|
||||
|
||||
If the `unescape_map` function returns `false`. The char is
|
||||
not escaped and `\` is kept in the char list.
|
||||
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns `false` for `?0`.
|
||||
|
||||
## Hex
|
||||
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns `false` for `?x`.
|
||||
Hexadecimals will be escaped if the map function returns `true`
|
||||
for `?x`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -908,7 +937,7 @@ defmodule Macro do
|
||||
be expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
`expand_once/2` for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
|
||||
@@ -105,9 +105,9 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(env.module) ->
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
|
||||
@@ -38,5 +38,32 @@ defmodule Map do
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a struct to map.
|
||||
|
||||
It accepts the struct module or a struct itself and
|
||||
simply removes the `__struct__` field from the struct.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct [:name]
|
||||
end
|
||||
|
||||
Map.from_struct(User)
|
||||
#=> %{name: nil}
|
||||
|
||||
Map.from_struct(%User{name: "john"})
|
||||
#=> %{name: "john"}
|
||||
|
||||
"""
|
||||
def from_struct(struct) when is_atom(struct) do
|
||||
:maps.remove(:__struct__, struct.__struct__)
|
||||
end
|
||||
|
||||
def from_struct(%{__struct__: _} = struct) do
|
||||
:maps.remove(:__struct__, struct)
|
||||
end
|
||||
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
end
|
||||
|
||||
@@ -85,6 +85,9 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@@ -476,19 +479,6 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an anonymous function from the given module, function
|
||||
and arity. The module and function are not verified to exist.
|
||||
|
||||
iex> fun = Module.function(Kernel, :is_atom, 1)
|
||||
iex> fun.(:hello)
|
||||
true
|
||||
|
||||
"""
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
@@ -530,7 +520,7 @@ defmodule Module do
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
if(is_nil(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
end
|
||||
@@ -904,7 +894,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
|
||||
@@ -92,8 +92,7 @@ defmodule Module.LocalsTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
:gen_server.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
@@ -242,6 +241,7 @@ defmodule Module.LocalsTracker do
|
||||
{:ok, {d, []}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call({:cache_env, env}, _from, {d, cache}) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
@@ -258,10 +258,9 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_call({:yank, local}, _from, {d, _} = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{:reply, {in_vertices, out_vertices}, state}
|
||||
:digraph.del_edges(d, :digraph.out_edges(d, local))
|
||||
{:reply, {[], out_vertices}, state}
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, {d, _} = state) do
|
||||
@@ -272,6 +271,7 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -299,9 +299,7 @@ defmodule Module.LocalsTracker do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:reattach, kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
|
||||
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh do
|
||||
:digraph.add_vertex(d, from)
|
||||
replace_edge!(d, from, tuple)
|
||||
@@ -323,10 +321,12 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@@ -231,6 +231,36 @@ defmodule OptionParser do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a key-value enumerable and convert it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.to_argv([foo_bar: "baz"])
|
||||
["--foo-bar", "baz"]
|
||||
|
||||
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
|
||||
["--bool", "--no-bool"]
|
||||
|
||||
"""
|
||||
@spec to_argv(Enumerable.t) :: argv
|
||||
def to_argv(enum) do
|
||||
Enum.flat_map(enum, fn
|
||||
{_key, nil} -> []
|
||||
{key, true} -> [to_switch(key)]
|
||||
{key, false} -> [to_switch(key, "--no-")]
|
||||
{key, value} -> [to_switch(key), to_string(value)]
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_switch(key, prefix \\ "--") when is_atom(key) do
|
||||
prefix <> String.replace(Atom.to_string(key), "_", "-")
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
|
||||
+116
-74
@@ -60,40 +60,70 @@ defmodule Path do
|
||||
def absname(path, relative_to) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
cond do
|
||||
path == "/" ->
|
||||
path
|
||||
:binary.last(path) == ?/ ->
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
true ->
|
||||
path
|
||||
end
|
||||
:relative -> absname_join(relative_to, path)
|
||||
:absolute -> absname_join([path])
|
||||
:volumerelative ->
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
|
||||
## Absolute path on current drive
|
||||
# Absolute path on current drive
|
||||
defp absname_vr(["/"|rest], [volume|_], _relative),
|
||||
do: join([volume|rest])
|
||||
do: absname_join([volume|rest])
|
||||
|
||||
## Relative to current directory on current drive.
|
||||
# Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
do: absname(join(rest), relative)
|
||||
do: absname(absname_join(rest), relative)
|
||||
|
||||
## Relative to current directory on another drive.
|
||||
# Relative to current directory on another drive.
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
absname(absname_join(name), cwd)
|
||||
end
|
||||
|
||||
# Joins a list
|
||||
defp absname_join([name1, name2|rest]), do:
|
||||
absname_join([absname_join(name1, name2)|rest])
|
||||
defp absname_join([name]), do:
|
||||
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
# Joins two paths
|
||||
defp absname_join(left, right),
|
||||
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
|
||||
defp do_absname_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_absname_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_absname_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, relativename, result, os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_absname_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp reverse_maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp reverse_maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp reverse_maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Converts the path to an absolute one and expands
|
||||
any `.` and `..` characters and a leading `~`.
|
||||
@@ -106,7 +136,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
normalize absname(expand_home(path), System.cwd!)
|
||||
expand_dot absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -133,7 +163,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -157,10 +187,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec type(t) :: :absolute | :relative | :volumerelative
|
||||
def type(name) when is_list(name) or is_binary(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(0)
|
||||
pathtype(name, major_os_type) |> elem(0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -182,10 +209,20 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative(t) :: binary
|
||||
def relative(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1) |> IO.chardata_to_string
|
||||
relative(name, major_os_type())
|
||||
end
|
||||
|
||||
defp relative(name, os_type) do
|
||||
pathtype(name, os_type)
|
||||
|> elem(1)
|
||||
|> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp pathtype(name, os_type) do
|
||||
case os_type do
|
||||
:win32 -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
@@ -390,7 +427,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more path components joined by the path separator.
|
||||
Joins a list of strings.
|
||||
|
||||
This function should be used to convert a list of strings to a path.
|
||||
Note that any trailing slash is removed on join.
|
||||
@@ -411,11 +448,14 @@ defmodule Path do
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]), do:
|
||||
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
name
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
|
||||
The right path will always be expanded to its relative format
|
||||
and any trailing slash is removed on join.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.join("foo", "bar")
|
||||
@@ -423,40 +463,25 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
def join(left, right) do
|
||||
left = IO.chardata_to_string(left)
|
||||
os_type = major_os_type()
|
||||
do_join(left, right, os_type) |> remove_dirsep(os_type)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
defp do_join("", right, os_type), do: relative(right, os_type)
|
||||
defp do_join(left, "", _os_type), do: left
|
||||
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
defp remove_dirsep("", _os_type), do: ""
|
||||
defp remove_dirsep(bin, os_type) do
|
||||
last = :binary.last(bin)
|
||||
if last == ?/ or (last == ?\\ and os_type == :win32) do
|
||||
binary_part(bin, 0, byte_size(bin) - 1)
|
||||
else
|
||||
bin
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Splits the path into a list at the path separator.
|
||||
@@ -568,7 +593,7 @@ defmodule Path do
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
# expand_dot the given path by expanding "..", "." and "~".
|
||||
|
||||
defp chardata_to_list(chardata) do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
@@ -585,34 +610,51 @@ defmodule Path do
|
||||
|
||||
defp expand_home(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
"~" <> rest -> resolve_home(rest)
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
defp resolve_home(""), do: System.user_home!
|
||||
|
||||
defp normalize([".."|t], ["/"|_] = acc) do
|
||||
normalize t, acc
|
||||
defp resolve_home(rest) do
|
||||
case {rest, major_os_type} do
|
||||
{"\\" <> _, :win32} ->
|
||||
System.user_home! <> rest
|
||||
{"/" <> _, _} ->
|
||||
System.user_home! <> rest
|
||||
_ -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
|
||||
normalize t, acc
|
||||
defp expand_dot(<<"/../", rest::binary>>),
|
||||
do: expand_dot("/" <> rest)
|
||||
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/", rest::binary>>)
|
||||
defp expand_dot("/.."),
|
||||
do: "/"
|
||||
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/">>)
|
||||
defp expand_dot(path),
|
||||
do: expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
|
||||
defp expand_dot([".."|t], [_, _|acc]) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
defp expand_dot(["."|t], acc) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize(["."|t], acc) do
|
||||
normalize t, acc
|
||||
defp expand_dot([h|t], acc) do
|
||||
expand_dot t, ["/", h|acc]
|
||||
end
|
||||
|
||||
defp normalize([h|t], acc) do
|
||||
normalize t, [h|acc]
|
||||
defp expand_dot([], ["/"|acc]) do
|
||||
IO.iodata_to_binary(:lists.reverse(acc))
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join :lists.reverse(acc)
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,11 +85,13 @@ defmodule Protocol do
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_impl!(module, module) :: :ok | no_return
|
||||
def assert_impl!(protocol, impl) do
|
||||
assert_impl!(protocol, impl, "")
|
||||
def assert_impl!(protocol, base) do
|
||||
assert_impl!(protocol, base, "")
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, impl, extra) do
|
||||
defp assert_impl!(protocol, base, extra) do
|
||||
impl = Module.concat(protocol, base)
|
||||
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError,
|
||||
@@ -287,7 +289,7 @@ defmodule Protocol do
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
atom when is_atom(atom) -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
@@ -574,14 +576,14 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
impl = Module.concat(protocol, Map)
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
assert_impl!(protocol, impl, extra)
|
||||
assert_impl!(protocol, Map, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
impl = Module.concat(protocol, Map)
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
|
||||
+74
-30
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? {User, "jose", 27}
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -29,7 +29,7 @@ defmodule Record do
|
||||
|
||||
defmodule MyModule do
|
||||
require Record
|
||||
Record.defrecord :user name: "José", age: 25
|
||||
Record.defrecord :user name: "john", age: 25
|
||||
|
||||
@type user :: record(:user, name: String.t, age: integer)
|
||||
# expands to: `@type user :: {:user, String.t, integer}`
|
||||
@@ -56,18 +56,7 @@ defmodule Record do
|
||||
Macro.escape Record.Extractor.extract(name, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record of `kind`.
|
||||
|
||||
This is implemented as a macro so it can be used in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
@doc false
|
||||
defmacro record?(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
@@ -84,6 +73,51 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record of `kind`.
|
||||
|
||||
This is implemented as a macro so it can be used in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
and :erlang.element(1, unquote(data)) == unquote(kind)
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and :erlang.element(1, result) == unquote(kind)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro record?(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
and is_atom(:erlang.element(1, unquote(data)))
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and is_atom(:erlang.element(1, result))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record.
|
||||
|
||||
@@ -91,15 +125,15 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record)
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
iex> Record.is_record(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -126,24 +160,24 @@ defmodule Record do
|
||||
|
||||
defmodule User do
|
||||
require Record
|
||||
Record.defrecord :user, [name: "José", age: "25"]
|
||||
Record.defrecord :user, [name: "meg", age: "25"]
|
||||
end
|
||||
|
||||
In the example above, a set of macros named `user` but with different
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
record = user() #=> {:user, "José", 25}
|
||||
record = user(age: 26) #=> {:user, "José", 26}
|
||||
record = user() #=> {:user, "meg", 25}
|
||||
record = user(age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
user(record, :name) #=> "meg"
|
||||
|
||||
# To update the record
|
||||
user(record, age: 26) #=> {:user, "José", 26}
|
||||
user(record, age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# Convert a record to a keyword list
|
||||
user(record) #=> [name: "José", age: 26]
|
||||
user(record) #=> [name: "meg", age: 26]
|
||||
|
||||
By default, Elixir uses the record name as the first element of
|
||||
the tuple (the tag). But it can be changed to something else:
|
||||
@@ -160,7 +194,7 @@ defmodule Record do
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecord, kv)
|
||||
fields = Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -178,7 +212,7 @@ defmodule Record do
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecordp, kv)
|
||||
fields = Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -194,9 +228,19 @@ defmodule Record do
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
:lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
|
||||
else
|
||||
val -> {key, val}
|
||||
end
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
other ->
|
||||
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
@@ -309,7 +353,7 @@ defmodule Record do
|
||||
# Returns a keyword list of the record
|
||||
@doc false
|
||||
def __keyword__(atom, fields, record) do
|
||||
if record?(record, atom) do
|
||||
if is_record(record, atom) do
|
||||
[_tag|values] = Tuple.to_list(record)
|
||||
join_keyword(fields, values, [])
|
||||
else
|
||||
|
||||
+52
-13
@@ -321,12 +321,17 @@ defmodule Regex do
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the given number of
|
||||
parts. If not specified, `:parts` is defaulted to `:infinity`, which will
|
||||
parts. If not specified, `:parts` defaults to `:infinity`, which will
|
||||
split the string into the maximum number of parts possible based on the
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result.
|
||||
|
||||
* `:on` - specifies which captures and order to split the string
|
||||
on. Check the moduledoc for `Regex` to see the possible capture
|
||||
values. Defaults to `:first` which means captures inside the
|
||||
Regex does not affect the split result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
@@ -341,27 +346,61 @@ defmodule Regex do
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc")
|
||||
["", ""]
|
||||
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
|
||||
["a", "c"]
|
||||
|
||||
"""
|
||||
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
opts = [return: :binary, parts: zero_to_infinity(parts)]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
def split(%Regex{}, "", _opts), do: [""]
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
do_split(matches, string, 0,
|
||||
parts_to_index(Keyword.get(opts, :parts, :infinity)),
|
||||
Keyword.get(opts, :trim, false))
|
||||
:match ->
|
||||
[string]
|
||||
:nomatch ->
|
||||
[string]
|
||||
end
|
||||
end
|
||||
|
||||
defp zero_to_infinity(0), do: :infinity
|
||||
defp zero_to_infinity(n), do: n
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp do_split(_, string, offset, _counter, true) when byte_size(string) <= offset,
|
||||
do: []
|
||||
|
||||
defp do_split(_, string, offset, 1, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([], string, offset, _counter, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
|
||||
do: do_split([h|t], string, offset, counter, trim)
|
||||
|
||||
defp do_split([[]|t], string, offset, counter, trim),
|
||||
do: do_split(t, string, offset, counter, trim)
|
||||
|
||||
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
|
||||
new_offset = pos + length
|
||||
keep = pos - offset
|
||||
|
||||
if keep == 0 and (length == 0 or trim) do
|
||||
do_split([h|t], string, new_offset, counter, trim)
|
||||
else
|
||||
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
|
||||
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
|
||||
@@ -198,7 +198,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+104
-14
@@ -89,6 +89,7 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
@type acc :: any
|
||||
@@ -161,7 +162,7 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
if is_nil(pad) || count == 0 do
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
@@ -337,6 +338,25 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a value every `n` milliseconds.
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.interval(10) |> Enum.take(10)
|
||||
[0,1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t
|
||||
def interval(n) do
|
||||
unfold 0, fn (count) ->
|
||||
:timer.sleep(n)
|
||||
{count, count + 1}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
@@ -562,6 +582,22 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t
|
||||
def timer(n) do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -846,6 +882,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable)
|
||||
|
||||
def cycle(enumerable) when is_list(enumerable) do
|
||||
unfold {enumerable, enumerable}, fn
|
||||
{source, [h | t]} -> {h, {source, t}}
|
||||
{source = [h | t], []} -> {h, {source, t}}
|
||||
end
|
||||
end
|
||||
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
@@ -933,14 +978,15 @@ defmodule Stream do
|
||||
@doc """
|
||||
Emits a sequence of values for the given resource.
|
||||
|
||||
Similar to `unfold/2` but the initial value is computed lazily via
|
||||
`start_fun` and executes an `after_fun` at the end of enumeration
|
||||
(both in cases of success and failure).
|
||||
Similar to `transform/2` but the initial accumulated value is
|
||||
computed lazily via `start_fun` and executes an `after_fun` at
|
||||
the end of enumeration (both in cases of success and failure).
|
||||
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple with the current and
|
||||
next accumulator. The enumeration finishes if it returns `nil`.
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
resources.
|
||||
@@ -950,8 +996,8 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
data when is_binary(data) -> {[data], file}
|
||||
_ -> {:halt, file}
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
@@ -971,11 +1017,17 @@ defmodule Stream do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
# TODO: We should check this is a two-item tuple once
|
||||
# we remove support for the deprecated nil
|
||||
# TODO: Support non lists once the previous format
|
||||
# is removed
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
# Optimize the most common cases
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
|
||||
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
|
||||
other -> other
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -983,11 +1035,40 @@ defmodule Stream do
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
{:halt, next_acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{list, next_acc} when is_list(list) ->
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
nil ->
|
||||
IO.write :stderr, "warning: returning nil from Stream.resource/3 is deprecated, " <>
|
||||
"please return {:halt, acc} instead\n#{Exception.format_stacktrace}"
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{v, next_acc} ->
|
||||
IO.write :stderr, "warning: returning {next, acc} from Stream.resource/3 is deprecated, " <>
|
||||
"please return {[val], acc} instead\n#{Exception.format_stacktrace}"
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce([v], &1, fun))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1088,3 +1169,12 @@ defimpl Enumerable, for: Stream do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Stream do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(%{enum: enum, funs: funs}, opts) do
|
||||
inner = [enum: enum, funs: Enum.reverse(funs)]
|
||||
concat ["#Stream<", to_doc(inner, opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+182
-115
@@ -4,28 +4,12 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
In addition to this module, Elixir provides more low-level
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.As per the standard, a codepoint is
|
||||
an Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
@@ -66,34 +50,110 @@ defmodule String do
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
To act accordingly to the Unicode Standard, many functions
|
||||
in this module runs in linear time, as it needs to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
the input grows. On the other hand, `byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
This means often there are performance costs in using the
|
||||
functions in this module, compared to the more low-level
|
||||
operations that work directly with binaries:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
There are many situations where using the `String` module can
|
||||
be avoided in favor of binary functions or pattern matching.
|
||||
For example, imagine you have a string `prefix` and you want to
|
||||
remove this prefix from another string named `full`.
|
||||
|
||||
One may be tempted to write:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base, String.length(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Although the function above works, it performs poorly. To
|
||||
calculate the length of the string, we need to traverse it
|
||||
fully, so we traverse both `prefix` and `full` strings, then
|
||||
slice the `full` one, traversing it again.
|
||||
|
||||
A first attempting at improving it could be with ranges:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base..-1)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
While this is much better (we don't traverse `full` twice),
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `byte_size/1`
|
||||
and `binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> binary_part(full, base, byte_size(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint in multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` special macro:
|
||||
codepoint. One may use the `?` construct:
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
iex> ?o
|
||||
111
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
iex> ?á
|
||||
225
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
iex> << eacute :: utf8 >> = "á"
|
||||
iex> eacute
|
||||
233
|
||||
225
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
"jos\x{0065}\x{0301}" #=>
|
||||
"josé"
|
||||
"ol\x{0061}\x{0301}" #=>
|
||||
"olá"
|
||||
|
||||
## Self-synchronization
|
||||
|
||||
@@ -129,7 +189,7 @@ defmodule String do
|
||||
@spec printable?(t) :: boolean
|
||||
|
||||
def printable?(<< h :: utf8, t :: binary >>)
|
||||
when h in ?\040..?\176
|
||||
when h in 0x20..0x7E
|
||||
when h in 0xA0..0xD7FF
|
||||
when h in 0xE000..0xFFFD
|
||||
when h in 0x10000..0x10FFFF do
|
||||
@@ -227,40 +287,31 @@ defmodule String do
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options \\ [])
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options) do
|
||||
index =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 -> num
|
||||
_ -> 0
|
||||
end
|
||||
split_codepoints(binary, index - 1, Keyword.get(options, :trim, false))
|
||||
def split(string, "", options) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
|
||||
end
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
def split(string, pattern, options) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
Regex.split(pattern, string, options)
|
||||
else
|
||||
splits =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
_ ->
|
||||
:binary.split(binary, pattern, [:global])
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
if parts == :infinity and trim == false do
|
||||
:binary.split(string, pattern, [:global])
|
||||
else
|
||||
splits
|
||||
split_parts(string, pattern, parts_to_index(parts), trim)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp split_codepoints(binary, 0, _trim), do: [binary]
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp split_codepoints(binary, 1, _trim), do: [binary]
|
||||
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
|
||||
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
|
||||
defp split_codepoints(<<h, t :: binary>>, count, trim),
|
||||
@@ -268,14 +319,21 @@ defmodule String do
|
||||
defp split_codepoints(<<>>, _, true), do: []
|
||||
defp split_codepoints(<<>>, _, false), do: [""]
|
||||
|
||||
defp split_parts("", _pattern, _num), do: [""]
|
||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
case :binary.split(binary, pattern) do
|
||||
[head] -> Enum.reverse([head|parts])
|
||||
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
|
||||
defp split_parts("", _pattern, _num, true), do: []
|
||||
defp split_parts("", _pattern, _num, _trim), do: [""]
|
||||
defp split_parts(string, _pattern, 1, _trim), do: [string]
|
||||
defp split_parts(string, pattern, num, trim) do
|
||||
case :binary.split(string, pattern) do
|
||||
[""] when trim ->
|
||||
[]
|
||||
[head] ->
|
||||
[head]
|
||||
[head, tail] ->
|
||||
if trim and head == "" do
|
||||
split_parts(tail, pattern, num, trim)
|
||||
else
|
||||
[head|split_parts(tail, pattern, num-1, trim)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -337,8 +395,8 @@ defmodule String do
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
iex> String.upcase("olá")
|
||||
"OLÁ"
|
||||
|
||||
"""
|
||||
@spec upcase(t) :: t
|
||||
@@ -355,8 +413,8 @@ defmodule String do
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
iex> String.downcase("OLÁ")
|
||||
"olá"
|
||||
|
||||
"""
|
||||
@spec downcase(t) :: t
|
||||
@@ -379,8 +437,8 @@ defmodule String do
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
iex> String.capitalize("olá")
|
||||
"Olá"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
@@ -420,7 +478,7 @@ defmodule String do
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) when char in 0..127 do
|
||||
if :binary.last(string) == char do
|
||||
do_rstrip(string, "", char)
|
||||
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
|
||||
else
|
||||
string
|
||||
end
|
||||
@@ -687,8 +745,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
@@ -714,8 +772,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_codepoint("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_codepoint("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_codepoint: 1}
|
||||
@@ -801,10 +859,10 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
|
||||
["abc\000"]
|
||||
["abc\0"]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
|
||||
["abc\000", <<0x0ffff::utf8>>]
|
||||
["abc\0", <<0x0ffff::utf8>>]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
|
||||
["abc", <<0, 0x0ffff::utf8>>]
|
||||
@@ -864,8 +922,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_grapheme("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_grapheme("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_grapheme: 1}
|
||||
@@ -1030,17 +1088,29 @@ defmodule String do
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
|
||||
case do_count_bytes(next_grapheme(string), start, 0) do
|
||||
{nil, _} -> ""
|
||||
{next, start_bytes} ->
|
||||
{_, len_bytes} = do_count_bytes(next, len, 0)
|
||||
binary_part(string, start_bytes, len_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
start = length(string) + start
|
||||
case start >= 0 do
|
||||
true -> slice(string, start, len)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_count_bytes(next, 0, acc), do: {next, acc}
|
||||
defp do_count_bytes(nil, _, acc), do: {nil, acc}
|
||||
|
||||
defp do_count_bytes({char, rest}, counter, acc) do
|
||||
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a substring from the offset given by the start of the
|
||||
range to the offset given by the end of the range.
|
||||
@@ -1088,54 +1158,51 @@ defmodule String do
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
def slice("", _.._), do: ""
|
||||
|
||||
def slice(string, first..-1) when first >= 0 do
|
||||
case do_count_bytes(next_grapheme(string), first, 0) do
|
||||
{nil, _} -> ""
|
||||
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, first..last) do
|
||||
total = length(string)
|
||||
|
||||
if first < 0 do
|
||||
first = total + first
|
||||
end
|
||||
|
||||
if last < 0 do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
if last >= first do
|
||||
slice(string, first, last - first + 1)
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
""
|
||||
def slice(string, first..last) do
|
||||
{bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
|
||||
|
||||
if first < 0, do: first = length + first
|
||||
if last < 0, do: last = length + last
|
||||
|
||||
if first < 0 or first > last or first > length do
|
||||
""
|
||||
else
|
||||
last = min(last + 1, length)
|
||||
bytes = Enum.drop(bytes, length - last)
|
||||
first = last - first
|
||||
{length_bytes, start_bytes} = Enum.split(bytes, first)
|
||||
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
|
||||
defp do_acc_bytes({char, rest}, bytes, length) do
|
||||
do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
|
||||
end
|
||||
|
||||
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
|
||||
end
|
||||
|
||||
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
acc
|
||||
defp do_acc_bytes(nil, bytes, length) do
|
||||
{bytes, length}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
GenServer.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +68,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
GenServer.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Supervisor do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, _from, t) do
|
||||
def handle_cast({:push, h}, t) do
|
||||
{:noreply, [h|t]}
|
||||
end
|
||||
end
|
||||
@@ -268,7 +268,9 @@ defmodule Supervisor do
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
@@ -290,7 +292,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
@@ -306,7 +310,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
def delete_child(supervisor, child_id) do
|
||||
call(supervisor, {:delete_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
@@ -335,7 +341,9 @@ defmodule Supervisor do
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
@@ -361,7 +369,9 @@ defmodule Supervisor do
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
@@ -384,6 +394,12 @@ defmodule Supervisor do
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
+13
-25
@@ -204,26 +204,20 @@ defmodule System do
|
||||
@doc """
|
||||
Registers a program exit handler function.
|
||||
|
||||
Registers a function that will be invoked
|
||||
at the end of program execution. Useful for
|
||||
invoking a hook in "script" mode.
|
||||
Registers a function that will be invoked at the end of program execution.
|
||||
Useful for invoking a hook in "script" mode.
|
||||
|
||||
The function must receive the exit status code
|
||||
as an argument.
|
||||
The handler always executes in a different process from the one it was
|
||||
registered in. As a consequence, any resources managed by the calling process
|
||||
(ETS tables, open files, etc.) won't be available by the time the handler
|
||||
function is invoked.
|
||||
|
||||
The function must receive the exit status code as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
List.to_string :os.cmd(String.to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locates an executable on the system.
|
||||
|
||||
@@ -234,12 +228,6 @@ defmodule System do
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
"""
|
||||
@spec find_executable(binary) :: binary | nil
|
||||
|
||||
def find_executable(program) when is_list(program) do
|
||||
IO.puts :stderr, "warning: passing a char_list to System.find_executable/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
case :os.find_executable(String.to_char_list(program)) do
|
||||
false -> nil
|
||||
@@ -379,7 +367,7 @@ defmodule System do
|
||||
`command` is expected to be an executable available in PATH
|
||||
unless an absolute path is given.
|
||||
|
||||
`args` must be a list of strings and they are not expanded
|
||||
`args` must be a list of strings which are not expanded
|
||||
in any way. For example, this means wildcard expansion will
|
||||
not happen unless `Path.wildcard/2` is used. On Windows though,
|
||||
wildcard expansion is up to the program.
|
||||
@@ -389,7 +377,7 @@ defmodule System do
|
||||
## Options
|
||||
|
||||
* `:into` - injects the result into the given collectable, defaults to `""`
|
||||
* `:cd` - the directory to run the command on
|
||||
* `:cd` - the directory to run the command in
|
||||
* `:env` - an enumerable of tuples containing environment key-value as binary
|
||||
* `:arg0` - set the command arg0
|
||||
* `:stderr_to_stdout` - redirects stderr to stdout when true
|
||||
@@ -402,7 +390,7 @@ defmodule System do
|
||||
## Error reasons
|
||||
|
||||
If invalid arguments are given, `ArgumentError` is raised by
|
||||
`System.cmd/3`. `System.cmd/3` also expect a strict set of
|
||||
`System.cmd/3`. `System.cmd/3` also expects a strict set of
|
||||
options and will raise if unknown or invalid options are given.
|
||||
|
||||
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
|
||||
@@ -414,7 +402,7 @@ defmodule System do
|
||||
|
||||
* `:eagain` - there are no more available operating system processes
|
||||
|
||||
* `:enametoolong` - the external command given was too long.
|
||||
* `:enametoolong` - the external command given was too long
|
||||
|
||||
* `:emfile` - there are no more available file descriptors
|
||||
(for the operating system process that the Erlang emulator runs in)
|
||||
@@ -432,7 +420,7 @@ defmodule System do
|
||||
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
|
||||
"""
|
||||
@spec cmd(binary, [binary], Keyword.t) ::
|
||||
{:ok, Collectable.t, exit_status :: non_neg_integer} | {:error, atom}
|
||||
{Collectable.t, exit_status :: non_neg_integer}
|
||||
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||
command = String.to_char_list(command)
|
||||
|
||||
|
||||
+36
-4
@@ -58,7 +58,7 @@ defmodule Task do
|
||||
that dynamically supervise tasks:
|
||||
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
Task.Supervisor.async(pid, fn -> do_work() end)
|
||||
Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
|
||||
long as the supervisor is registered locally or globally:
|
||||
@@ -67,7 +67,7 @@ defmodule Task do
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# In the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
|
||||
@@ -77,7 +77,15 @@ defmodule Task do
|
||||
supervisor(Task.Supervisor, [[name: :tasks_sup]])
|
||||
]
|
||||
|
||||
Check `Task.Supervisor` for other operations supported by the Task supervisor.
|
||||
Note that, when working with distributed tasks, one should use the `async/3` API,
|
||||
that expects explicit module, function and arguments, instead of `async/1` that
|
||||
works with anonymous functions. That's because the anonymous function API expects
|
||||
the same module version to exist on all involved nodes. Check the `Agent` module
|
||||
documentation for more information on distributed processes, as the limitations
|
||||
described in the agents documentation apply to the whole ecosystem.
|
||||
|
||||
Finally, check `Task.Supervisor` for other operations supported by the Task
|
||||
supervisor.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -111,6 +119,30 @@ defmodule Task do
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
def start(fun) do
|
||||
start(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
def start(mod, fun, args) do
|
||||
Task.Supervised.start(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
@@ -152,7 +184,7 @@ defmodule Task do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
Awaits a task reply.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
+14
-6
@@ -68,7 +68,7 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
keys and values are URL encoded as per `encode/2`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
@@ -309,7 +309,7 @@ defmodule URI do
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
if is_nil(port) and not is_nil(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
@@ -349,12 +349,20 @@ defimpl String.Chars, for: URI do
|
||||
if uri.port == port, do: uri = %{uri | port: nil}
|
||||
end
|
||||
|
||||
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
|
||||
|
||||
if uri.host do
|
||||
authority = uri.host
|
||||
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
|
||||
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
|
||||
else
|
||||
authority = uri.authority
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
|
||||
if uri.scheme, do: result = result <> uri.scheme <> ":"
|
||||
if authority, do: result = result <> "//" <> authority
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
@@ -324,7 +324,7 @@ defmodule Version do
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
if is_nil(minor) or (is_nil(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = String.to_integer(major)
|
||||
|
||||
+1
-4
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
|
||||
def project do
|
||||
[app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n"]
|
||||
build_per_environment: false]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
-export([start_cli/0,
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
@@ -50,16 +50,21 @@ stop(_S) ->
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
ok = application:ensure_started(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
%% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
ok = application:ensure_started(?MODULE),
|
||||
{ok, _} = application:ensure_all_started(?MODULE),
|
||||
|
||||
%% We start the Logger so tools that depend on Elixir
|
||||
%% always have the Logger directly accessible. However
|
||||
%% Logger is not a dependency of the Elixir application,
|
||||
%% which means releases that want to use Logger must
|
||||
%% always list it as part of its applications.
|
||||
_ = case code:ensure_loaded('Elixir.Logger') of
|
||||
{module, _} -> application:start(logger);
|
||||
{error, _} -> ok
|
||||
end,
|
||||
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
@@ -41,28 +41,29 @@
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
|
||||
-define(is_bin(S), (S >= $0 andalso S =< $1)).
|
||||
-define(is_octal(S), (S >= $0 andalso S =< $7)).
|
||||
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_digit(S), (S >= $0 andalso S =< $9)).
|
||||
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
|
||||
-define(is_downcase(S), (S >= $a andalso S =< $z)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_quote(S), (S == $" orelse S == $')).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
|
||||
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
|
||||
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
|
||||
-define(is_invalid_space(S), (S == 16#A0)).
|
||||
@@ -36,7 +36,7 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
|
||||
|
||||
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
|
||||
case Size of
|
||||
@@ -111,16 +111,9 @@ handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Info ->
|
||||
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
%% Deprecate me: we can remove unpack_bit_info/3 once deprecated.
|
||||
unpack_bit_info(Meta, [H|T], _E) ->
|
||||
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
|
||||
unpack_bit_info(Dashed, []);
|
||||
unpack_bit_info(_Meta, Expr, _E) ->
|
||||
unpack_bit_info(Expr, []).
|
||||
|
||||
unpack_bit_info({'-', _, [H, T]}, Acc) ->
|
||||
unpack_bit_info(H, unpack_bit_info(T, Acc));
|
||||
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -15,15 +15,19 @@ get_opt(Key) ->
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
string(Contents, File, nil).
|
||||
string(Contents, File, Dest) ->
|
||||
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
quoted(Forms, File, Dest).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
quoted(Forms, File, nil).
|
||||
quoted(Forms, File, Dest) ->
|
||||
Previous = get(elixir_compiled),
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
elixir_lexical:run(File, fun
|
||||
elixir_lexical:run(File, Dest, fun
|
||||
(Pid) ->
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
|
||||
@@ -34,14 +38,20 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
end.
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
file(Relative, nil).
|
||||
file(Relative, Dest) ->
|
||||
File = filename:absname(Relative),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
string(elixir_utils:characters_to_list(Bin), File, case Dest of
|
||||
nil -> Dest;
|
||||
_ -> filename:absname(Dest)
|
||||
end).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Lists = file(File),
|
||||
_ = [binary_to_path(X, Path) || X <- Lists],
|
||||
Lists.
|
||||
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
|
||||
Comp = file(File, Dest),
|
||||
Abs = filename:absname(Dest),
|
||||
_ = [binary_to_path(X, Abs) || X <- Comp],
|
||||
Comp.
|
||||
|
||||
%% Evaluation
|
||||
|
||||
@@ -70,7 +80,7 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
%% process when native is set to true
|
||||
AllOpts = elixir_code_server:call(erl_compiler_options),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
|
||||
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
@@ -128,36 +138,45 @@ allows_fast_compilation(_) -> false.
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, File, Opts, Callback) ->
|
||||
module(Forms, Opts, E, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
|
||||
false -> elixir_code_server:call(erl_compiler_options)
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
inner_module(Forms, Final, false, E, Callback).
|
||||
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
|
||||
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Source = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Source}|Options]) of
|
||||
{ok, Module, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
{module, ModuleName} = code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
|
||||
Callback(Module, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
beam_location(#{module := nil}) -> in_memory;
|
||||
beam_location(#{lexical_tracker := Pid, module := Module}) ->
|
||||
case elixir_lexical:dest(Pid) of
|
||||
nil -> in_memory;
|
||||
Dest ->
|
||||
filename:join(elixir_utils:characters_to_list(Dest),
|
||||
atom_to_list(Module) ++ ".beam")
|
||||
end.
|
||||
|
||||
no_auto_import() ->
|
||||
{attribute, 0, compile, no_auto_import}.
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
ok = application:ensure_started(elixir),
|
||||
{ok, _} = application:ensure_all_started(elixir),
|
||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
assert_valid_name(Line, Kind, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
|
||||
|
||||
@@ -234,7 +235,7 @@ unwrap_definitions(File, Module) ->
|
||||
|
||||
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
|
||||
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
||||
split_definition(All, Unreachable, [], [], [], [], {[], []}).
|
||||
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
|
||||
|
||||
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
|
||||
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
|
||||
@@ -263,31 +264,35 @@ unwrap_definition([], _File, _Module, _CTable, All, Private) ->
|
||||
{All, Private}.
|
||||
|
||||
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
|
||||
[export(def, Tuple)|Exports],
|
||||
add_definition(Line, Location, Body, Functions));
|
||||
|
||||
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
case lists:member(Tuple, Unreachable) of
|
||||
false ->
|
||||
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
|
||||
add_definition(Line, Location, Body, Functions));
|
||||
Exports, add_definition(Line, Location, Body, Functions));
|
||||
true ->
|
||||
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop, Functions)
|
||||
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
|
||||
Exports, Functions)
|
||||
end;
|
||||
|
||||
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
|
||||
[export(defmacro, Tuple)|Exports],
|
||||
add_definition(Line, Location, Body, Functions));
|
||||
|
||||
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop], Functions);
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
|
||||
Exports, Functions);
|
||||
|
||||
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, {Head, Tail}) ->
|
||||
{Def, Defp, Defmacro, Defmacrop, Head ++ Tail}.
|
||||
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
|
||||
{Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -400,8 +405,12 @@ invalid_arg(_) ->
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
|
||||
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
|
||||
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
@@ -443,5 +452,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({is_record, Kind}) ->
|
||||
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
|
||||
"issues with the Erlang compiler (it is a known bug)", [Kind]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -31,17 +31,20 @@ default_macros() ->
|
||||
default_requires() ->
|
||||
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
|
||||
|
||||
%% This is used by elixir_quote. Note we don't record the
|
||||
%% import locally because at that point there is no
|
||||
%% ambiguity.
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = {Name, Arity},
|
||||
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
{macro, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -519,19 +522,19 @@ deprecation_message(Warning, Message) ->
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
deprecation('Elixir.Mix.Generator', 'from_file', _) ->
|
||||
"instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
|
||||
"Note that [from_file: file] expects paths relative to the current working "
|
||||
"directory and not to the current file";
|
||||
deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
|
||||
"check EEx.SmartEngine for how to build custom engines";
|
||||
deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
||||
"check EEx.SmartEngine for how to build custom engines";
|
||||
deprecation('Elixir.Kernel', 'xor', _) ->
|
||||
true; %% Remember to remove xor operator from tokenizer
|
||||
|
||||
% deprecation('Elixir.System', 'cmd', 1) ->
|
||||
% "use System.cmd/3 instead;"
|
||||
|
||||
deprecation('Elixir.Collectable', 'empty', 1) ->
|
||||
true;
|
||||
deprecation('Elixir.Enum', 'traverse', 2) ->
|
||||
true;
|
||||
deprecation('Elixir.Kernel', 'nil?', 1) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', 'record?', 1) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', 'record?', 2) ->
|
||||
true;
|
||||
deprecation('Elixir.Integer', 'even?', 1) ->
|
||||
true;
|
||||
deprecation('Elixir.Integer', 'odd?', 1) ->
|
||||
true;
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -96,7 +96,7 @@ handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}})
|
||||
false ->
|
||||
case erl_internal:bif(F, A) of
|
||||
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
|
||||
true -> {"the call to ~ts/~B has no effect", [F,A]}
|
||||
true -> {"the call to ~ts/~B has no effect", [F,A]}
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
|
||||
@@ -66,6 +66,8 @@ expand({'__aliases__', _, _} = Alias, E) ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{Receiver, EA};
|
||||
false ->
|
||||
elixir_errors:warn(?m(E, line), ?m(E, file), io_lib:format("an alias must expand to an atom "
|
||||
"at compilation time but got ~ts", ['Elixir.Macro':to_string(hd(EAliases))])),
|
||||
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
|
||||
end
|
||||
end;
|
||||
@@ -354,7 +356,7 @@ expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
|
||||
expand({_, _, _} = Tuple, E) ->
|
||||
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
|
||||
['Elixir.Kernel':inspect(Tuple, [])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
@@ -456,8 +458,7 @@ assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{Name, Meta, EArgs}, EA};
|
||||
compile_error(Meta, ?m(E, file), "undefined function ~ts/~B", [Name, length(Args)]);
|
||||
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
|
||||
elixir_locals:record_local({Name, length(Args)}, Module, Function),
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
|
||||
@@ -74,58 +74,51 @@ unescape_chars(String) ->
|
||||
unescape_chars(String, fun unescape_map/1).
|
||||
|
||||
unescape_chars(String, Map) ->
|
||||
Octals = Map($0) /= false,
|
||||
Hex = Map($x) /= false,
|
||||
unescape_chars(String, Map, Octals, Hex, <<>>).
|
||||
unescape_chars(String, Map, Map($x) == true, <<>>).
|
||||
|
||||
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) ->
|
||||
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
|
||||
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
|
||||
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
|
||||
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
|
||||
append_escaped(Rest, Map, [A], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
|
||||
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
|
||||
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
|
||||
append_escaped(Rest, Map, [A], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,A,B,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,A,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
append_escaped(Rest, Map, [A,B,C], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E,F], true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
|
||||
unescape_chars(<<$\\,$x,_/binary>>, _Map, true, _Acc) ->
|
||||
Msg = <<"missing hex sequence after \\x">>,
|
||||
error('Elixir.ArgumentError':exception([{message,Msg}]));
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,P,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Hex, Acc) ->
|
||||
case Map(Escaped) of
|
||||
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
|
||||
false -> unescape_chars(Rest, Map, Hex, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Hex, <<Acc/binary, Other>>)
|
||||
end;
|
||||
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
|
||||
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Hex, Acc) ->
|
||||
unescape_chars(Rest, Map, Hex, <<Acc/binary, Char>>);
|
||||
|
||||
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
|
||||
unescape_chars(<<>>, _Map, _Hex, Acc) -> Acc.
|
||||
|
||||
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
append_escaped(Rest, Map, List, Hex, Acc, Base) ->
|
||||
Codepoint = list_to_integer(List, Base),
|
||||
try <<Acc/binary, Codepoint/utf8>> of
|
||||
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
|
||||
Binary -> unescape_chars(Rest, Map, Hex, Binary)
|
||||
catch
|
||||
error:badarg ->
|
||||
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
|
||||
@@ -134,6 +127,7 @@ append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
unescape_map($0) -> 0;
|
||||
unescape_map($a) -> 7;
|
||||
unescape_map($b) -> $\b;
|
||||
unescape_map($d) -> $\d;
|
||||
@@ -144,6 +138,7 @@ unescape_map($r) -> $\r;
|
||||
unescape_map($s) -> $\s;
|
||||
unescape_map($t) -> $\t;
|
||||
unescape_map($v) -> $\v;
|
||||
unescape_map($x) -> true;
|
||||
unescape_map(E) -> E.
|
||||
|
||||
% Extract Helpers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%% Module responsible for tracking lexical information.
|
||||
-module(elixir_lexical).
|
||||
-export([run/2,
|
||||
-export([run/3, dest/1,
|
||||
record_alias/4, record_alias/2,
|
||||
record_import/4, record_import/2,
|
||||
record_remote/2, format_error/1
|
||||
@@ -9,10 +9,10 @@
|
||||
|
||||
-define(tracker, 'Elixir.Kernel.LexicalTracker').
|
||||
|
||||
run(File, Callback) ->
|
||||
run(File, Dest, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{file, _} ->
|
||||
Pid = ?tracker:start_link(),
|
||||
{ok, Pid} = ?tracker:start_link(Dest),
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
@@ -24,6 +24,9 @@ run(File, Callback) ->
|
||||
Callback(nil)
|
||||
end.
|
||||
|
||||
dest(nil) -> nil;
|
||||
dest(Pid) -> ?tracker:dest(Pid).
|
||||
|
||||
%% RECORD
|
||||
|
||||
record_alias(Module, Line, Warn, Ref) ->
|
||||
|
||||
@@ -73,8 +73,12 @@ rewrite_clause(Else, _) -> Else.
|
||||
|
||||
setup(Module) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
|
||||
false -> ok
|
||||
{file, _} ->
|
||||
{ok, Pid} = ?tracker:start_link(),
|
||||
ets:insert(Module, {?attr, Pid}),
|
||||
ok;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
|
||||
@@ -82,7 +82,7 @@ load_struct(Meta, Name, S) ->
|
||||
Context = lists:keyfind(struct, 1, Meta) == {struct, context},
|
||||
|
||||
Local =
|
||||
elixir_module:is_open(Name) andalso
|
||||
not(ensure_loaded(Name)) andalso
|
||||
(Context orelse wait_for_struct(Name)),
|
||||
|
||||
try
|
||||
@@ -101,17 +101,23 @@ load_struct(Meta, Name, S) ->
|
||||
error:undef ->
|
||||
Inspected = elixir_aliases:inspect(Name),
|
||||
|
||||
case Context of
|
||||
case Context andalso (S#elixir_scope.function == nil) of
|
||||
true ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot access struct ~ts in the same context that defines it as "
|
||||
"the struct fields are not yet accessible", [Inspected]);
|
||||
"cannot access struct ~ts, the struct was not yet defined or the struct is being "
|
||||
"accessed in the same context that defines it", [Inspected]);
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
||||
"cannot expand struct ~ts", [Inspected, Inspected])
|
||||
end
|
||||
end.
|
||||
|
||||
ensure_loaded(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{module, Module} -> true;
|
||||
{error, _} -> false
|
||||
end.
|
||||
|
||||
wait_for_struct(Module) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined ->
|
||||
|
||||
@@ -34,7 +34,7 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
|
||||
|
||||
case ?m(LexEnv, lexical_tracker) of
|
||||
nil ->
|
||||
elixir_lexical:run(?m(LexEnv, file), fun(Pid) ->
|
||||
elixir_lexical:run(?m(LexEnv, file), nil, fun(Pid) ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv#{lexical_tracker := Pid})
|
||||
end);
|
||||
_ ->
|
||||
@@ -60,10 +60,11 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
{Def, Defp, Defmacro, Defmacrop, Functions} =
|
||||
{Def, Defp, Defmacro, Defmacrop, Exports, Functions} =
|
||||
elixir_def:unwrap_definitions(File, Module),
|
||||
|
||||
{All, Forms0} = functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Functions),
|
||||
{All, Forms0} = functions_form(Line, File, Module, Def, Defp,
|
||||
Defmacro, Defmacrop, Exports, Functions),
|
||||
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
|
||||
Forms2 = types_form(Line, File, Module, Forms1),
|
||||
Forms3 = attributes_form(Line, File, Module, Forms2),
|
||||
@@ -149,15 +150,13 @@ eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Body) ->
|
||||
All = Def ++ Defp ++ Defmacro ++ Defmacrop,
|
||||
functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body) ->
|
||||
All = Def ++ Defmacro ++ Defp ++ Defmacrop,
|
||||
{Spec, Info} = add_info_function(Line, File, Module, All, Def, Defmacro),
|
||||
|
||||
NewBody = [Spec, Info|Body],
|
||||
Export = [{Name, Arity} || {function, _, Name, Arity, _} <- NewBody],
|
||||
|
||||
{[{'__info__',1}|All],
|
||||
[{attribute, Line, export, lists:sort(Export)} | NewBody]}.
|
||||
[{attribute, Line, export, lists:sort([{'__info__',1}|Exports])},
|
||||
Spec, Info | Body]}.
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
@@ -308,8 +307,8 @@ compile_opts(Module) ->
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #{file := File} = E) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary0) ->
|
||||
load_form(Line, Forms, Opts, E) ->
|
||||
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
|
||||
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
|
||||
@@ -322,7 +321,7 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
|
||||
undefined -> ok;
|
||||
PID ->
|
||||
Ref = make_ref(),
|
||||
PID ! {module_available, self(), Ref, File, Module, Binary},
|
||||
PID ! {module_available, self(), Ref, ?m(E, file), Module, Binary},
|
||||
receive {Ref, ack} -> ok end
|
||||
end;
|
||||
_ ->
|
||||
|
||||
@@ -7,7 +7,7 @@ Nonterminals
|
||||
add_op_eol mult_op_eol hat_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
type_op_eol rel_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_paren close_paren empty_paren eoe
|
||||
list list_args open_bracket close_bracket
|
||||
tuple open_curly close_curly
|
||||
bit_string open_bit close_bit
|
||||
@@ -16,11 +16,11 @@ Nonterminals
|
||||
container_args_base container_args call_args_parens_base call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
|
||||
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
@@ -32,13 +32,13 @@ Terminals
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op
|
||||
capture_op rel_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'true' 'false' 'nil' 'do' eol ';' ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
%% There are two shift/reduce conflicts coming from call_args_parens.
|
||||
%% Two shift/reduce conflicts coming from call_args_parens.
|
||||
Expect 2.
|
||||
|
||||
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
|
||||
@@ -54,7 +54,7 @@ Right 60 type_op_eol. %% ::
|
||||
Right 70 pipe_op_eol. %% |
|
||||
Right 80 assoc_op_eol. %% =>
|
||||
Right 90 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 130 or_op_eol. %% ||, |||, or
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
|
||||
Left 160 rel_op_eol. %% <, >, <=, >=
|
||||
@@ -72,16 +72,16 @@ Nonassoc 330 dot_identifier.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
grammar -> eol : nil.
|
||||
grammar -> eoe : nil.
|
||||
grammar -> expr_list : to_block('$1').
|
||||
grammar -> eol expr_list : to_block('$2').
|
||||
grammar -> expr_list eol : to_block('$1').
|
||||
grammar -> eol expr_list eol : to_block('$2').
|
||||
grammar -> eoe expr_list : to_block('$2').
|
||||
grammar -> expr_list eoe : to_block('$1').
|
||||
grammar -> eoe expr_list eoe : to_block('$2').
|
||||
grammar -> '$empty' : nil.
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
expr_list -> expr_list eoe expr : ['$3'|'$1'].
|
||||
|
||||
expr -> empty_paren : nil.
|
||||
expr -> matched_expr : '$1'.
|
||||
@@ -202,8 +202,12 @@ access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' close_paren : build_stab([]).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}.
|
||||
access_expr -> list : element(1, '$1').
|
||||
@@ -226,7 +230,6 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_alias : '$1'.
|
||||
|
||||
bracket_arg -> open_bracket ']' : build_list('$1', []).
|
||||
bracket_arg -> open_bracket kw close_bracket : build_list('$1', '$2').
|
||||
bracket_arg -> open_bracket container_expr close_bracket : build_list('$1', '$2').
|
||||
bracket_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2').
|
||||
@@ -241,28 +244,32 @@ bracket_at_expr -> at_op_eol access_expr bracket_arg :
|
||||
|
||||
%% Blocks
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eol block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
do_block -> do_eoe 'end' : [[{do,nil}]].
|
||||
do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
eoe -> eol : '$1'.
|
||||
eoe -> ';' : '$1'.
|
||||
eoe -> eol ';' : '$1'.
|
||||
|
||||
do_eol -> 'do' : '$1'.
|
||||
do_eol -> 'do' eol : '$1'.
|
||||
fn_eoe -> 'fn' : '$1'.
|
||||
fn_eoe -> 'fn' eoe : '$1'.
|
||||
|
||||
end_eol -> 'end' : '$1'.
|
||||
end_eol -> eol 'end' : '$2'.
|
||||
do_eoe -> 'do' : '$1'.
|
||||
do_eoe -> 'do' eoe : '$1'.
|
||||
|
||||
block_eol -> block_identifier : '$1'.
|
||||
block_eol -> block_identifier eol : '$1'.
|
||||
end_eoe -> 'end' : '$1'.
|
||||
end_eoe -> eoe 'end' : '$2'.
|
||||
|
||||
block_eoe -> block_identifier : '$1'.
|
||||
block_eoe -> block_identifier eoe : '$1'.
|
||||
|
||||
stab -> stab_expr : ['$1'].
|
||||
stab -> stab eol stab_expr : ['$3'|'$1'].
|
||||
stab -> stab eoe stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
stab_eoe -> stab : '$1'.
|
||||
stab_eoe -> stab eoe : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
@@ -276,8 +283,8 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab_eol : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eol : {?exprs('$1'), nil}.
|
||||
block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eoe : {?exprs('$1'), nil}.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
block_list -> block_item block_list : ['$1'|'$2'].
|
||||
|
||||
@@ -242,6 +242,15 @@ do_quote({Left, Right}, Q, E) ->
|
||||
{TRight, RQ} = do_quote(Right, LQ, E),
|
||||
{{TLeft, TRight}, RQ};
|
||||
|
||||
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
|
||||
case bit_size(BitString) rem 8 of
|
||||
0 ->
|
||||
{BitString, Q};
|
||||
Size ->
|
||||
<<Bits:Size, Bytes/binary>> = BitString,
|
||||
{{'<<>>', [], [{'::', [], [Bits, Size]}, Bytes]}, Q}
|
||||
end;
|
||||
|
||||
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
|
||||
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
|
||||
{{'%{}', [], TT}, TQ};
|
||||
@@ -251,7 +260,7 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
|
||||
{{'{}', [], TT}, TQ};
|
||||
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
|
||||
% The improper case is pretty inefficient, but improper lists are are.
|
||||
%% The improper case is a bit inefficient, but improper lists are rare.
|
||||
case reverse_improper(List) of
|
||||
{L} -> do_splice(L, Q, E);
|
||||
{L, R} ->
|
||||
@@ -259,14 +268,35 @@ do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
|
||||
{TR, QR} = do_quote(R, QL, E),
|
||||
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
|
||||
end;
|
||||
|
||||
do_quote(Other, #elixir_quote{escape=true} = Q, _)
|
||||
when is_number(Other); is_pid(Other); is_atom(Other) ->
|
||||
{Other, Q};
|
||||
|
||||
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
|
||||
case (erlang:fun_info(Fun, env) == {env, []}) andalso
|
||||
(erlang:fun_info(Fun, type) == {type, external}) of
|
||||
true -> {Fun, Q};
|
||||
false -> bad_escape(Fun)
|
||||
end;
|
||||
|
||||
do_quote(Other, #elixir_quote{escape=true}, _) ->
|
||||
bad_escape(Other);
|
||||
|
||||
do_quote(List, Q, E) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, E);
|
||||
do_splice(lists:reverse(List), Q, E);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{Other, Q}.
|
||||
|
||||
%% Quote helpers
|
||||
|
||||
bad_escape(Arg) ->
|
||||
Msg = <<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
|
||||
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"pids and remote functions in the format &Mod.fun/arity">>,
|
||||
error('Elixir.ArgumentError':exception([{message,Msg}])).
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
||||
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
|
||||
Q#elixir_quote.context],
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
-define(pipe_op(T1),
|
||||
-define(pipe_op(T),
|
||||
T == $|).
|
||||
|
||||
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
|
||||
@@ -118,15 +118,15 @@ tokenize([], EndLine, #elixir_tokenizer{terminators=[{Start, StartLine}|_]}, Tok
|
||||
|
||||
% Base integers
|
||||
|
||||
tokenize([$0,X,H|T], Line, Scope, Tokens) when (X == $x orelse X == $X), ?is_hex(H) ->
|
||||
tokenize([$0,$x,H|T], Line, Scope, Tokens) when ?is_hex(H) ->
|
||||
{Rest, Number} = tokenize_hex([H|T], []),
|
||||
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
|
||||
|
||||
tokenize([$0,B,H|T], Line, Scope, Tokens) when (B == $b orelse B == $B), ?is_bin(H) ->
|
||||
tokenize([$0,$b,H|T], Line, Scope, Tokens) when ?is_bin(H) ->
|
||||
{Rest, Number} = tokenize_bin([H|T], []),
|
||||
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
|
||||
|
||||
tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
|
||||
tokenize([$0,$o,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
|
||||
{Rest, Number} = tokenize_octal([H|T], []),
|
||||
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
|
||||
|
||||
@@ -159,59 +159,44 @@ tokenize([$~,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upca
|
||||
|
||||
% Char tokens
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,E,F,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
Char = escape_char([$\\,$x,${,A,B,C,D,E,F,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,E,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
Char = escape_char([$\\,$x,${,A,B,C,D,E,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,B,C,D,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
Char = escape_char([$\\,$x,${,A,B,C,D,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
Char = escape_char([$\\,P,${,A,B,C,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,B,C,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
Char = escape_char([$\\,$x,${,A,B,C,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
Char = escape_char([$\\,P,${,A,B,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,B,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B) ->
|
||||
Char = escape_char([$\\,$x,${,A,B,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,$}|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
Char = escape_char([$\\,P,${,A,$}]),
|
||||
tokenize([$?,$\\,$x,${,A,$}|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A) ->
|
||||
Char = escape_char([$\\,$x,${,A,$}]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,A,B|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
Char = escape_char([$\\,P,A,B]),
|
||||
tokenize([$?,$\\,$x,A,B|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A), ?is_hex(B) ->
|
||||
Char = escape_char([$\\,$x,A,B]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,A|T], Line, Scope, Tokens)
|
||||
when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
Char = escape_char([$\\,P,A]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens)
|
||||
when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
|
||||
Char = escape_char([$\\,A,B,C]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens)
|
||||
when ?is_octal(A), ?is_octal(B) ->
|
||||
Char = escape_char([$\\,A,B]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A|T], Line, Scope, Tokens)
|
||||
when ?is_octal(A) ->
|
||||
Char = escape_char([$\\,A]),
|
||||
tokenize([$?,$\\,$x,A|T], Line, Scope, Tokens)
|
||||
when ?is_hex(A) ->
|
||||
Char = escape_char([$\\,$x,A]),
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
|
||||
@@ -219,6 +204,14 @@ tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
case handle_char(Char) of
|
||||
{Escape, Name} ->
|
||||
Msg = io_lib:format("found ? followed by codepoint 0x~.16B (~ts), please use ~ts instead",
|
||||
[Char, Name, Escape]),
|
||||
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, Msg);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
|
||||
|
||||
% Heredocs
|
||||
@@ -306,10 +299,19 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
% End of line
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, []) ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', []));
|
||||
tokenize(Rest, Line, Scope, [{';', Line}]);
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|_] = Tokens) when element(1, Top) /= ';' ->
|
||||
tokenize(Rest, Line, Scope, [{';', Line}|Tokens]);
|
||||
|
||||
tokenize("\\" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\r\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
@@ -318,10 +320,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
@@ -461,22 +463,14 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
Error
|
||||
end;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens]) when
|
||||
?dual_op(Sign),
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
|
||||
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
|
||||
Identifier == identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(strip_horizontal_space(Rest), Line, Scope, Tokens);
|
||||
handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
|
||||
tokenize([T|Rest] = Original, Line, _Scope, Tokens) when ?is_invalid_space(T) ->
|
||||
Message = io_lib:format("invalid space character U+~.16B before: ", [T]),
|
||||
{error, {Line, lists:flatten(Message), until_eol(Rest)}, Original, Tokens};
|
||||
|
||||
tokenize(T, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
|
||||
|
||||
@@ -497,8 +491,20 @@ until_eol("\n" ++ _) -> [];
|
||||
until_eol([]) -> [];
|
||||
until_eol([H|T]) -> [H|until_eol(T)].
|
||||
|
||||
handle_char(7) -> {"\\a", "alert"};
|
||||
handle_char($\b) -> {"\\b", "backspace"};
|
||||
handle_char($\d) -> {"\\d", "delete"};
|
||||
handle_char($\e) -> {"\\e", "escape"};
|
||||
handle_char($\f) -> {"\\f", "form feed"};
|
||||
handle_char($\n) -> {"\\n", "newline"};
|
||||
handle_char($\r) -> {"\\r", "carriage return"};
|
||||
handle_char($\s) -> {"\\s", "space"};
|
||||
handle_char($\t) -> {"\\t", "tab"};
|
||||
handle_char($\v) -> {"\\v", "vertical tab"};
|
||||
handle_char(_) -> false.
|
||||
|
||||
escape_char(List) ->
|
||||
<< Char/utf8 >> = elixir_interpolation:unescape_chars(list_to_binary(List)),
|
||||
<<Char/utf8>> = elixir_interpolation:unescape_chars(list_to_binary(List)),
|
||||
Char.
|
||||
|
||||
%% Handlers
|
||||
@@ -590,11 +596,25 @@ handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({'.', Line}, Tokens)]).
|
||||
|
||||
% ## Ambiguous unary/binary operators tokens
|
||||
handle_space_sensitive_tokens([Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens]) when
|
||||
?dual_op(Sign),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
|
||||
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
|
||||
Identifier == identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
|
||||
tokenize(String, Line, Scope, Tokens).
|
||||
|
||||
%% Helpers
|
||||
|
||||
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
eol(_Line, [{';',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{eol,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Tokens) -> [{eol,Line}|Tokens].
|
||||
|
||||
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
|
||||
is_binary(Part) andalso size(Part) > 255;
|
||||
@@ -821,7 +841,7 @@ check_call_identifier(_Kind, Line, Atom, [$(|_]) -> {paren_identifier, Line, Ato
|
||||
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> {bracket_identifier, Line, Atom};
|
||||
check_call_identifier(Kind, Line, Atom, _Rest) -> {Kind, Line, Atom}.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, [{eol,_}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
%% Error handling
|
||||
@@ -945,7 +965,6 @@ keyword('nil') -> token;
|
||||
keyword('not') -> unary_op;
|
||||
keyword('and') -> and_op;
|
||||
keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
|
||||
|
||||
@@ -195,18 +195,16 @@ translate({Name, Meta, Args}, S) when is_atom(Name), is_list(Meta), is_list(Args
|
||||
if
|
||||
S#elixir_scope.context == match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
|
||||
"cannot invoke local ~ts/~B inside match", [Name, length(Args)]);
|
||||
S#elixir_scope.context == guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Name, Name, Arity]);
|
||||
"local ~ts/~B inside guard", [Name, Name, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Name, Arity])
|
||||
end;
|
||||
S#elixir_scope.function == nil ->
|
||||
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
|
||||
true ->
|
||||
Line = ?line(Meta),
|
||||
{TArgs, NS} = translate_args(Args, S),
|
||||
|
||||
@@ -3,7 +3,11 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule AgentTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "start_link/2 workflow with unregistered name" do
|
||||
def identity(state) do
|
||||
state
|
||||
end
|
||||
|
||||
test "start_link/2 workflow with unregistered name and anonymous functions" do
|
||||
{:ok, pid} = Agent.start_link(fn -> %{} end)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
@@ -17,13 +21,13 @@ defmodule AgentTest do
|
||||
wait_until_dead(pid)
|
||||
end
|
||||
|
||||
test "start/2 workflow with registered name" do
|
||||
{:ok, pid} = Agent.start(fn -> %{} end, name: :agent)
|
||||
test "start/2 workflow with registered name and module functions" do
|
||||
{:ok, pid} = Agent.start(Map, :new, [], name: :agent)
|
||||
assert Process.info(pid, :registered_name) == {:registered_name, :agent}
|
||||
assert Agent.cast(:agent, &Map.put(&1, :hello, :world)) == :ok
|
||||
assert Agent.get(:agent, &Map.get(&1, :hello)) == :world
|
||||
assert Agent.get_and_update(:agent, &Map.pop(&1, :hello)) == :world
|
||||
assert Agent.get(:agent, &(&1)) == %{}
|
||||
assert Agent.cast(:agent, Map, :put, [:hello, :world]) == :ok
|
||||
assert Agent.get(:agent, Map, :get, [:hello]) == :world
|
||||
assert Agent.get_and_update(:agent, Map, :pop, [:hello]) == :world
|
||||
assert Agent.get(:agent, AgentTest, :identity, []) == %{}
|
||||
assert Agent.stop(:agent) == :ok
|
||||
assert Process.info(pid, :registered_name) == nil
|
||||
end
|
||||
@@ -31,7 +35,7 @@ defmodule AgentTest do
|
||||
test ":sys.change_code/4 with mfa" do
|
||||
{ :ok, pid } = Agent.start_link(fn -> %{} end)
|
||||
:ok = :sys.suspend(pid)
|
||||
mfa = { Map, :put, [:hello, :world] }
|
||||
mfa = {Map, :put, [:hello, :world]}
|
||||
assert :sys.change_code(pid, __MODULE__, "vsn", mfa) == :ok
|
||||
:ok = :sys.resume(pid)
|
||||
assert Agent.get(pid, &Map.get(&1, :hello)) == :world
|
||||
|
||||
@@ -381,8 +381,6 @@ defmodule DictTest.Common do
|
||||
dict = new_dict([{1}, {2}, {3}], fn {x} -> {<<x + 64>>, x} end)
|
||||
assert Dict.size(dict) == 3
|
||||
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
|
||||
|
||||
assert Collectable.empty(new_dict) == new_dict([])
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
|
||||
@@ -189,11 +189,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
|
||||
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
|
||||
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
|
||||
assert Enum.into([1, 2, 3], fn
|
||||
func, {:cont, x} when is_function(func) -> [x]
|
||||
list, {:cont, x} -> [x|list]
|
||||
list, _ -> list
|
||||
end) == [3, 2, 1]
|
||||
end
|
||||
|
||||
test :intersperse do
|
||||
@@ -368,11 +363,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.to_list(1 .. 3) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :traverse do
|
||||
assert Enum.traverse([1, 2, 3], &(&1 * &1)) == [1, 4, 9]
|
||||
assert Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v*2} end) == %{a: 2, b: 4}
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
|
||||
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
|
||||
|
||||
@@ -308,8 +308,10 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test "undefined function message" do
|
||||
assert %UndefinedFunctionError{} |> message == "undefined function"
|
||||
assert %UndefinedFunctionError{module: Kernel, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Kernel.bar/1"
|
||||
assert %UndefinedFunctionError{module: Foo, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Foo.bar/1"
|
||||
"undefined function: Foo.bar/1 (module Foo is not available)"
|
||||
assert %UndefinedFunctionError{module: nil, function: :bar, arity: 0} |> message ==
|
||||
"undefined function: nil.bar/0"
|
||||
end
|
||||
|
||||
@@ -689,7 +689,7 @@ defmodule FileTest do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.write(fixture, "test")
|
||||
assert File.exists?(fixture)
|
||||
File.chmod(fixture, 0100444)
|
||||
File.chmod(fixture, 0o100444)
|
||||
assert File.rm(fixture) == :ok
|
||||
refute File.exists?(fixture)
|
||||
end
|
||||
@@ -1195,14 +1195,14 @@ defmodule FileTest do
|
||||
|
||||
File.touch(fixture)
|
||||
try do
|
||||
assert File.chmod(fixture, 0100666) == :ok
|
||||
assert File.chmod(fixture, 0o100666) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100666
|
||||
assert stat.mode == 0o100666
|
||||
|
||||
unless is_win? do
|
||||
assert File.chmod(fixture, 0100777) == :ok
|
||||
assert File.chmod(fixture, 0o100777) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100777
|
||||
assert stat.mode == 0o100777
|
||||
end
|
||||
after
|
||||
File.rm(fixture)
|
||||
@@ -1214,14 +1214,14 @@ defmodule FileTest do
|
||||
|
||||
File.touch(fixture)
|
||||
try do
|
||||
assert File.chmod!(fixture, 0100666) == :ok
|
||||
assert File.chmod!(fixture, 0o100666) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100666
|
||||
assert stat.mode == 0o100666
|
||||
|
||||
unless is_win? do
|
||||
assert File.chmod!(fixture, 0100777) == :ok
|
||||
assert File.chmod!(fixture, 0o100777) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100777
|
||||
assert stat.mode == 0o100777
|
||||
end
|
||||
after
|
||||
File.rm(fixture)
|
||||
@@ -1232,7 +1232,7 @@ defmodule FileTest do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chmod(fixture, 0100777) == {:error,:enoent}
|
||||
assert File.chmod(fixture, 0o100777) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chmod_with_failure! do
|
||||
@@ -1241,7 +1241,7 @@ defmodule FileTest do
|
||||
|
||||
message = ~r"could not change mode for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chmod!(fixture, 0100777)
|
||||
File.chmod!(fixture, 0o100777)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
defmodule CompileSample, do: nil
|
||||
defmodule CompileSample, do: nil
|
||||
|
||||
# Assert that modules compiled are loaded into memory
|
||||
# with the proper destination for the BEAM files.
|
||||
dest = __DIR__ |> Path.join("../../../tmp/#{CompileSample}.beam") |> Path.expand
|
||||
^dest = CompileSample |> :code.which() |> List.to_string
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEvent.StreamTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule SlowHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
end
|
||||
end
|
||||
|
||||
defp notify(:async, manager, event), do: GenEvent.notify(manager, event)
|
||||
defp notify(:sync, manager, event), do: GenEvent.sync_notify(manager, event)
|
||||
defp notify(:ack, manager, event), do: GenEvent.ack_notify(manager, event)
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "ack stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
# Note the length is 4 compared to 5 n the sync case.
|
||||
wait_for_queue_length(pid, 4)
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 1)
|
||||
end
|
||||
|
||||
test "stream/2 with timeout" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
child = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with error/timeout on subscription" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid)) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "stream/2 with manager killed and trap_exit" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:killed, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with manager not alive" do
|
||||
stream = GenEvent.stream(:does_not_exit)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async, :ack], fn mode ->
|
||||
@mode mode
|
||||
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take3, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:take5, Enum.take(stream, 5)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
assert_receive {:take3, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:take5, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^child, :normal}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref())
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, UnknownHandler, [])
|
||||
assert_receive {:EXIT, ^child,
|
||||
{{:swapped, UnknownHandler, _},
|
||||
{Enumerable.GenEvent.Stream, :stop, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
notify(@mode, pid, 1)
|
||||
notify(@mode, pid, 2)
|
||||
notify(@mode, pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,392 +1,486 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEventTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
defmodule LoggerHandler do
|
||||
defmodule ReplyHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event({:log, x}, messages) do
|
||||
{:ok, [x|messages]}
|
||||
def init(:raise) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:messages, messages) do
|
||||
{:ok, Enum.reverse(messages), []}
|
||||
def init({:throw, process}) do
|
||||
{:ok, process}
|
||||
end
|
||||
|
||||
def handle_call(call, state) do
|
||||
super(call, state)
|
||||
def init({:raise, _}) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def init({:swap, {:error, :module_not_found}}) do
|
||||
{:error, :module_not_found_on_swap}
|
||||
end
|
||||
|
||||
def init({:swap, parent}) when is_pid(parent) do
|
||||
send parent, :swapped
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def init({:custom, return}) do
|
||||
return
|
||||
end
|
||||
|
||||
def init({parent, :hibernate}) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def init({parent, trap}) when is_pid(parent) and is_boolean(trap) do
|
||||
Process.flag(:trap_exit, trap)
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_event(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_event(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_event({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_event(event, parent) do
|
||||
send parent, {:event, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_call(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:hibernate, parent) do
|
||||
{:ok, :ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_call({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_call(event, parent) do
|
||||
send parent, {:call, event}
|
||||
{:ok, :ok, parent}
|
||||
end
|
||||
|
||||
def handle_info(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_info(event, parent) do
|
||||
send parent, {:info, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def terminate(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def terminate(:swapped, parent) do
|
||||
send parent, {:terminate, :swapped}
|
||||
parent
|
||||
end
|
||||
|
||||
def terminate(arg, parent) do
|
||||
send parent, {:terminate, arg}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SlowHandler do
|
||||
defmodule DefaultHandler do
|
||||
use GenEvent
|
||||
end
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
defmodule Via do
|
||||
def register_name(name, pid) do
|
||||
Process.register(pid, name)
|
||||
:yes
|
||||
end
|
||||
|
||||
def whereis_name(name) do
|
||||
Process.whereis(name) || :undefined
|
||||
end
|
||||
end
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "start_link/2 and handler workflow" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 0}) == :ok
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 2}) == :ok
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == []
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :whatever) == {:error, :bad_call}
|
||||
assert GenEvent.call(pid, UnknownHandler, :messages) == {:error, :bad_module}
|
||||
|
||||
assert GenEvent.remove_handler(pid, LoggerHandler, []) == :ok
|
||||
test "start/1" do
|
||||
assert {:ok, pid} = GenEvent.start()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
end
|
||||
|
||||
test "start/2 with linked handler" do
|
||||
test "start_link/1" do
|
||||
assert {:ok, pid} = GenEvent.start_link()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:global, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:global, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:global, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:via, Via, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:via, Via, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:via, Via, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.start_link(name: :my_gen_event_name) ==
|
||||
{:error, {:already_started, pid}}
|
||||
end
|
||||
|
||||
test "handles exit signals" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
# Terminates on signal from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
# Terminates on signal from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
assert_receive {:terminate, :stop}
|
||||
|
||||
# Terminates on signal not from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
# Does not terminate on signal not from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
terminator = spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:info, {:EXIT, ^terminator, :shutdown}}
|
||||
refute_received {:terminate, _}
|
||||
end
|
||||
|
||||
defp hibernating?(pid) do
|
||||
Process.info(pid, :current_function) ==
|
||||
{:current_function,{:erlang,:hibernate,3}}
|
||||
end
|
||||
|
||||
defp wait_until(fun, counter \\ 0) do
|
||||
cond do
|
||||
counter > 100 ->
|
||||
flunk "Waited for 1s, but #{inspect fun} never returned true"
|
||||
fun.() ->
|
||||
true
|
||||
true ->
|
||||
receive after: (10 -> wait_until(fun, counter + 1))
|
||||
end
|
||||
end
|
||||
|
||||
defp wake_up(pid) do
|
||||
send pid, :wake
|
||||
assert_receive {:info, :wake}
|
||||
end
|
||||
|
||||
test "hibernates" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), :hibernate})
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
refute hibernating?(pid)
|
||||
|
||||
:ok = GenEvent.call(pid, ReplyHandler, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
:ok = GenEvent.sync_notify(pid, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "add_handler/4" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, {:error, :my_error}}) ==
|
||||
{:error, :my_error}
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, :oops}) ==
|
||||
{:error, {:bad_return_value, :oops}}
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, [], link: true) == :ok
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.add_handler(pid, ReplyHandler, :raise)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [ReplyHandler]
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_added}
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.sync_notify(pid, {:log, 2}) == :ok
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
end
|
||||
|
||||
test "start/2 with linked swap" do
|
||||
test "add_handler/4 with monitor" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
parent = self()
|
||||
|
||||
{mon_pid, mon_ref} = spawn_monitor(fn ->
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true) == :ok
|
||||
send parent, :ok
|
||||
receive after: (:infinity -> :ok)
|
||||
end)
|
||||
|
||||
assert_receive :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
|
||||
# A regular monitor message is passed forward
|
||||
send pid, {:DOWN, make_ref(), :process, self(), :oops}
|
||||
assert_receive {:info, {:DOWN, _, :process, _, :oops}}
|
||||
|
||||
# Killing the monitor though is not passed forward
|
||||
Process.exit(mon_pid, :oops)
|
||||
assert_receive {:DOWN, ^mon_ref, :process, ^mon_pid, :oops}
|
||||
refute_received {:info, {:DOWN, _, :process, _, :oops}}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
end
|
||||
|
||||
test "add_handler/4 with notifications" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
self = self()
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
|
||||
GenEvent.remove_handler(pid, ReplyHandler, :ok)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :normal}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
|
||||
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, nil}}
|
||||
|
||||
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap, monitor: true)
|
||||
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap, monitor: true)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, ^self}}
|
||||
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :shutdown}
|
||||
end
|
||||
|
||||
test "remove_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :ok) ==
|
||||
{:error, :module_not_found}
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:terminate, :ok}
|
||||
assert_receive {:terminate, :ok}
|
||||
|
||||
assert GenEvent.swap_handler(pid, LoggerHandler, [], LoggerHandler, [], link: true) == :ok
|
||||
GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}, monitor: true)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:error, :module_not_found}
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.remove_handler(pid, {ReplyHandler, self()}, :raise)
|
||||
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
end
|
||||
|
||||
test "swap_handler/6" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}) == :ok
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == {:error, :already_added}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :remove_handler) ==
|
||||
{:terminate, :remove_handler}
|
||||
|
||||
# The handler is initialized even when the module does not exist
|
||||
# on swap. However, in this case, we are returning an error on init.
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap) ==
|
||||
{:error, :module_not_found_on_swap}
|
||||
end
|
||||
|
||||
test "notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.notify(pid, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "notify/2 with bad args" do
|
||||
assert GenEvent.notify({:global, :foo}, :bar) == :ok
|
||||
assert GenEvent.notify({:foo, :bar}, :bar) == :ok
|
||||
assert GenEvent.notify(self, :bar) == :ok
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenEvent.notify(:foo, :bar)
|
||||
end
|
||||
end
|
||||
|
||||
test "ack_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.ack_notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.ack_notify(pid, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.ack_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "sync_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.sync_notify(pid, :hello) == :ok
|
||||
assert_received {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.sync_notify(pid, msg) == :ok
|
||||
assert_received {:terminate, :swapped}
|
||||
assert_received :swapped
|
||||
|
||||
assert GenEvent.sync_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_received {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_received {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, :raise) == :ok
|
||||
assert_received {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, :hello) == :ok
|
||||
assert_receive {:call, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, {:custom, {:remove_handler, :ok}}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
|
||||
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
GenEvent.add_handler(pid, {ReplyHandler, self}, {self(), false})
|
||||
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
|
||||
assert GenEvent.call(pid, {ReplyHandler, self()}, msg) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [ReplyHandler]
|
||||
|
||||
assert {:error, {:bad_return_value, :oops}} =
|
||||
GenEvent.call(pid, ReplyHandler, {:custom, :oops})
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert {:error, {%RuntimeError{}, _}} = GenEvent.call(pid, ReplyHandler, :raise)
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/2 with bad args" do
|
||||
Logger.remove_backend(:console)
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
assert GenEvent.add_handler(pid, DefaultHandler, []) == :ok
|
||||
assert GenEvent.call(pid, UnknownHandler, :messages) ==
|
||||
{:error, :module_not_found}
|
||||
assert GenEvent.call(pid, DefaultHandler, :whatever) ==
|
||||
{:error, {:bad_call, :whatever}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
end
|
||||
|
||||
test "start/2 with registered name" do
|
||||
{:ok, _} = GenEvent.start(name: :logger)
|
||||
assert GenEvent.stop(:logger) == :ok
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :sync), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :sync), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :async), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :async), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
|
||||
# One of the subscriptions are gone
|
||||
wait_for_handlers(pid, 1)
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async], fn mode ->
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:to_list, Enum.to_list(stream)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
assert_receive {:take, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:to_list, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with timeout" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
pid = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50, mode: unquote(mode)), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^pid,
|
||||
{:timeout, {Enumerable.GenEvent, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with error/timeout on subscription" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
# Start a subscriber with timeout
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid, mode: unquote(mode))) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Kill and wait until we have 0 handlers
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: unquote(mode)), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:shutdown, {Enumerable.GenEvent, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with cancel streams" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
GenEvent.cancel_streams(stream)
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, LogHandler, [])
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{{:swapped, LogHandler, _},
|
||||
{Enumerable.GenEvent, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with duration" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:duration, Enum.take(stream, 10)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Wait until the handler is gone
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# The stream is not complete but terminated anyway due to duration
|
||||
assert_receive {:duration, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager killed and trap_exit" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:killed, {Enumerable.GenEvent, :next, [_,_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager not alive" do
|
||||
# Start a manager and subscribers
|
||||
stream = GenEvent.stream(:does_not_exit, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:noproc, {Enumerable.GenEvent, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
GenEvent.notify(pid, 1)
|
||||
GenEvent.notify(pid, 2)
|
||||
GenEvent.notify(pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,6 +49,12 @@ defmodule GenServerTest do
|
||||
assert GenServer.cast(pid, {:push, :world}) == :ok
|
||||
assert GenServer.call(pid, :pop) == :world
|
||||
assert GenServer.call(pid, :stop) == :ok
|
||||
|
||||
assert GenServer.cast({:global, :foo}, {:push, :world}) == :ok
|
||||
assert GenServer.cast({:via, :foo, :bar}, {:push, :world}) == :ok
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenServer.cast(:foo, {:push, :world})
|
||||
end
|
||||
end
|
||||
|
||||
test "nil name" do
|
||||
|
||||
@@ -15,17 +15,23 @@ defmodule Inspect.AlgebraTest do
|
||||
"d")
|
||||
end
|
||||
|
||||
def factor(doc, w), do: format(w, 0, [{0, :flat, group(doc)}])
|
||||
def sdoc(doc) do
|
||||
format(group(doc), :infinity)
|
||||
end
|
||||
|
||||
defp render(doc, limit) do
|
||||
format(doc, limit) |> IO.iodata_to_binary
|
||||
end
|
||||
|
||||
test "empty doc" do
|
||||
# Consistence with definitions
|
||||
assert empty == :doc_nil
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(empty, 80) == []
|
||||
assert sdoc(empty) == []
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(empty, 80) == ""
|
||||
assert render(empty, 80) == ""
|
||||
end
|
||||
|
||||
test "break doc" do
|
||||
@@ -37,10 +43,10 @@ defmodule Inspect.AlgebraTest do
|
||||
assert_raise FunctionClauseError, fn -> break(42) end
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(break("_"), 80) == ["_"]
|
||||
assert sdoc(break("_")) == ["_"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(break("_"), 80) == "_"
|
||||
assert render(break("_"), 80) == "_"
|
||||
end
|
||||
|
||||
test "glue doc" do
|
||||
@@ -56,10 +62,10 @@ defmodule Inspect.AlgebraTest do
|
||||
|
||||
test "text doc" do
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor("_", 80) == ["_"]
|
||||
assert sdoc("_") == ["_"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty("_", 80) == "_"
|
||||
assert render("_", 80) == "_"
|
||||
end
|
||||
|
||||
test "space doc" do
|
||||
@@ -78,12 +84,11 @@ defmodule Inspect.AlgebraTest do
|
||||
assert_raise FunctionClauseError, fn -> nest("foo", empty) end
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(nest("a", 1), 80) == ["a"]
|
||||
assert format(2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == ["a", "\n ", "b"]
|
||||
assert sdoc(nest("a", 1)) == ["a"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(nest("a", 1), 80) == "a"
|
||||
assert render(format 2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == "a\n b"
|
||||
assert render(nest("a", 1), 80) == "a"
|
||||
assert render(nest(glue("a", "b"), 1), 2) == "a\n b"
|
||||
end
|
||||
|
||||
test "line doc" do
|
||||
@@ -92,11 +97,10 @@ defmodule Inspect.AlgebraTest do
|
||||
{:doc_cons, "a", {:doc_cons, :doc_line, "b"}}
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(line("a", "b"), 1) == ["a", "\n", "b"]
|
||||
assert factor(line("a", "b"), 9) == ["a", "\n", "b"]
|
||||
assert sdoc(line("a", "b")) == ["a", "\n", "b"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(line(glue("aaa", "bbb"), glue("ccc", "ddd")), 10) ==
|
||||
assert render(line(glue("aaa", "bbb"), glue("ccc", "ddd")), 10) ==
|
||||
"aaa bbb\nccc ddd"
|
||||
end
|
||||
|
||||
@@ -107,12 +111,11 @@ defmodule Inspect.AlgebraTest do
|
||||
assert group(empty) == {:doc_group, empty}
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(glue("a", "b"), 1) == ["a", " ", "b"]
|
||||
assert factor(glue("a", "b"), 9) == ["a", " ", "b"]
|
||||
assert sdoc(glue("a", "b")) == ["a", " ", "b"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(helloabcd, 5) == "hello\na b\ncd"
|
||||
assert pretty(helloabcd, 80) == "hello a b cd"
|
||||
assert render(helloabcd, 5) == "hello\na b\ncd"
|
||||
assert render(helloabcd, 80) == "hello a b cd"
|
||||
end
|
||||
|
||||
test "formatting with infinity" do
|
||||
@@ -120,6 +123,6 @@ defmodule Inspect.AlgebraTest do
|
||||
g = ";"
|
||||
doc = group(glue(s, g, s) |> glue(g, s) |> glue(g, s) |> glue(g, s))
|
||||
|
||||
assert pretty(doc, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
|
||||
assert render(doc, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,20 +92,20 @@ defmodule Inspect.BitStringTest do
|
||||
end
|
||||
|
||||
test :opt_infer do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == ~s(<<101, 114, 105, 99, 193, 109, 106>>)
|
||||
assert inspect(<<"eric">>, binaries: :infer) == ~s("eric")
|
||||
assert inspect(<<"john", 193, "doe">>, binaries: :infer) == ~s(<<106, 111, 104, 110, 193, 100, 111, 101>>)
|
||||
assert inspect(<<"john">>, binaries: :infer) == ~s("john")
|
||||
assert inspect(<<193>>, binaries: :infer) == ~s(<<193>>)
|
||||
end
|
||||
|
||||
test :opt_as_strings do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == ~s("eric\\301mj")
|
||||
assert inspect(<<"eric">>, binaries: :as_strings) == ~s("eric")
|
||||
assert inspect(<<193>>, binaries: :as_strings) == ~s("\\301")
|
||||
assert inspect(<<"john", 193, "doe">>, binaries: :as_strings) == ~s("john\\xC1doe")
|
||||
assert inspect(<<"john">>, binaries: :as_strings) == ~s("john")
|
||||
assert inspect(<<193>>, binaries: :as_strings) == ~s("\\xC1")
|
||||
end
|
||||
|
||||
test :opt_as_binaries do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_binaries) == "<<101, 114, 105, 99, 193, 109, 106>>"
|
||||
assert inspect(<<"eric">>, binaries: :as_binaries) == "<<101, 114, 105, 99>>"
|
||||
assert inspect(<<"john", 193, "doe">>, binaries: :as_binaries) == "<<106, 111, 104, 110, 193, 100, 111, 101>>"
|
||||
assert inspect(<<"john">>, binaries: :as_binaries) == "<<106, 111, 104, 110>>"
|
||||
assert inspect(<<193>>, binaries: :as_binaries) == "<<193>>"
|
||||
end
|
||||
|
||||
@@ -121,6 +121,22 @@ defmodule Inspect.NumberTest do
|
||||
assert inspect(100) == "100"
|
||||
end
|
||||
|
||||
test :decimal do
|
||||
assert inspect(100, base: :decimal) == "100"
|
||||
end
|
||||
|
||||
test :hex do
|
||||
assert inspect(100, base: :hex) == "0x64"
|
||||
end
|
||||
|
||||
test :octal do
|
||||
assert inspect(100, base: :octal) == "0o144"
|
||||
end
|
||||
|
||||
test :binary do
|
||||
assert inspect(86, base: :binary) == "0b1010110"
|
||||
end
|
||||
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0E10) == "1.0e10"
|
||||
@@ -169,20 +185,20 @@ defmodule Inspect.ListTest do
|
||||
end
|
||||
|
||||
test :opt_infer do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :infer) == "[101, 114, 105, 99, 0, 109, 106]"
|
||||
assert inspect('eric', char_lists: :infer) == "'eric'"
|
||||
assert inspect('john' ++ [0] ++ 'doe', char_lists: :infer) == "[106, 111, 104, 110, 0, 100, 111, 101]"
|
||||
assert inspect('john', char_lists: :infer) == "'john'"
|
||||
assert inspect([0], char_lists: :infer) == "[0]"
|
||||
end
|
||||
|
||||
test :opt_as_strings do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_char_lists) == "'eric\\000mj'"
|
||||
assert inspect('eric', char_lists: :as_char_lists) == "'eric'"
|
||||
assert inspect([0], char_lists: :as_char_lists) == "'\\000'"
|
||||
assert inspect('john' ++ [0] ++ 'doe', char_lists: :as_char_lists) == "'john\\0doe'"
|
||||
assert inspect('john', char_lists: :as_char_lists) == "'john'"
|
||||
assert inspect([0], char_lists: :as_char_lists) == "'\\0'"
|
||||
end
|
||||
|
||||
test :opt_as_lists do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_lists) == "[101, 114, 105, 99, 0, 109, 106]"
|
||||
assert inspect('eric', char_lists: :as_lists) == "[101, 114, 105, 99]"
|
||||
assert inspect('john' ++ [0] ++ 'doe', char_lists: :as_lists) == "[106, 111, 104, 110, 0, 100, 111, 101]"
|
||||
assert inspect('john', char_lists: :as_lists) == "[106, 111, 104, 110]"
|
||||
assert inspect([0], char_lists: :as_lists) == "[0]"
|
||||
end
|
||||
|
||||
@@ -190,12 +206,19 @@ defmodule Inspect.ListTest do
|
||||
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
test :unproper do
|
||||
test :improper do
|
||||
assert inspect([:foo | :bar]) == "[:foo | :bar]"
|
||||
|
||||
assert inspect([1,2,3,4,5|42], [pretty: true, width: 1]) == "[1,\n 2,\n 3,\n 4,\n 5 |\n 42]"
|
||||
end
|
||||
|
||||
test :nested do
|
||||
assert inspect(Enum.reduce(1..100, [0], &[&2 , Integer.to_string(&1)]), [limit: 5]) ==
|
||||
"[[[[[[...], ...], \"97\"], \"98\"], \"99\"], \"100\"]"
|
||||
assert inspect(Enum.reduce(1..100, [0], &[&2 | Integer.to_string(&1)]), [limit: 5]) ==
|
||||
"[[[[[[...] | \"96\"] | \"97\"] | \"98\"] | \"99\"] | \"100\"]"
|
||||
end
|
||||
|
||||
test :codepoints do
|
||||
assert inspect('é') == "[233]"
|
||||
end
|
||||
@@ -338,7 +361,7 @@ defmodule Inspect.OthersTest do
|
||||
|
||||
test :regex do
|
||||
"~r/foo/m" = inspect(~r(foo)m)
|
||||
"~r/\\a\\010\\177\\033\\f\\n\\r \\t\\v\\//" = inspect(Regex.compile!("\a\b\d\e\f\n\r\s\t\v/"))
|
||||
"~r/\\a\\x08\\x7F\\x1B\\f\\n\\r \\t\\v\\//" = inspect(Regex.compile!("\a\b\d\e\f\n\r\s\t\v/"))
|
||||
"~r/\\a\\b\\d\\e\\f\\n\\r\\s\\t\\v\\//" = inspect(~r<\a\b\d\e\f\n\r\s\t\v/>)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule IntegerTest do
|
||||
require Integer
|
||||
|
||||
test :odd? do
|
||||
assert Integer.odd?(0) == false
|
||||
assert Integer.odd?(1) == true
|
||||
assert Integer.odd?(2) == false
|
||||
assert Integer.odd?(3) == true
|
||||
assert Integer.odd?(-1) == true
|
||||
assert Integer.odd?(-2) == false
|
||||
assert Integer.odd?(-3) == true
|
||||
assert Integer.is_odd(0) == false
|
||||
assert Integer.is_odd(1) == true
|
||||
assert Integer.is_odd(2) == false
|
||||
assert Integer.is_odd(3) == true
|
||||
assert Integer.is_odd(-1) == true
|
||||
assert Integer.is_odd(-2) == false
|
||||
assert Integer.is_odd(-3) == true
|
||||
end
|
||||
|
||||
test :even? do
|
||||
assert Integer.even?(0) == true
|
||||
assert Integer.even?(1) == false
|
||||
assert Integer.even?(2) == true
|
||||
assert Integer.even?(3) == false
|
||||
assert Integer.even?(-1) == false
|
||||
assert Integer.even?(-2) == true
|
||||
assert Integer.even?(-3) == false
|
||||
assert Integer.is_even(0) == true
|
||||
assert Integer.is_even(1) == false
|
||||
assert Integer.is_even(2) == true
|
||||
assert Integer.is_even(3) == false
|
||||
assert Integer.is_even(-1) == false
|
||||
assert Integer.is_even(-2) == true
|
||||
assert Integer.is_even(-3) == false
|
||||
end
|
||||
|
||||
test :parse do
|
||||
|
||||
@@ -5,11 +5,11 @@ defmodule IO.ANSI.DocsTest do
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
def format_heading(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.strip
|
||||
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.rstrip
|
||||
end
|
||||
|
||||
def format(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.strip
|
||||
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.rstrip
|
||||
end
|
||||
|
||||
test "heading is formatted" do
|
||||
@@ -41,52 +41,47 @@ defmodule IO.ANSI.DocsTest do
|
||||
|
||||
test "* list is converted" do
|
||||
result = format("* one\n* two\n* three\n")
|
||||
assert result == "• one\n• two\n• three\n\e[0m"
|
||||
assert result == " • one\n • two\n • three\n\e[0m"
|
||||
end
|
||||
|
||||
test "* list surrounded by text is converted" do
|
||||
result = format("Count:\n\n* one\n* two\n* three\n\nDone")
|
||||
assert result == "Count:\n\e[0m\n• one\n• two\n• three\n\e[0m\nDone\n\e[0m"
|
||||
assert result == "Count:\n\e[0m\n • one\n • two\n • three\n\e[0m\nDone\n\e[0m"
|
||||
end
|
||||
|
||||
test "* list with continuation is converted" do
|
||||
result = format("* one\n two\n three\n* four")
|
||||
assert result == "• one two three\n• four"
|
||||
result = format("* one\ntwo\n\n three\nfour\n* five")
|
||||
assert result == " • one two\n three four\n\e[0m\n • five\n\e[0m"
|
||||
end
|
||||
|
||||
test "* nested lists are converted" do
|
||||
result = format("* one\n * one.one\n * one.two\n* two")
|
||||
assert result == "• one\n • one.one\n • one.two\n• two"
|
||||
assert result == " • one\n • one.one\n • one.two\n\e[0m\n • two\n\e[0m"
|
||||
end
|
||||
|
||||
test "* lists with spaces are converted" do
|
||||
result = format(" * one\n * two\n * three")
|
||||
assert result == "• one\n• two\n• three"
|
||||
assert result == " • one\n • two\n • three\n\e[0m"
|
||||
end
|
||||
|
||||
test "* lists with code" do
|
||||
result = format(" * one\n two three")
|
||||
assert result == " • one\n\e[36m\e[1m ┃ two three\e[0m\n\e[0m\n\e[0m"
|
||||
end
|
||||
|
||||
test "- list is converted" do
|
||||
result = format("- one\n- two\n- three\n")
|
||||
assert result == "• one\n• two\n• three\n\e[0m"
|
||||
end
|
||||
|
||||
test "- list surrounded by text is converted" do
|
||||
result = format("Count:\n\n- one\n- two\n- three\n\nDone")
|
||||
assert result == "Count:\n\e[0m\n• one\n• two\n• three\n\e[0m\nDone\n\e[0m"
|
||||
end
|
||||
|
||||
test "- list with continuation is converted" do
|
||||
result = format("- one\n two\n three\n- four")
|
||||
assert result == "• one two three\n• four"
|
||||
assert result == " • one\n • two\n • three\n\e[0m"
|
||||
end
|
||||
|
||||
test "+ list is converted" do
|
||||
result = format("+ one\n+ two\n+ three\n")
|
||||
assert result == "• one\n• two\n• three\n\e[0m"
|
||||
assert result == " • one\n • two\n • three\n\e[0m"
|
||||
end
|
||||
|
||||
test "+ and - nested lists are converted" do
|
||||
result = format("- one\n + one.one\n + one.two\n- two")
|
||||
assert result == "• one\n • one.one\n • one.two\n• two"
|
||||
assert result == " • one\n • one.one\n • one.two\n\e[0m\n • two\n\e[0m"
|
||||
end
|
||||
|
||||
test "paragraphs are split" do
|
||||
@@ -101,7 +96,7 @@ defmodule IO.ANSI.DocsTest do
|
||||
|
||||
test "extra whitespace doesn't mess up a following list" do
|
||||
result = format("para1\n \n* one\n* two")
|
||||
assert result == "para1\n\e[0m\n• one\n• two"
|
||||
assert result == "para1\n\e[0m\n • one\n • two\n\e[0m"
|
||||
end
|
||||
|
||||
test "star/underscore/backtick works" do
|
||||
@@ -216,4 +211,34 @@ defmodule IO.ANSI.DocsTest do
|
||||
result = format("[ANSI escape code](http://en.wikipedia.org/wiki/ANSI_escape_code)")
|
||||
assert result == "ANSI escape code (http://en.wikipedia.org/wiki/ANSI_escape_code)\n\e[0m"
|
||||
end
|
||||
|
||||
test "lone thing that looks like a table line isn't" do
|
||||
assert format("one\n2 | 3\ntwo\n") ==
|
||||
"one 2 | 3 two\n\e[0m"
|
||||
end
|
||||
|
||||
test "lone table line at end of input isn't" do
|
||||
assert format("one\n2 | 3") ==
|
||||
"one 2 | 3\n\e[0m"
|
||||
end
|
||||
|
||||
test "two successive table lines are a table" do
|
||||
assert format("a | b\none | two\n") ==
|
||||
"a | b \none | two\n\e[0m" # note spacing
|
||||
end
|
||||
|
||||
test "table with heading" do
|
||||
assert format("column 1 | and 2\n-- | --\na | b\none | two\n") ==
|
||||
"\e[7mcolumn 1 | and 2\e[0m\na | b \none | two \n\e[0m"
|
||||
end
|
||||
|
||||
test "table with formatting in cells" do
|
||||
assert format("`a` | _b_\nc | d") ==
|
||||
"\e[36ma\e[0m | \e[4mb\e[0m\nc | d\n\e[0m"
|
||||
end
|
||||
|
||||
test "table with variable number of columns" do
|
||||
assert format("a | b | c\nd | e") ==
|
||||
"a | b | c\nd | e | \n\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,52 +3,92 @@ Code.require_file "../test_helper.exs", __DIR__
|
||||
defmodule IO.ANSITest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :escape_single do
|
||||
assert IO.ANSI.escape("Hello, %{red}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{red}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
test :format_ansicode do
|
||||
assert IO.chardata_to_string(IO.ANSI.format(:green, true)) ==
|
||||
"#{IO.ANSI.green}#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(:green, false)) ==
|
||||
""
|
||||
end
|
||||
|
||||
test :escape_non_attribute do
|
||||
assert IO.ANSI.escape("Hello %{clear}world!", true) ==
|
||||
"Hello #{IO.ANSI.clear}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello %{home}world!", true) ==
|
||||
"Hello #{IO.ANSI.home}world!#{IO.ANSI.reset}"
|
||||
end
|
||||
|
||||
test :escape_multiple do
|
||||
assert IO.ANSI.escape("Hello, %{red,bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{red, bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{red , bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
end
|
||||
|
||||
test :no_emit do
|
||||
assert IO.ANSI.escape("Hello, %{}world!", false) ==
|
||||
test :format_binary do
|
||||
assert IO.chardata_to_string(IO.ANSI.format("Hello, world!", true)) ==
|
||||
"Hello, world!"
|
||||
assert IO.chardata_to_string(IO.ANSI.format("A map: %{foo: :bar}", false)) ==
|
||||
"A map: %{foo: :bar}"
|
||||
end
|
||||
|
||||
assert IO.ANSI.escape("Hello, %{red,bright}world!", false) ==
|
||||
test :format_empty_list do
|
||||
assert IO.chardata_to_string(IO.ANSI.format([], true)) ==
|
||||
""
|
||||
assert IO.chardata_to_string(IO.ANSI.format([], false)) ==
|
||||
""
|
||||
end
|
||||
|
||||
test :format_ansicode_list do
|
||||
assert IO.chardata_to_string(IO.ANSI.format([:red, :bright], true)) ==
|
||||
"#{IO.ANSI.red}#{IO.ANSI.bright}#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format([:red, :bright], false)) ==
|
||||
""
|
||||
end
|
||||
|
||||
test :format_binary_list do
|
||||
assert IO.chardata_to_string(IO.ANSI.format(["Hello, ", "world!"], true)) ==
|
||||
"Hello, world!"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(["Hello, ", "world!"], false)) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :fragment do
|
||||
assert IO.ANSI.escape("%{red}", true) == "#{IO.ANSI.red}#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape_fragment("", true) == ""
|
||||
test :format_char_list do
|
||||
assert IO.chardata_to_string(IO.ANSI.format('Hello, world!', true)) ==
|
||||
"Hello, world!"
|
||||
assert IO.chardata_to_string(IO.ANSI.format('Hello, world!', false)) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :noop do
|
||||
assert IO.ANSI.escape("") == ""
|
||||
test :format_mixed_list do
|
||||
data = ["Hello", ?,, 32, :red, "world!"]
|
||||
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, true)) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, false)) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :invalid do
|
||||
test :format_nested_list do
|
||||
data = ["Hello, ", ["nested", 32, :red, "world!"]]
|
||||
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, true)) ==
|
||||
"Hello, nested #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, false)) ==
|
||||
"Hello, nested world!"
|
||||
end
|
||||
|
||||
test :format_improper_list do
|
||||
data = ["Hello, ", :red, "world" | "!"]
|
||||
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, true)) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, false)) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :format_nested_improper_list do
|
||||
data = [["Hello, " | :red], "world!" | :green]
|
||||
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, true)) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.green}#{IO.ANSI.reset}"
|
||||
assert IO.chardata_to_string(IO.ANSI.format(data, false)) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :format_fragment do
|
||||
assert IO.chardata_to_string(IO.ANSI.format_fragment([:red, "Hello!"], true)) ==
|
||||
"#{IO.ANSI.red}Hello!"
|
||||
end
|
||||
|
||||
test :format_invalid_sequence do
|
||||
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
|
||||
IO.ANSI.escape("%{brigh}, yes")
|
||||
end
|
||||
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
|
||||
IO.ANSI.escape("%{brigh,red}, yes")
|
||||
IO.ANSI.format([:brigh, "Hello!"], true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,16 +79,6 @@ bar """
|
||||
end
|
||||
end
|
||||
|
||||
test :octals do
|
||||
assert "\1" == <<1>>
|
||||
assert "\12" == "\n"
|
||||
assert "\123" == "S"
|
||||
assert "\123" == "S"
|
||||
assert "\377" == "ÿ"
|
||||
assert "\128" == "\n8"
|
||||
assert "\18" == <<1, ?8>>
|
||||
end
|
||||
|
||||
test :hex do
|
||||
assert "\xa" == "\n"
|
||||
assert "\xE9" == "é"
|
||||
|
||||
@@ -21,16 +21,6 @@ bar '''
|
||||
assert length(' ゆんゆん') == 5
|
||||
end
|
||||
|
||||
test :octals do
|
||||
assert '\1' == [1]
|
||||
assert '\12' == '\n'
|
||||
assert '\123' == 'S'
|
||||
assert '\123' == 'S'
|
||||
assert '\377' == 'ÿ'
|
||||
assert '\128' == '\n8'
|
||||
assert '\18' == [1, ?8]
|
||||
end
|
||||
|
||||
test :hex do
|
||||
assert '\xa' == '\n'
|
||||
assert '\xE9' == 'é'
|
||||
|
||||
@@ -6,6 +6,25 @@ defmodule Kernel.ComprehensionTest do
|
||||
import ExUnit.CaptureIO
|
||||
require Integer
|
||||
|
||||
defmodule PDict do
|
||||
defstruct []
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(struct) do
|
||||
struct
|
||||
end
|
||||
|
||||
def into(struct) do
|
||||
{struct,
|
||||
fn
|
||||
_, {:cont, x} -> Process.put(:into_cont, [x|Process.get(:into_cont)])
|
||||
_, :done -> Process.put(:into_done, true)
|
||||
_, :halt -> Process.put(:into_halt, true)
|
||||
end}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp to_bin(x) do
|
||||
<< x >>
|
||||
end
|
||||
@@ -86,7 +105,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
Process.put(:into_done, false)
|
||||
Process.put(:into_halt, false)
|
||||
|
||||
for x <- 1..3, into: collectable_pdict do
|
||||
for x <- 1..3, into: %PDict{} do
|
||||
x * 2
|
||||
end
|
||||
|
||||
@@ -101,7 +120,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
Process.put(:into_halt, false)
|
||||
|
||||
catch_error(
|
||||
for x <- 1..3, into: collectable_pdict do
|
||||
for x <- 1..3, into: %PDict{} do
|
||||
if x > 2, do: raise("oops"), else: x
|
||||
end
|
||||
)
|
||||
@@ -114,21 +133,13 @@ defmodule Kernel.ComprehensionTest do
|
||||
test "for comprehension with into, generators and filters" do
|
||||
Process.put(:into_cont, [])
|
||||
|
||||
for x <- 1..3, Integer.odd?(x), << y <- "hello" >>, into: collectable_pdict do
|
||||
for x <- 1..3, Integer.is_odd(x), << y <- "hello" >>, into: %PDict{} do
|
||||
x + y
|
||||
end
|
||||
|
||||
assert IO.iodata_to_binary(Process.get(:into_cont)) == "roohkpmmfi"
|
||||
end
|
||||
|
||||
defp collectable_pdict do
|
||||
fn
|
||||
_, {:cont, x} -> Process.put(:into_cont, [x|Process.get(:into_cont)])
|
||||
_, :done -> Process.put(:into_done, true)
|
||||
_, :halt -> Process.put(:into_halt, true)
|
||||
end
|
||||
end
|
||||
|
||||
## List generators (inlined by the compiler)
|
||||
|
||||
test "list for comprehensions" do
|
||||
|
||||
@@ -206,8 +206,8 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :unbound_expr do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^x(1)",
|
||||
'^x(1) = 1'
|
||||
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^is_atom(:foo)",
|
||||
'^is_atom(:foo) = true'
|
||||
end
|
||||
|
||||
test :literal_on_map_and_struct do
|
||||
@@ -232,8 +232,8 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :struct_access_on_body do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: cannot access struct TZ in the same context that defines it " <>
|
||||
"as the struct fields are not yet accessible",
|
||||
"nofile:3: cannot access struct TZ, the struct was not yet defined or the struct " <>
|
||||
"is being accessed in the same context that defines it",
|
||||
'''
|
||||
defmodule TZ do
|
||||
defstruct %{name: "Brasilia"}
|
||||
@@ -269,7 +269,7 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
defmodule GoodStruct do
|
||||
def __struct__ do
|
||||
%{name: "josé"}
|
||||
%{name: "john"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -321,7 +321,17 @@ defmodule Kernel.ErrorsTest do
|
||||
test :undefined_non_local_function do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: undefined function casea/2",
|
||||
'casea foo, do: 1'
|
||||
'casea foo, do: @hello :world'
|
||||
end
|
||||
|
||||
test :invalid_attribute do
|
||||
msg = ~r"cannot inject attribute @foo into function/macro because cannot escape "
|
||||
assert_raise ArgumentError, msg, fn ->
|
||||
defmodule Foo do
|
||||
@foo fn -> end
|
||||
def bar, do: @foo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_fn_args do
|
||||
@@ -330,6 +340,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn 1'
|
||||
end
|
||||
|
||||
test :invalid_escape do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:1: invalid escape \\ at end of file",
|
||||
'1 \\'
|
||||
end
|
||||
|
||||
test :function_local_conflict do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: imported Kernel.&&/2 conflicts with local function",
|
||||
@@ -558,7 +574,7 @@ defmodule Kernel.ErrorsTest do
|
||||
test :invalid_for_without_generators do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: for comprehensions must start with a generator",
|
||||
'for x, do: x'
|
||||
'for is_atom(:foo), do: :foo'
|
||||
end
|
||||
|
||||
test :invalid_for_bit_generator do
|
||||
@@ -580,19 +596,61 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
|
||||
test :end_of_expression do
|
||||
# All valid examples
|
||||
Code.eval_quoted '''
|
||||
1;
|
||||
2;
|
||||
3
|
||||
|
||||
(;)
|
||||
(;1)
|
||||
(1;)
|
||||
(1; 2)
|
||||
|
||||
fn -> 1; 2 end
|
||||
fn -> ; end
|
||||
|
||||
if true do
|
||||
;
|
||||
end
|
||||
|
||||
try do
|
||||
;
|
||||
catch
|
||||
_, _ -> ;
|
||||
after
|
||||
;
|
||||
end
|
||||
'''
|
||||
|
||||
# All invalid examples
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'1+;\n2'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'max(1,;2)'
|
||||
end
|
||||
|
||||
test :new_line_error do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:3: syntax error before: newline",
|
||||
"nofile:3: syntax error before: eol",
|
||||
'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
test :invalid_var_or_function_on_guard do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: unknown variable something_that_does_not_exist or " <>
|
||||
"cannot invoke function something_that_does_not_exist/0 inside guard",
|
||||
"nofile:4: unknown variable something_that_does_not_exist or " <>
|
||||
"cannot invoke local something_that_does_not_exist/0 inside guard",
|
||||
'''
|
||||
case [] do
|
||||
[] when something_that_does_not_exist == [] -> :ok
|
||||
defmodule ErrorsTest do
|
||||
def bar do
|
||||
case [] do
|
||||
[] when something_that_does_not_exist == [] -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
'''
|
||||
end
|
||||
@@ -620,8 +678,14 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :invalid_function_on_match do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
|
||||
'case [] do; something_that_does_not_exist() -> :ok; end'
|
||||
"nofile:3: cannot invoke local something_that_does_not_exist/0 inside match",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
def fun do
|
||||
case [] do; something_that_does_not_exist() -> :ok; end
|
||||
end
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_remote_on_match do
|
||||
|
||||
@@ -55,11 +55,6 @@ defmodule Kernel.ExpansionTest do
|
||||
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Source"
|
||||
end
|
||||
|
||||
test "__aliases__: expands to elixir_aliases on runtime" do
|
||||
assert expand(quote do: hello.World) ==
|
||||
quote do: :elixir_aliases.concat([hello(), :World])
|
||||
end
|
||||
|
||||
## =
|
||||
|
||||
test "=: sets context to match" do
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user