Compare commits

...
367 Commits
Author SHA1 Message Date
José Valim 5b4497fa6b Release v1.0.0-rc1 2014-08-30 13:33:02 -04:00
José Valim 612e124ec9 Add tests for sync_threshold 2014-08-29 08:13:53 -04:00
José Valim abd2b35738 Get rid of Logger timeout config, closes #2724 2014-08-29 08:01:03 -04:00
José Valim 2afb875099 Merge pull request #2725 from MSch/document-compile-attr-accumulation
Document that @compile accumulates.
2014-08-28 14:10:41 -04:00
Martin Schürrer 88507f65f8 Document that @compile accumulates. 2014-08-28 20:05:56 +02:00
José Valim 275d1f5494 Straight up and unify impls of clean, deps.clean and deps.unlock 2014-08-28 13:00:36 -04:00
José Valim e6420c8d13 Merge pull request #2715 from nurugger07/remove_unused_deps
Add an unused flag to deps.clean & deps.unlock
2014-08-28 11:38:46 -04:00
José Valim 2af00f4c33 Merge pull request #2723 from edgurgel/fix-private-function-on-algebra
Make do_surround_many private on Inspect.Algebra
2014-08-27 16:08:55 -04:00
Eduardo Gurgel 0fe86e77c1 Make do_surround_many private on Inspect.Algebra 2014-08-28 08:01:32 +12:00
José Valim 1ca451f779 Normalize is_guard checks 2014-08-27 15:28:31 -04:00
José Valim ebe0c8cf8e Update CHANGELOG 2014-08-27 12:21:12 -04:00
José Valim de9feb26d5 Deprecate x.Alias syntax 2014-08-27 12:17:15 -04:00
José Valim b8528f6c8e Remove warnings from the test suite 2014-08-25 14:42:24 -04:00
Alexei Sholik c9fbbac391 Add version checks and proper error formatting to escripts. Closes #2709
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-25 14:26:54 -04:00
José Valim 98e1b4ccf8 Ensure we tag the destination when compiling through mix
Closes #2533
2014-08-25 14:26:10 -04:00
Alexei Sholik ae19491620 Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2014-08-23 23:24:48 +03:00
Alexei Sholik 64f3760b19 Add missing fixtures for escript test 2014-08-23 23:16:29 +03:00
José Valim 0b38bc050f Demonitor and flush when appropriate 2014-08-23 15:56:38 -04:00
José Valim 9431338654 Add timeouts to ExUnit, closes #2660 2014-08-23 15:55:33 -04:00
José Valim 88be750a32 import_config/1 now accepts wildcards 2014-08-23 15:14:12 -04:00
José Valim 032c8b8291 Update CHANGELOG 2014-08-23 14:42:52 -04:00
José Valim 2d90d8ac60 Remove code duplication on watcher 2014-08-23 14:38:28 -04:00
José Valim a8b2cb99ee Use to_string when possible on Logger.log 2014-08-23 14:34:51 -04:00
José Valim 68274311fb Merge pull request #2712 from fishcakez/logger_rc
Fix logger watcher race condition
2014-08-23 14:34:05 -04:00
José Valim 88f84f80b5 Merge pull request #2713 from fishcakez/logger_eh_racecon
Fix race condition in Logger.ErrorHandlerTest
2014-08-23 14:32:48 -04:00
James Fish 2bf898e8fe Fix race condition in Logger.ErrorHandlerTest
When crashing the error logger handler, the handler's watcher could log
its message before Logger was flushed, causing the watcher's log
message to appear in the first, rather than second, capture_log.
2014-08-23 19:20:06 +01:00
José Valim d45f451943 Allow configurable timeout and catch exits on logger 2014-08-23 14:09:11 -04:00
James Fish c0a075724f Fix logger watcher race condition
Previously a backend could be removed from logger and the watcher would
not have exited before the remove_backend/1 call returned. This meant
that a new watcher could not always be started because the child
(watcher) might still exist but be about to die.
2014-08-23 18:59:13 +01:00
José Valim f1a39375c9 Do not crash the caller when Logger is not available
Instead, return :ok | {:error, :not_available} and leave it up
to the caller to decide.
2014-08-23 13:43:58 -04:00
Alexei Sholik b9c1b22163 Add protocol consolidation support to escript.build. Closes #2699 2014-08-23 20:40:56 +03:00
José Valim be75e3be17 Purge in memory in modules before consolidating, closes #2700 2014-08-23 13:32:19 -04:00
José Valim 9793caa1ef Ensure we automatically load deps on compile 2014-08-23 12:22:38 -04:00
José Valim 0792fe2c52 Merge pull request #2708 from alco/typo
Fix a typo in Mix.Project.compile_path doc
2014-08-22 12:02:31 -04:00
Alexei Sholik 4408aa3450 Fix a typo in Mix.Project.compile_path doc 2014-08-22 18:33:24 +03:00
Eric Meadows-Jönsson e4ba8d5d21 Mix should not depend directly on Logger 2014-08-22 15:56:25 +02:00
Eric Meadows-Jönsson 689f14a867 Always compile project if task cannot be found
Closes #2704.
2014-08-21 15:26:58 +02:00
Eric Meadows-Jönsson aa5b414e8e Merge pull request #2703 from Kosmas/master
Update CONTRIBUTING.md
2014-08-21 12:19:11 +02:00
Kosmas Chatzimichalis 07467a733b Update CONTRIBUTING.md
Corrected 'should be send' to 'should be sent'
2014-08-21 11:34:34 +02:00
Alexei Sholik 74391b793c Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2014-08-20 20:56:20 +03:00
Alexei Sholik f0b8bc1756 Use common code for Elixir and Erlang escripts 2014-08-20 20:38:24 +03:00
José Valim 3e54b95bf2 Fix the build 2014-08-20 11:30:30 -04:00
José Valim 313d26cb3b Use task start to get better reports 2014-08-20 10:28:11 -04:00
José Valim 44b85be014 Add Task.start/1 and Task.start/3 2014-08-20 10:27:23 -04:00
José Valim e344b1f9d8 Add a small note to UndefinedFunctionError when the module is not available 2014-08-20 10:22:36 -04:00
Eric Meadows-Jönsson f5f4205969 Merge pull request #2698 from Kosmas/master
Update CONTRIBUTING.md
2014-08-20 15:08:20 +02:00
Kosmas Chatzimichalis 538c717ded Update CONTRIBUTING.md
renamed 'issues tracker' to 'issue tracker' for consistency
2014-08-20 14:54:50 +02:00
Eric Meadows-Jönsson 8dd81e661a Merge pull request #2697 from hamiltop/patch-1
Fix docs (missing indentation in examples)
2014-08-20 11:24:32 +02:00
hamiltop 26b61b98f9 Fix docs (missing indentation in examples) 2014-08-19 20:28:24 -07:00
Eric Meadows-Jönsson ab9450d92f Don't print the underlying type for @opaque
Closes #2612
2014-08-19 17:14:30 +02:00
Eric Meadows-Jönsson a4a23b9a93 Fix Enum.traverse deprecated warning doctest 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 2f83ea5a7f Wrap erlang macros in () 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 7940d223ec Move elixir.hrl to src/ 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson c6fa951520 Better error message on nbsp 2014-08-19 17:00:58 +02:00
José Valim e5c8194a70 Rename :add_stream_handler to :add_process_handler 2014-08-19 09:55:26 -04:00
Eric Meadows-Jönsson 4a7e3dd11f Document alias arguments 2014-08-19 14:16:19 +02:00
José Valim 638fccb79d Remove duration from GenEvent.Stream 2014-08-19 07:34:49 -04:00
José Valim e9d84ec43b Allow gen_event_EXIT notifications to be disabled for streams 2014-08-19 07:07:23 -04:00
Eric Meadows-Jönsson bb35b9afb1 Include makefile in precompiled zip 2014-08-18 23:11:27 +02:00
Eric Meadows-Jönsson 1496c04592 Improve error message for invalid typespec 2014-08-18 23:08:26 +02:00
José Valim 3e9725a5a8 Make tests that remove the logging backend sync 2014-08-18 16:51:38 -04:00
José Valim 1565ec7658 Update CHANGELOG 2014-08-18 16:43:17 -04:00
José Valim f1aa2f01c7 Merge pull request #2695 from elixir-lang/jv-ge
GenEvent++
2014-08-18 16:33:11 -04:00
José Valim 7284542116 Split GenEvent.Stream into its own file 2014-08-18 16:28:25 -04:00
José Valim 4f17e8fd1c Also tag notify messages 2014-08-18 16:20:25 -04:00
José Valim f536507b9d Make the mode a property of the sender 2014-08-18 15:38:55 -04:00
José Valim 592300a0f0 Ensure Logger watcher monitors event manager 2014-08-18 09:18:28 -04:00
Alexei Sholik 28b1bd16de Document the :language setting 2014-08-18 16:12:15 +03:00
José Valim 19fdd84f80 Update docs 2014-08-17 19:48:50 -04:00
José Valim 631e2a051a Document lack of guaranteed on monitored handlers 2014-08-17 18:44:06 -04:00
José Valim df0f7b5f95 Fix failures in logger 2014-08-17 18:36:59 -04:00
José Valim a0dfc4160e Ensure proper whereis expansion 2014-08-17 18:28:14 -04:00
José Valim ee8331023e Do not add duplicated handlers 2014-08-17 16:55:11 -04:00
José Valim a2ee71f5ab Implement some of fishcakez feedback 2014-08-17 13:18:55 -04:00
José Valim 60163194e2 Improve coverage for GenEvent 2014-08-17 09:20:02 -04:00
José Valim ba37682300 Merge pull request #2691 from herenowcoder/patch-1
fix doc typo in Macro.expand/2
2014-08-17 08:57:53 -04:00
Wojciech Kaczmarek 15265b9352 fix doc typo in Macro.expand/2 2014-08-17 14:55:25 +02:00
Alexei Sholik 0a929310de Support building Erlang-based escripts in mix escript.build 2014-08-17 15:19:42 +03:00
José Valim a8670c1cfa Do not pre-compile a project if an alias was found
Closes #2689
2014-08-17 06:10:20 -04:00
Alexei Sholik ebd18d45fa Only add :elixir application for Elixir projects in compile.app task 2014-08-17 01:22:33 +03:00
José Valim ccbfec611a Merge pull request #2684 from alco/mix-compile-erlang
Properly handle compilation errors in Erlang compiler
2014-08-16 17:05:00 -04:00
Alexei Sholik 98a0999c2e Properly handle compilation errors in Erlang compiler 2014-08-16 23:45:44 +03:00
José Valim e504142d3a Merge pull request #2686 from billgathen/patch-1
Docs: Using autocomplete for function discovery in IEx
2014-08-16 15:58:01 -04:00
José Valim c04e13169f Deprecate Collectable.empty and Enum.traverse
The current implementation has three issues:

1. It is slow as we can't reuse the data structure but rather
   build a new one from scratch

2. Due to 1., trees can't be traversed

3. empty/1 is unable to deal with resources
2014-08-16 15:46:58 +02:00
José Valim 96a4bff67b Use the new resource acc on GenEvent.stream/2 2014-08-16 15:41:30 +02:00
José Valim 46de5ea462 Make Stream.resource/3 more like Stream.transform/2
With this change, we allow the accumulator to be passed
to after_fun once iteration is done.

This, however, introduces a deprecation on the old
format used by Stream.resource/3.
2014-08-16 15:35:56 +02:00
Bill Gathen 22257b8342 Using autocomplete for function discovery
I had to go to the mailing list to discover this trick. Knowing it's autocomplete supplying the information makes it obvious in hindsight, but I had it in my head that it would be part of the h() command. It might also be worth adding a mention of autocomplete to the h(IEx) page, as well, but since this is likely first help page a new user would come across, this may be enough.
2014-08-16 08:59:18 -04:00
José Valim a0be9fe272 Improve Logger formatter defaults
* Make colors in the docs consistent with the ones in the code
* Add new lines before and after each message
2014-08-15 18:30:30 +02:00
José Valim 1fd5a2a38e Use monitored handlers instead of linked ones 2014-08-15 18:02:47 +02:00
José Valim 13aec18bd5 Update docs 2014-08-15 13:57:22 +02:00
José Valim a85938d092 Fix the build 2014-08-15 13:54:08 +02:00
José Valim 0d4af57abe Port GenEvent to Elixir for upcoming extensions 2014-08-15 13:16:14 +02:00
José Valim e00db53112 Talk about correctness first, performance later 2014-08-14 22:09:00 +02:00
José Valim b941618a10 Port the :gen_event client API to Elixir
The only difference is that notify is made similar to
GenServer's cast where an unknown atom name does not
trigger an error.
2014-08-13 18:20:10 +02:00
Eric Meadows-Jönsson d895c762a1 Add --force option to 'mix local.rebar' 2014-08-13 17:11:28 +02:00
Eric Meadows-Jönsson 427c4b01c9 Ensure load_tasks return no duplicates 2014-08-13 17:10:40 +02:00
Eric Meadows-Jönsson 9d5a728cb8 Output mix version on 'mix -v' 2014-08-13 17:10:40 +02:00
José Valim 1ed99d4ac7 Improve error message for undefined struct 2014-08-13 09:19:26 +02:00
José Valim ceb17fac7d Use GenServer when possible 2014-08-12 23:22:51 +02:00
José Valim c638f45542 Merge pull request #2674 from koudelka/unquote-doc-fix
Minor unquote/1 doc fix.
2014-08-12 21:40:02 +02:00
Michael Shapiro c09905d537 Minor unquote/1 doc fix. 2014-08-12 12:23:08 -07:00
José Valim 117a428030 Merge pull request #2668 from reset/fix-nocolor
fix --no-color / --color option for `mix test`
2014-08-12 19:32:33 +02:00
Jamie Winsor 2ada7c40d7 fix --no-color / --color option for mix test 2014-08-12 10:32:10 -07:00
José Valim cd162bf3d6 Do not inherit bugs on GenServer calls and casts
Prior to this change, we were inheriting OTP bug where
bad arguments in call were treated as exits.

We also fixed the bug in cast where messages could be
delivered out of order. It is preferrable to wait on
connection.
2014-08-12 17:30:25 +02:00
José Valim e6e71675ac Make a distinction between eol and ; 2014-08-12 16:27:45 +02:00
José Valim c54e0caf6f Merge pull request #2670 from chyndman/cygpath-quotes
Use quotes to avoid space-in-path issues with cygpath
2014-08-12 09:34:41 +02:00
José Valim b64552780e Merge pull request #2671 from lexmag/no-purge
No need in some purges in `Kernel.WarningTest`
2014-08-12 09:34:25 +02:00
Eric Meadows-Jönsson add8c66e82 Merge pull request #2673 from alexrp/master
Clarify match?/2 docs.
2014-08-12 02:02:35 +02:00
Alex Rønne Petersen aecbd2c16a Clarify match?/2 docs. 2014-08-12 01:54:08 +02:00
Aleksei Magusev 32a649c1cd No need for Stream. prefix 2014-08-12 03:31:03 +04:00
Aleksei Magusev a1e657cb75 No need in some purges in Kernel.WarningTest 2014-08-12 03:06:48 +04:00
Chris Hyndman 6bb4c4de03 Use quotes to avoid space-in-path issues with cygpath 2014-08-11 15:13:50 -07:00
José Valim ea5b56d434 Merge pull request #2667 from MSch/fix-new-config-exs
Fix example in config/config.exs that's created for new projects.
2014-08-11 20:51:03 +02:00
Martin Schürrer a4082748cf Fix example in config/config.exs that's created for new projects. 2014-08-11 20:43:36 +02:00
José Valim 4d763846c3 Ensure code escape \ works in IEx 2014-08-11 10:05:24 +02:00
José Valim d56cc88f5f Start v0.15.2-dev 2014-08-11 10:05:24 +02:00
José Valim 5e27b7b72b Merge pull request #2666 from hamiltop/stream_cycle_performance
Add special case for lists in Stream.cycle
2014-08-11 09:53:38 +02:00
Peter Hamilton 260d6d16ab Add special case for lists in Stream.cycle 2014-08-11 00:38:53 -07:00
José Valim 1a82f126a4 Merge pull request #2664 from hamiltop/add_timer_and_interval_streams
Add Stream.interval/1 and Stream.timer/1
2014-08-11 08:16:51 +02:00
Peter Hamilton 3cf3eee2ea Clean up interval/1 and timer/1 docs 2014-08-10 16:03:43 -07:00
Peter Hamilton 9e8a41b2d3 Add Stream.interval/1 and Stream.timer/1 2014-08-10 11:25:53 -07:00
José Valim a54d9212b8 Release v0.15.1 2014-08-10 19:06:49 +02:00
José Valim ee33608f75 Update CHANGELOG 2014-08-10 19:05:45 +02:00
José Valim 6e91275782 Merge pull request #2663 from lexmag/fix-pipe_op
Bind value to proper name in `elixir_tokenizer`
2014-08-10 17:56:59 +02:00
Aleksei Magusev c6f27541c1 Bind value to proper name in elixir_tokenizer 2014-08-10 19:41:13 +04:00
José Valim a2662a4b51 Ensure elixir and iex works on MinGW 2014-08-10 15:11:18 +02:00
José Valim 1d29262949 Check for terminal on both stdout and stderr 2014-08-10 14:40:46 +02:00
José Valim 1d655fa8f2 Check for terminal support from Elixir command line 2014-08-10 13:57:41 +02:00
José Valim 4bec785f4c Also support :sync mode in GenEvent, make :ack one the default 2014-08-10 12:00:13 +02:00
José Valim e3273f731f Fix build 2014-08-09 21:50:24 +02:00
José Valim a8564a7045 Deprecate functions as collectable 2014-08-09 21:32:32 +02:00
José Valim e2bac56556 Reply to GenEvent manager from stream right away
This way the GenEvent can start processing the other
handlers, reducing the processing time for each event
from sum(handlers) to max(handlers).
2014-08-09 21:12:51 +02:00
José Valim a38e6b7a0d Ensure aliases are invoked for recursive tasks
Closes #2650
2014-08-09 13:59:32 +02:00
José Valim 72199ca71d Fix build 2014-08-09 13:47:48 +02:00
José Valim 9420945f59 Leave it to the application to start Logger after compilation
Closes #2649
2014-08-09 13:38:27 +02:00
Eric Meadows-Jönsson 4ca1736f86 Accept more forms of git versions
Fixes the parsing of “git version 1.9.3 (Apple Git-50)”.
Closes #2653.
2014-08-09 11:16:25 +02:00
José Valim fd85ca443e Merge pull request #2652 from chyndman/elixir-bat-iex-startcli
Suppress "-s elixir start_cli" for IEx
2014-08-09 10:36:32 +02:00
Chris Hyndman 187e86749e Suppress "-s elixir start_cli" for IEx 2014-08-08 18:13:42 -07:00
José Valim e04a7aef6a Merge pull request #2651 from fishcakez/translator_format
Improve translator formatting
2014-08-08 22:19:23 +02:00
José Valim 0b1c8c92c0 Set -noshell on bin/iex 2014-08-08 22:11:06 +02:00
Chris Hyndman bf270d144f Prepend -noshell and friends always within Cygwin
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-08 22:11:06 +02:00
James Fish 2c009d1ec3 Improve translator formatting
* Improve application stop reasons
* Include stacktrace in crash reports
2014-08-08 21:08:36 +01:00
José Valim 08555f545c Merge pull request #2648 from sunaku/makefile-message-clarity
Makefile: shorten tip about Unicode embedding time
2014-08-08 20:41:26 +02:00
Suraj N. Kurapati 65d3af694b Makefile: shorten tip about Unicode embedding time
The estimate about this step taking "up to a minute" really depends on
the machine we're compiling on.  For example, on an old or underpowered
machine, it may take _longer_ than a minute.  So this patch changes the
wording accordingly and puts the reason for the delay at the beginning.
2014-08-08 09:07:26 -07:00
José Valim 8aa43eaedd Merge pull request #2647 from jwarwick/task_docs
Fixed doc typos in Task and URI
2014-08-08 16:30:12 +02:00
John Warwick 334c732691 Fixed doc typos in Task and URI 2014-08-08 10:23:36 -04:00
José Valim 24587110ad Merge pull request #2646 from jwarwick/logger_type
Fixed Logger.configure_backend spec
2014-08-08 16:18:46 +02:00
John Warwick b98421fe51 Fixed Logger.configure_backend spec 2014-08-08 10:11:39 -04:00
José Valim 752e3f8bc3 Update CHANGELOG 2014-08-08 13:31:40 +02:00
José Valim 6c8e70b1f8 Load handlers inside supervisor as they are now dynamic 2014-08-08 13:21:55 +02:00
José Valim c6fbfd2a67 Copy and check elixir version in archives
Closes #2396
2014-08-08 12:53:50 +02:00
José Valim 56ea6e52e4 Update configure_backend docs 2014-08-08 11:39:10 +02:00
José Valim 4726e0af91 Persist calls to add_backend and remove_backend 2014-08-08 11:38:18 +02:00
José Valim ee82679908 Merge pull request #2645 from tomkadwill/doc_false_handle_info
Added @doc false to handle_info function
2014-08-08 11:25:56 +02:00
Tom Kadwill aedd5a938f Added @doc false to handle_info function 2014-08-08 10:21:26 +01:00
José Valim 2d9c3fb6f0 Print warnings when used ? with characters with escape codes
Closes #2640
2014-08-08 11:13:13 +02:00
José Valim 6f07f4af22 Create the config directory before writing to config file 2014-08-08 10:23:54 +02:00
José Valim 656bd640e6 Load config/config.exs into escript
Closes #2643
2014-08-08 10:13:17 +02:00
José Valim bc2cb15a96 Add Logger.flush/0 2014-08-07 20:14:04 +02:00
José Valim dbf205b311 Deprecate Module.function/3 2014-08-07 20:00:57 +02:00
José Valim 0e0bb1c2a7 Do not raise conflicts on imports used from quotes, closes #2589 2014-08-07 18:09:19 +02:00
José Valim 736406a9b3 Do not allow invalid quoted expression to leak
Closes #2499
2014-08-07 17:42:05 +02:00
José Valim 886917753e Ensure we can escape bitstrings, add Macro.validate/1 2014-08-07 17:42:05 +02:00
José Valim f29889da99 Make environment explicit on escript build 2014-08-07 17:42:05 +02:00
José Valim aee33eac6d Merge pull request #2638 from jwarwick/logger_docs
Fixing typos in Logger module docs
2014-08-07 16:16:30 +02:00
John Warwick d8428ba1e6 Fixing typos in Logger module docs 2014-08-07 10:11:36 -04:00
José Valim 1f725b0d2c Merge pull request #2637 from fishcakez/improve_sasl_reports
Improve Logger's SASL report translation
2014-08-06 22:37:22 +02:00
James Fish 38f895cc13 Improve Logger's SASL report translation
* Pattern match keyword lists for faster parsing
* Support :supervisor_bridge reports
* Support missing :initial_call in :crash_report
* Improve formatting of registered name in :crash_report output
* More tests
2014-08-06 21:21:36 +01:00
José Valim 3fc07eff8e Merge pull request #2636 from voltone/uri-recomposition
Improved RFC compliance for URI to_string
2014-08-06 21:20:48 +02:00
Bram Verburg aa244c30fc Improved RFC compliance for URI to_string 2014-08-06 21:55:48 +03:00
José Valim f8647dae3e Ensure backends receive the id when added, closes #2635 2014-08-06 19:56:22 +02:00
José Valim 39fa2878ce Fix list rendering on ANSI docs 2014-08-06 19:42:26 +02:00
José Valim 5b5b95eb11 Update CHANGELOG 2014-08-06 13:27:54 +02:00
José Valim 67caa3ebb3 Fix padding in Logger 2014-08-06 13:20:01 +02:00
José Valim d09cd173bf Merge pull request #2634 from fishcakez/sasl_reports
Add SASL log forwarding option to Logger
2014-08-06 12:05:59 +02:00
José Valim 79fb40ab6a Correctly follow code specs when loading binaries
If we are compiling the module, we will load it with the
output of its beam file. If the module exists only in
memory, :code.which/1 will return `:in_memory`.

Closes #2533
2014-08-06 12:05:55 +02:00
José Valim ae2cb6bc43 Add the Windows wiki link to the README 2014-08-06 12:05:55 +02:00
James Fish 413b815ecc Add tests for SASL report logging 2014-08-06 09:47:17 +01:00
James Fish df89b5022e Add SASL log forwarding option to Logger 2014-08-05 21:22:50 +01:00
José Valim 90729655ee Merge pull request #2633 from lexmag/not-match
Use `not` instead of `!` in `IO.ANSI.enabled?/0`
2014-08-05 18:12:12 +02:00
Aleksei Magusev 171e9b3659 Use not instead of ! in IO.ANSI.enabled?/0 2014-08-05 19:29:39 +04:00
José Valim 3e256fcc1e Merge pull request #2631 from chyndman/contributing-windows
Remove Windows-related CONTRIBUTING.md section (now in Wiki)
2014-08-05 09:18:27 +02:00
Chris Hyndman 29ebbeda93 Remove Windows-related CONTRIBUTING.md section (now in Wiki) 2014-08-04 15:14:02 -07:00
José Valim 214d056911 Support on log messages 2014-08-04 20:38:13 +02:00
pragdave 9f065a77ca align the messages between log messages with different length level text. Added an extra format $levelpad, which is a space if the level is "warn" or "info"
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-04 20:32:43 +02:00
José Valim e70ac9768e Fix iex test 2014-08-04 19:41:37 +02:00
José Valim ec6c98ef47 Merge Logger.metadata 2014-08-04 19:03:24 +02:00
José Valim 7d0f92e12a Fix docs and specs introspection in iex 2014-08-04 19:03:24 +02:00
José Valim 8bf6193ccc Merge pull request #2630 from scrogson/patch-2
Doc correction for GenEvent
2014-08-04 18:51:15 +02:00
Sonny Scroggin 50f5dc76fb Doc correction for GenEvent 2014-08-04 11:45:22 -05:00
José Valim b9000999c9 Use the Logger PID to avoid race conditions on shutdown, closes #2628 2014-08-04 15:21:06 +02:00
José Valim 52be06c314 Do not expand args for unknown functions, closes #2387 2014-08-04 13:46:49 +02:00
José Valim 3da560f10c Update Mix.Dep.Lock documentation 2014-08-04 13:18:58 +02:00
José Valim f0f25300a0 Load mix deps only when there is a need to use them
This helps speed up peformance to many tasks. Tasks like
clean, help and compile --list must now explicitly call
loadpaths with checks disabled so they can still show
data coming from dependencies but without causing crashes.
2014-08-04 13:04:56 +02:00
José Valim c8a3a619af Update CHANGELOG 2014-08-04 10:45:59 +02:00
José Valim 25f64051ba Remove documentation from defimpls 2014-08-04 10:45:00 +02:00
José Valim 5efa6bb1c4 Since IO.ANSI.enabled? is a fast operation, no need to prefetch it on IEx 2014-08-04 10:43:02 +02:00
José Valim cb48f90f28 Merge pull request #2627 from smpallen99/inspect_base
Add integer base support to inspect
2014-08-04 10:42:34 +02:00
Stephen M. Pallen 3ba93b1186 Add integer base support to inspect
Option :base for inspect/2 supports :binary, :octal, :decimal, :hex.
Includes tests and documentation.
2014-08-03 20:17:58 -04:00
José Valim 7babcd0ed2 Merge pull request #2625 from niku/doc-about-.iex.exs
Improve IEx docs about .iex.exs
2014-08-03 20:04:12 +02:00
José Valim b6c1f2b2e1 Merge pull request #2624 from lexmag/pathtype
Reuse pathtype detection in `Path` module
2014-08-03 20:03:25 +02:00
Johnny Winn 5f853f8e71 Implement flag to clean unused dependencies
This addresses issues #2569
https://github.com/elixir-lang/elixir/issues/2569
2014-08-03 13:42:51 -04:00
Aleksei Magusev 6cccf3ac22 Reuse pathtype detection in Path module 2014-08-03 21:20:11 +04:00
niku 5b9b186d4b Improve IEx docs about .iex.exs 2014-08-03 23:02:03 +09:00
José Valim 57898bee03 Do not normalize paths on join, closes #2500 2014-08-03 14:59:38 +02:00
José Valim 6a6ba9f50d Remove deprecated code 2014-08-02 22:35:57 +02:00
José Valim 1e3135514f Start v0.15.1-dev 2014-08-02 21:57:43 +02:00
José Valim 2f2a52a648 Release v0.15.0 2014-08-02 21:49:21 +02:00
José Valim 89fd8f305f Print a nice prelude if we fail to load a project, closes #2430 2014-08-02 15:19:53 +02:00
José Valim 353c043d8c Use Mix.Shell.printable_app_name 2014-08-02 14:50:55 +02:00
José Valim 54a40efeec Push the given app name as default on Mix.Project 2014-08-02 14:29:12 +02:00
José Valim f781795032 Bump hex requirement 2014-08-02 14:04:07 +02:00
José Valim 54ffb5768d Add a test case for printing samples on IO shell 2014-08-02 12:13:36 +02:00
José Valim 8fc068821f Print app name on Mix.shell.cmd/1/2 2014-08-02 12:08:42 +02:00
Eric Meadows-Jönsson 1f91fd351e Merge pull request #2619 from knewter/feature/2616_print_app_name_before_compiling_makefile_dependencies
Print app name before compiling Makefile deps
2014-08-02 03:00:03 +02:00
Josh Adams ab4399323f Print app name before compiling Makefile deps
This should close out #2616.
2014-08-01 19:40:11 -05:00
José Valim 3b34a57bd2 Avoid race conditions in the struct detection 2014-08-01 23:58:02 +02:00
José Valim 62a469dc23 Improve mix new error messages 2014-08-01 21:36:28 +02:00
José Valim 8b0c4127ef Merge pull request #2618 from patrickgombert/mix-new
Validate app and module on mix new
2014-08-01 21:22:56 +02:00
Patrick Gombert dc4b7c89ea Validate app and module on mix new 2014-08-01 14:15:15 -05:00
José Valim b5febd991c Allow elixirc_paths to also be given through the command line
Once given, the command line overrides the project
configuration in `mix compile.elixir`.

Closes #2557
2014-08-01 19:52:32 +02:00
José Valim f1323ade7c Accept upcoming hex formats 2014-08-01 18:28:56 +02:00
José Valim 89561c321d Ensure defoverridable is not order dependant, closes #2615 2014-08-01 16:49:57 +02:00
José Valim d1b8657933 Do not load mix.exs for mix deps coming from hex
This is an optimization that allows us to shave off some
seconds from booting applications.
2014-08-01 14:53:41 +02:00
José Valim 89d9cc89ca Ensure IO.ANSI.Docs can correctly fill missing columns 2014-08-01 14:10:42 +02:00
José Valim 7b0cbb1632 Speed up and calculate effective length after rendering 2014-08-01 13:34:11 +02:00
pragdave 3dc0e87c33 Add basic support for markdown tables to built-in formatter
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-01 13:14:12 +02:00
José Valim a19613cf19 Quote bash comparison arguments
/cc @chyndman
2014-08-01 12:50:47 +02:00
José Valim a8e4fcdb6c Reduce limit as we traverse collections on inspect 2014-08-01 12:50:36 +02:00
José Valim a70ca83ba2 Add logger to the list of elixir apps for escript 2014-08-01 11:15:27 +02:00
José Valim 11c857a9a2 Merge pull request #2613 from chyndman/stdlib-test-win-cmd
Use Command Prompt for stdlib test on Windows
2014-08-01 10:12:05 +02:00
Chris Hyndman 84b1f6546b Use Command Prompt for stdlib test on Windows 2014-07-31 15:06:11 -07:00
José Valim c8c1757fdd Merge pull request #2611 from lexmag/tilde-in-path
Handle tilde in `Path.expand/1,2` properly
2014-07-31 19:05:34 +02:00
José Valim 9a162b5076 Inspect.Algebra.surround_many/6 now expects Inspect.Opts 2014-07-31 18:33:34 +02:00
Aleksei Magusev 92d2f25048 Handle tilde in Path.expand/1,2 properly 2014-07-31 20:09:58 +04:00
José Valim a2ddb6abbb Add Agent MFAs 2014-07-31 17:55:48 +02:00
José Valim ac629c875b Move children decision down the tree so we don't ignore umbrella children 2014-07-31 16:49:59 +02:00
José Valim ab8ba93d1d Only print req mismatch for dependencies on compile 2014-07-31 16:06:03 +02:00
José Valim 9d62e674d7 Fix test suite 2014-07-31 14:01:18 +02:00
José Valim 58ca3401e4 Deprecate IO.ANSI.terminal? and tidy up colors 2014-07-31 13:54:08 +02:00
José Valim a5926616a6 Ensure log configuration triggers a restart 2014-07-31 12:59:34 +02:00
José Valim 59910ae4bf Add flush to Logger.add_backend/2 2014-07-31 12:43:11 +02:00
José Valim 69b47483c1 Merge pull request #2609 from fishcakez/logger_ansi_console
Add ANSI support to console logger
2014-07-31 12:43:06 +02:00
José Valim 284e75d031 Remove warnings from the test suite 2014-07-31 10:46:29 +02:00
José Valim c1d63e4639 Optimize IO.ANSI.format/2 by avoiding remote calls 2014-07-31 01:50:10 +02:00
James Fish 212b204af8 Add ANSI support to console logger 2014-07-31 00:06:31 +01:00
José Valim 90f83e1b3d Improve docs for Logger 2014-07-30 21:22:40 +02:00
José Valim 562af52d07 Fix logger docs 2014-07-30 20:16:53 +02:00
José Valim ed79d60cef Update CHANGELOG 2014-07-30 20:11:29 +02:00
José Valim 42d378142b No more binding with a list 2014-07-30 20:00:12 +02:00
José Valim aeefd59b15 Add Logger 2014-07-30 19:46:26 +02:00
José Valim 6db5091378 Provide :on option for Regex.split/3 2014-07-30 15:45:48 +02:00
José Valim 4e7ba9d598 Deprecate IO.ANSI.escape/2 and IO.ANSI.escape_fragment/2 2014-07-30 13:14:53 +02:00
José Valim 60b66ac669 Merge pull request #2562 from thmzlt/ansi
Implement IO.ANSI format/2 and format_fragment/2
2014-07-30 13:00:20 +02:00
José Valim 1fda82d9c2 Merge pull request #2606 from edgurgel/patch-1
Add OTP 17.1 to run on Travis CI
2014-07-30 12:50:12 +02:00
José Valim 55dbd617a6 Ensure no lock for rebar dependencies, closes #2561 2014-07-30 12:48:16 +02:00
Eduardo Gurgel 841a4e0195 Add OTP 17.1 to run on Travis CI 2014-07-30 22:47:01 +12:00
José Valim 5d60728d4b Always touch all marked dependencies from deps fetcher 2014-07-30 12:42:43 +02:00
José Valim 9717a63604 Do not consider subpatterns on Regex.split/3 2014-07-30 11:13:18 +02:00
José Valim fae20670ed Update CHANGELOG 2014-07-29 20:40:54 +02:00
José Valim 6874eb7ce8 Deprecate octal and upcase char syntaxes 2014-07-29 20:33:42 +02:00
Joe-noh 404e95b7ac Fix indent
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-29 18:02:15 +02:00
José Valim 30a5914a92 Merge pull request #2600 from niku/remove-duplicate-test
Remove duplicate test
2014-07-29 16:53:41 +02:00
José Valim f97a36ff98 Merge pull request #2599 from rclements/patch-1
Update gen_server.ex
2014-07-29 16:53:27 +02:00
niku e68f617e2a Remove duplicate test 2014-07-29 23:51:24 +09:00
Robby Clements 3056e95ded Update gen_server.ex 2014-07-29 09:49:47 -05:00
José Valim 9ba77e1ce6 List crypto, compiler and syntax tools as dependencies, closes #2593 2014-07-29 11:42:12 +02:00
José Valim 84010d4467 Ensure private functions are not exported, closes #2587 2014-07-29 11:22:19 +02:00
José Valim a91aa70103 Do not expose protocol convention on assert_impl!
Closes #2594
2014-07-29 10:59:25 +02:00
José Valim 301c7fd122 Update CHANGELOG 2014-07-29 10:45:12 +02:00
José Valim 8809ab2f5e Do not leak Stream representation 2014-07-29 10:41:02 +02:00
José Valim 71e441dcc2 Remove IEx warnings 2014-07-29 10:40:40 +02:00
José Valim f11197fd04 Remove escript from elixir app 2014-07-29 10:38:20 +02:00
José Valim 7ac771da67 Do two separate :io.setopts calls 2014-07-29 10:38:20 +02:00
José Valim a47751f4de Merge pull request #2592 from tomkadwill/doc-false
Added @doc false to terminate and code_change
2014-07-28 22:09:33 +02:00
José Valim c91217860c Merge pull request #2573 from chyndman/iex-werl-option
Windows IEx enhancements
2014-07-28 22:09:00 +02:00
Chris Hyndman f69e9df983 Add new --werl option 2014-07-28 07:49:02 -10:00
Chris Hyndman 2ee1d5daef Suppress "dumb terminal" warning 2014-07-28 07:48:21 -10:00
Chris Hyndman d3dba63659 Use start to spawn werl without lingering cmd window 2014-07-28 07:42:28 -10:00
Chris Hyndman c016d74379 Use setlocal/endlocal to prevent polluting user's environment 2014-07-28 07:42:27 -10:00
Tom Kadwill a6d887ee09 Added @doc false to terminate and code_change 2014-07-28 12:49:33 +01:00
José Valim aa017a4dcb Merge pull request #2585 from herenowcoder/enum-docs
use os.timestamp for setting random seed in doc examples
2014-07-28 09:34:59 +02:00
José Valim b7c7d7e72c Merge pull request #2586 from jquadrin/patch-1
small IEx evaluator refactoring
2014-07-28 09:34:23 +02:00
José Valim d164a9b93f Merge pull request #2590 from MSch/fix-mix-escript
Fix mix escript
2014-07-28 09:30:49 +02:00
Martin Schürrer 0105cf538e Update Elixir's mix.exs to fix escript.build 2014-07-27 22:47:21 -05:00
Martin Schürrer 957e389fe7 Update mix's mix.exs to fix escript.build 2014-07-27 22:45:40 -05:00
Joseph Quadrino 88d4d38afa small evaluator refactoring 2014-07-27 16:52:16 -04:00
Wojciech Kaczmarek 8b4b985894 use os.timestamp for setting random seed in doc examples 2014-07-27 17:59:22 +02:00
José Valim 986eb8bdb1 Merge pull request #2556 from patrickgombert/master
Allow --app flag to be passed on mix new
2014-07-27 08:33:58 -05:00
José Valim 052eac333c Merge pull request #2580 from Joe-noh/lowercase-http-s-proxy
[MIX] allow lowercase http(s)_proxy env var
2014-07-27 08:31:43 -05:00
José Valim acae645db1 Merge pull request #2581 from mururu/parse-space
Parse unary/binary operators regardless of number of spaces
2014-07-27 08:27:29 -05:00
José Valim 7cb5f6ff3c Merge pull request #2582 from tomkadwill/added-doc-false
Added @doc false to handle_call functions
2014-07-27 07:58:05 -05:00
Tom Kadwill 0fe940a621 Added @doc false to handle call 2014-07-27 11:52:15 +01:00
Yuki Ito 81ed03ffb2 Parse unary/binary operators regardless of number of spaces
closes #2534.
2014-07-26 20:03:38 +09:00
Joe-noh 3947f7455a allow lowercase http(s)_proxy env var 2014-07-26 17:03:25 +09:00
José Valim 406a2e6755 Merge pull request #2576 from Gazler/docs/dict
Docs: change Dict.merge description to read more naturally
2014-07-25 23:30:30 -05:00
Gary Rennie 0b3fcb254a Docs: change Dict.merge description to read more naturally
Previously there was an additional "dict" - this is as a result of it
previously saying "dict `a`"
2014-07-25 23:10:50 +01:00
Eric Meadows-Jönsson 054843505a Merge pull request #2575 from Gazler/docs/dict
Docs: ensure docs for Dict use full variable names
2014-07-25 15:42:48 -05:00
Gary Rennie 92016cd80e Docs: ensure docs for Dict use full variable names
The following variable switches have been made:

 * `d` has been changed to `dict`
 * `a` has been changed to `dict1`
 * `b` has been changed to `dict2`
 * `d1` has been changed to `dict1`
 * `d2` has been changed to `dict2`

 This fixes #2574
2014-07-25 21:07:53 +01:00
José Valim 0c06e72f0c Merge pull request #2570 from chyndman/iex-expand-fun-fallback
Fallback to no tab expansion if not supported
2014-07-23 22:12:38 -07:00
Chris Hyndman a722a4b33c Fallback to no tab expansion if not supported 2014-07-23 08:53:27 -10:00
José Valim 4afb8a516c Remove bug where access/update was incorrectly filling values with maps 2014-07-22 12:44:05 -07:00
José Valim e9fc50bc6d Exit on GenEvent call, move stream to its own module 2014-07-22 09:49:11 -07:00
José Valim 0a5a980ab7 Merge pull request #2566 from jwarwick/mix_clean_typo
Fixed typos in Mix.Tasks.Clean docs
2014-07-21 16:42:52 -05:00
John Warwick 30505b7758 Fixed typos in Mix.Tasks.Clean docs 2014-07-21 15:35:05 -04:00
Thomaz Leite 9f867f2ec8 Fix IO.ANSI doctests 2014-07-21 12:22:13 -03:00
Thomaz Leite 5e2020804a Reimplement IO.ANSI do_format
This avoids copying partial lists, and handles nested improper lists.
2014-07-21 12:07:39 -03:00
Thomaz Leite b5cea26e57 Add test case for nested improper lists 2014-07-21 11:33:37 -03:00
Thomaz Leite 03e6d3a5a2 Add docs to format/2 and format_fragment/2 2014-07-20 13:36:34 -03:00
Thomaz Leite 89513f9d15 Put IO.ANSI.reset inside a list before concat'ing 2014-07-20 13:15:06 -03:00
Thomaz Leite f20ef46e90 Implement IO.ANSI format/2 and format_fragment/2 2014-07-20 12:11:54 -03:00
José Valim d7ebc46a2d Improve config.exs docs 2014-07-20 11:07:16 +02:00
José Valim bd6d4bcd6f Merge pull request #2563 from alco/typespec-typo
Fix a typo in typespec doc
2014-07-20 11:05:43 +02:00
Alexei Sholik 4f8862cedd Fix a typo in typespec doc 2014-07-20 11:57:22 +03:00
Eric Meadows-Jönsson 1afc5ec71b Trigger rebuild in Makefile for io/ansi/docs.ex
Closes #2558.
2014-07-19 23:26:29 +02:00
José Valim 925ff6050a Merge pull request #2554 from chyndman/mix-ps1-revised
mix.ps1 Revision
2014-07-19 20:52:29 +02:00
Thomaz Leite 6ce97090f3 Change IO.ANSI tests to assume chardata is returned 2014-07-19 14:36:32 -03:00
Thomaz Leite 6538f33825 Bring back original tests for IO.ANSI
To avoid conflict and preserve compatibilty, the new tests use the
functions `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`.
2014-07-19 13:26:53 -03:00
José Valim a1e4a2741f Avoid unecessary wrap inside do keyword 2014-07-19 18:13:38 +02:00
Thomaz Leite fe57a821d8 Rewrite IO.ANSI tests to match new behavior 2014-07-19 12:44:59 -03:00
José Valim c42fe88dcc Provide Inspect.Algebra.format/2 that returns iodata 2014-07-19 13:32:45 +02:00
José Valim 28d7fc35a6 Merge pull request #2559 from jwarwick/keyword_docs
Fixing doc typos in Keyword
2014-07-19 10:58:16 +02:00
John Warwick 9fa279897b Fixing doc typos in Keyword 2014-07-18 16:26:46 -04:00
Patrick Gombert 6465a3a4af Allow --app flag to be passed on mix new
Allows path to not follow app name conventions
2014-07-17 20:48:04 -05:00
Chris Hyndman 1582df8d70 Fixed to use flattened array $newArgs 2014-07-17 16:19:26 -07:00
Chris Hyndman 8d9aec5a55 Revise mix.ps1 to use FileInfo object to represent path, modify $args directly 2014-07-17 15:09:38 -07:00
José Valim 7f4df95922 Merge pull request #2553 from chyndman/mix-test-archive-win-refute
Skip additional refutation due to code server file handle on Windows
2014-07-17 21:02:06 +02:00
Chris Hyndman 98a5affa4a Skip additional refutation due to code server file handle on Windows 2014-07-17 11:33:07 -07:00
José Valim b720559d56 Merge pull request #2552 from alco/application-doc-typo
Fix a typo on Application.format_error doc
2014-07-17 16:22:22 +02:00
Alexei Sholik cd5cc91cd4 Fix a typo on Application.format_error doc 2014-07-17 17:12:51 +03:00
José Valim 93d92d1f36 Optimize rstrip for ascii chars 2014-07-16 23:59:09 +02:00
José Valim ce3674dd07 Add OptionParser.to_argv/1 2014-07-16 22:22:02 +02:00
José Valim e4cfb62a74 Merge pull request #2549 from jwarwick/exception_typo
Fixed typos in Exception docs
2014-07-16 22:08:03 +02:00
John Warwick bb51701614 Fixed typos in Exception docs 2014-07-16 16:05:48 -04:00
José Valim 6ffb248f03 No longer allow empty access as in [][] 2014-07-16 17:54:26 +02:00
José Valim 50af8058ef Use john and meg in code samples 2014-07-16 17:41:28 +02:00
José Valim b1d9e147b3 Update CHANGELOG 2014-07-16 15:06:35 +02:00
José Valim 7ea91d2d68 Clean up String.slice/2/3 impls 2014-07-16 14:03:52 +02:00
Alexei Sholik c7640d3cf4 Do not traverse the string twice in String.slice(<str>, <range>)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 13:05:54 +02:00
José Valim 399240295b Remove invalid return values from GenEvent.handle_info/2 docs 2014-07-16 12:58:03 +02:00
José Valim b965d8eb5d Also allow structs atoms on Map.from_struct/1 2014-07-16 12:57:11 +02:00
Josh Nussbaum 380bf1e279 Added Map.from_struct/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 12:50:14 +02:00
José Valim 6272b60d36 Ensure all elixir dependencies are started before hand, closes #2542 2014-07-16 12:48:46 +02:00
José Valim 0c7699f21c Merge pull request #2541 from chyndman/mix-bat-exec-directly
Call Mix.start, Mix.CLI.main directly in bin\mix.bat
2014-07-16 12:07:04 +02:00
José Valim 1086a8455f Merge pull request #2544 from eproxus/patch-1
Remove non-existing from argument from handle_cast
2014-07-16 11:44:47 +02:00
Adam Lindberg d8e7578e41 Remove non-existing from argument from handle_cast
`handle_cast` does not support a `from` argument and this example will crash if it has one.
2014-07-16 10:54:02 +02:00
Chris Hyndman 2d0fbcc23c Call Mix.start, Mix.CLI.main directly in bin\mix.bat 2014-07-15 13:42:58 -07:00
José Valim 21d5d43ac5 Merge pull request #2539 from hamiltop/update_genevent_stream_doc
Update doc for GenEvent.stream/2
2014-07-15 20:56:59 +02:00
Peter Hamilton 027f90380b Update doc for GenEvent.stream/2
- GenEvent streams are not two way, so remove language suggesting that
you can notify via the streams.
- Clarify that consumption doesn't start until we enumerate.
2014-07-15 11:54:04 -07:00
José Valim 1a68f63525 Improve description of operations that takes linear time 2014-07-15 20:14:44 +02:00
José Valim 798c53a4cb Add linearity notes to Enum.fetch and friends 2014-07-15 16:33:25 +02:00
José Valim e3a89050f2 Merge pull request #2536 from alco/string-slice-speedup
Optimize String.slice by using binary_part
2014-07-15 10:56:42 +02:00
Alexei Sholik f176cb55c1 Optimize String.slice by using binary_part 2014-07-15 02:40:03 +03:00
José Valim 97f2e8744b Insert new line after IEx clear message 2014-07-14 19:48:24 +02:00
José Valim c513459c10 Merge pull request #2531 from jwarwick/system_cmd_type
Updating System.cmd return types
2014-07-14 16:11:04 +02:00
John Warwick fd6f1d47be Updating System.cmd return types 2014-07-14 10:06:23 -04:00
José Valim 6b8b5bfd01 Revert "Merge pull request #2530 from qingliangcn/master"
This reverts commit 42875c0b72, reversing
changes made to be320880b2.
2014-07-14 14:02:13 +02:00
Eric Meadows-Jönsson e6e96165aa Ensure type_to_signature works for all AST variables 2014-07-14 12:05:40 +02:00
José Valim 42875c0b72 Merge pull request #2530 from qingliangcn/master
use pure erlang function to judge the erlang version, no shell.
2014-07-14 11:29:57 +02:00
qingliangcn b35e2cf82a use pure erlang function to judge the erlang version, no shell. 2014-07-14 14:49:06 +08:00
José Valim be320880b2 Ensure functions in Kernel.SpecialForms and IEx.Helpers are also auto-completed
Closes #2527
2014-07-13 09:50:55 +02:00
José Valim fe1712d642 Merge pull request #2528 from alco/base-warnings
Update bitstring syntax in base.ex
2014-07-13 09:22:59 +02:00
Alexei Sholik 63d815912e Update bitstring syntax in base.ex 2014-07-13 06:03:22 +03:00
José Valim cd4dc75c93 Merge pull request #2526 from alco/at-exit-doc
Clarify the consequences of using System.at_exit
2014-07-12 16:31:42 +02:00
José Valim 058b157b36 Remove deprecated features and deprecate soft ones 2014-07-12 16:23:17 +02:00
Alexei Sholik 19c9941e7f Clarify the consequences of using System.at_exit 2014-07-12 17:11:57 +03:00
Eric Meadows-Jönsson 7895ca0821 Clarify release instructions 2014-07-12 15:21:29 +02:00
Eric Meadows-Jönsson 2b20bb424f Start v0.14.4-dev 2014-07-12 15:06:10 +02:00
254 changed files with 8847 additions and 3341 deletions
+1
View File
@@ -7,3 +7,4 @@ notifications:
- eric.meadows.jonsson@gmail.com
otp_release:
- 17.0
- 17.1
+111
View File
@@ -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
View File
@@ -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:
+13 -7
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -1 +1 @@
0.14.3
1.0.0-rc1
+8 -1
View File
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-60
View File
@@ -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.
+2 -1
View File
@@ -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
View File
@@ -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 """
+11 -8
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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, ?=, ?=, ?=, ?=, ?=, ?=>> ->
-20
View File
@@ -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
View File
@@ -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
View File
@@ -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 """
+18 -9
View File
@@ -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
+3 -3
View File
@@ -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
File diff suppressed because it is too large Load Diff
+181
View File
@@ -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
+58 -8
View File
@@ -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
View File
@@ -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
+92 -87
View File
@@ -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
+16 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+45 -37
View File
@@ -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
+34 -30
View File
@@ -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
+10 -38
View File
@@ -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)
+38 -33
View File
@@ -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` &#124; `true`
`byte` | `0..255`
`char` | `0..0x10ffff`
`number` | `integer` &#124; `float`
`char_list` | [char]
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom` | `tuple`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `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
View File
@@ -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
View File
@@ -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)
+2 -2
View File
@@ -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
+27
View File
@@ -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
+5 -15
View File
@@ -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)
+8 -8
View File
@@ -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
+30
View File
@@ -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
View File
@@ -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
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 """
+3 -3
View File
@@ -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
+23 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+13 -8
View File
@@ -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)).
+2 -9
View File
@@ -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) ->
+38 -19
View File
@@ -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()].
+24 -11
View File
@@ -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]).
+19 -16
View File
@@ -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.
+1 -1
View File
@@ -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),
+4 -3
View File
@@ -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),
+30 -35
View File
@@ -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
+6 -3
View File
@@ -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) ->
+6 -2
View File
@@ -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) ->
+10 -4
View File
@@ -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 ->
+11 -12
View File
@@ -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;
_ ->
+37 -30
View File
@@ -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'].
+32 -2
View File
@@ -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],
+86 -67
View File
@@ -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;
+2 -4
View File
@@ -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),
+12 -8
View File
@@ -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
-2
View File
@@ -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
-10
View File
@@ -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]
+3 -1
View File
@@ -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
+11 -11
View File
@@ -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
+447 -353
View File
@@ -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
+22 -19
View File
@@ -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
+39 -16
View File
@@ -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
+14 -14
View File
@@ -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
+47 -22
View File
@@ -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
+74 -34
View File
@@ -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
+78 -14
View File
@@ -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