Compare commits

...
134 Commits
Author SHA1 Message Date
José Valim 9033b217d9 Support canonical URLs 2016-02-21 21:33:30 +01:00
José Valim 1d9548fd28 Merge pull request #3475 from siscia/fixIntTake
Ensure integer in guards in Enum/Stream.take
2015-07-12 17:58:49 +02:00
siscia 9a6c195880 add test for integer passed as argument in {Enum,Stream}.take #3473 2015-07-12 17:40:16 +02:00
José Valim 3eb938a0ba Release v1.0.5 2015-06-29 09:56:28 +02:00
José Valim 66f1e74e0c Run badkey and badmap only on 18
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 18:01:05 +02:00
José Valim 7c382b94af Ensure we also normalize badmap and badkey in rescue 2015-06-25 16:49:30 +02:00
Boris Mühmer 9297a9167e fix for Erlang R18.0 changes in maps module
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 16:12:34 +02:00
James Fish 0410a1cec9 Fix logger translating process exit 2015-06-25 13:06:38 +01:00
James Fish b545a1e491 Translate emulator logged process crashes in 18.0+
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-25 11:57:49 +01:00
José Valim c08b5b11c6 Move tests to Erlang 18 which has proper logger behaviour
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 13:01:04 +02:00
José Valim 8939b39fc2 Update to Erlang 18 2015-06-25 12:47:55 +02:00
José Valim e12a0c561e Backport improvements to <<>>' docs 2015-06-22 13:47:27 +02:00
José Valim 61c3e82123 Improve wording
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:58 +01:00
José Valim d43b41695d Also document exit reasons
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:34 +01:00
José Valim fdca8b2657 Add docs for simple one for one
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:03 +01:00
José Valim dec06d8c82 Update CHANGELOG 2015-06-13 18:47:53 +02:00
José Valim 11c84cbd2e Delegate to :erlang.binary_to_float on Float.parse/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-13 18:45:45 +02:00
José Valim 67501bcddc Avoid non-fixed table errors on autocomplete 2015-06-08 16:56:22 +02:00
José Valim 1f05e7ce73 Rely on loaded_applications instead of running ones
If the application is loaded, it is enough for its
modules to be available for autocompletion. This also
simplifies the code and makes completion faster.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 16:08:43 +02:00
James Fish 02c279af12 Really fix race condition in logger translator test
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-08 12:08:26 +01:00
José Valim 2c2fb820ab Update CHANGELOG 2015-06-08 09:45:01 +02:00
James Fish 1ad1d6d6b0 Merge pull request #3382 from lexmag/string_io-fixes
Fix `StringIO` processes leakage in the `ExUnit.CaptureIO`

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 09:33:46 +02:00
James Fish 83d8161959 Fix race condition in logger translator test 2015-06-07 22:07:36 +01:00
Eric Meadows-Jönsson b87ea61713 Join pre-release parts with "."
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:13:05 +02:00
Eric Meadows-Jönsson c355ab6482 Fix to_string for Version with numeric pre
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:12:43 +02:00
Eric Meadows-Jönsson f0ed2d48ce Fix Version.pre typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:35 +02:00
Eric Meadows-Jönsson d23a46fa9f Add more documentation for ~> requirements
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:31 +02:00
Alexei Sholik 6c0cbbd73e Handle :undefined arity in Logger.Translator
We get :undefined when a temporary worker of a simple_one_for_one
supervisor crashes.

Thanks to @fishcakez for feedback
2015-06-07 23:03:54 +03:00
José Valim d58ef57e13 Ensure take/1 does not consume more than required
Kudos to @hamiltop for the idea and initial work on this fix.

Closes #3381 #3379

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:09:04 +02:00
James Fish 1a9493534e Fix GenEvent detecting module that isn't loaded
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:07:41 +02:00
Eric Meadows-Jönsson 45fe1cb084 Expand attributes and macros in record extractor
Achieved by using :epp.parse_file/2 instead of
:epp_dodger.quick_parse_file/1.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:52:49 +02:00
José Valim 3f0e7c5613 Only consider outer halts in flat_map when the op is not halt itself
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:51:36 +02:00
José Valim 49e76833d6 Ensure blocks do not clobber eex buffer
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:17:05 +02:00
José Valim 7963e44a5e Speed up upcase and downcase for large strings
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:16:15 +02:00
José Valim 3ba5e44dae Ensure we properly underscore acronyms followed by paths
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:56 +02:00
José Valim 1d74ddac78 Handle corner cases for small strings in rstrp
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:43 +02:00
José Valim c1e3d4f976 Optimize rstrip
This new implementation is no longer linear without affecting
smaller samples. For a string that is 100 bytes long, it is
25x faster than the previous implementation.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:12 +02:00
Michael Sokolnicki b4fc2b35fc Add unit test for IO.binread with :all option
Unit test checks correct handling of 0x0D0A (CR+LF) when reading entire
binary file

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:22 +02:00
Michael Sokolnicki 18a5d8affb Read 4K blocks instead of lines in IO.binread
Fix for incorrect handling of 0x0D0A (CR+LF) sequence when reading
binary files with option :all

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:15 +02:00
José Valim cd80994a70 Ensure we escape config before injecting into escript
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-14 15:23:13 +02:00
José Valim e02d7bf913 Release v1.0.4 2015-04-07 23:35:13 +02:00
José Valim fe5ff41514 Tag async tests as such 2015-04-07 11:35:31 +02:00
José Valim e9887a760e Wait until test process is down in ExUnit runner
This should fix a race condition where the next test would
start before all linked processes in the previous test were
killed, often leading to already registered errors.
2015-04-06 19:13:09 +02:00
Eric Meadows-Jönsson 0559fe5927 Fix doc formatting in ExUnit.Formatter
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-04-05 19:27:01 -05:00
José Valim 1a05bff45d Update CHANGELOG 2015-04-05 19:28:53 +02:00
José Valim 4d6670342b Fixes for Erlang 18.0-rc1 2015-04-05 18:28:13 +02:00
Eric Meadows-Jönsson 193e85882e Add compatibilty with OTP 18 typespec changes
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-05 17:59:29 +02:00
José Valim a58c007e0c Avoid missing behaviour warnings when compiling erlang files 2015-04-04 18:34:58 +02:00
José Valim ce6f4e6d15 Clean up ExUnit.Assertions docs 2015-04-03 17:08:03 +02:00
José Valim 321096665a Do not propagate protocol consolidation to children 2015-04-02 21:39:33 +02:00
José Valim b93aae9f86 Ensure we quote proper file on exceptions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/test/elixir/exception_test.exs
2015-04-02 18:40:24 +02:00
José Valim a503069479 Ensure we consolidate only when there was a change 2015-04-02 15:42:56 +02:00
José Valim 120ea0fc8a Start v1.0.4-dev 2015-04-02 15:15:27 +02:00
José Valim d725c66d93 Update CHANGELOG 2015-04-02 15:04:00 +02:00
José Valim fab917dba3 Do not print app name as a filename 2015-04-02 15:00:52 +02:00
José Valim 23fb92f444 Improve error message for IEx.pry/1 2015-04-02 14:57:00 +02:00
José Valim db61109495 Generate build_embedded and start_permanent in new apps 2015-04-02 14:41:58 +02:00
José Valim c4e8ad0d65 Support start_permanent 2015-04-02 14:41:47 +02:00
José Valim ce77fa54b9 Support build_embedded and consolidate_protocols 2015-04-02 14:39:08 +02:00
Paulo Almeida eee921bf7b Support mix help --search PATTERN
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-02 09:47:07 +02:00
José Valim 73c6a0be8b Update CHANGELOG 2015-04-02 09:39:05 +02:00
José Valim 7644bd87a5 Merge pull request #3198 from eksperimental/pattern_operator
fix Kernel.=~/2 for "" =~ ""

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-02 09:37:05 +02:00
José Valim 9e2c5a29a4 Speed up decode_www_form
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-17 19:02:12 +01:00
José Valim 09498422d0 Speed up URI.decode_query/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-17 18:17:29 +01:00
Eric Meadows-Jönsson a697bab7f2 Store relative paths in erlang manifest
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-03-13 13:44:16 +01:00
José Valim 36e81d587e Increase elixir_counter timeout
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-12 19:00:56 +01:00
Booker C. Bense 17a0abd57a Added test for markup ending in . and code to pass the test
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-07 00:19:39 +01:00
Or Neeman e6cfc20556 Produce clearer messages for syntax errors before a sigil
Closes #3130

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-07 00:19:20 +01:00
Or Neeman 906f03102b Return value when matching with _ on last line of block
Closes #3125

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-02 14:27:47 +01:00
José Valim 6a594e2bba Ensure changes in child deps forces the parent to recompile
This test fell through the cracks when we changed mix
to no longer compile dependencies on get but during
project compilation.

Therefore, this patch fixes the bug and ensures the test
is up to date to check the original scenario it was supposed
to.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:03:03 +01:00
José Valim 10dd8f843b Fallback to undocumented API only if necessary
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/src/elixir.erl
2015-03-01 11:02:51 +01:00
José Valim 53ecea8b03 Do not erase stacktrace on bad configs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:02:05 +01:00
Eric Meadows-Jönsson efba8d8bfa Fix httpc proxy option profile
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:00:43 +01:00
José Valim 5a201065dc Update new major.minor instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-02-12 11:17:22 +01:00
José Valim 307675284c Release v1.0.3 2015-02-12 10:57:06 +01:00
José Valim 38a9513111 Fix boundary in Enum.take/2, closes #3042
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-02-01 09:19:30 -02:00
José Valim 578f798cc1 Use which_applications instead of undocumented info
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-31 09:46:53 -02:00
José Valim efbdf4b9b0 Update CHANGELOG 2015-01-31 11:43:06 +01:00
José Valim 9679bed4d7 Revert "Don't ignore escape characters in regex"
This change will be on v1.1 only as it may break applications.

This reverts commit 337c040da8.
2015-01-31 11:32:45 +01:00
José Valim b6c0045fc2 Ensure rebar is quoted when running escript on windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-30 11:10:41 +01:00
José Valim 10612ddf22 Always rebuild structures on Windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-30 10:13:15 +01:00
José Valim cbcbbcf876 Use :application.info[:loaded] to avoid races 2015-01-29 12:05:09 +01:00
José Valim 95b81c2a13 Merge pull request #3025 from MSch/flush-logger-after-tests
Call Logger.flush in System.at_exit when running mix test.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-29 11:45:32 +01:00
José Valim a525f71e13 Update CHANGELOG 2015-01-29 11:14:40 +01:00
José Valim 750d2a3f7a Ignore exported vars warning
Closes #2798

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-28 21:50:16 +01:00
José Valim 61e9fafb73 Improve error message with invalid structures in maps
Closes #2958

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-27 17:48:50 +01:00
José Valim aa48f6a881 Ensure priv/include are copied on Windows even if source did not change
Closes #2908

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-27 15:29:43 +01:00
José Valim b5e2d45b7d Update CHANGELOG 2015-01-24 14:10:17 +01:00
José Valim 29f2eb7dab Merge pull request #2801 from fishcakez/app_env
Move application state out of application env
2015-01-24 13:57:50 +01:00
José Valim a3754e3841 Do not show aliases and import warnings when code cannot compile
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-24 11:47:45 +01:00
José Valim 337c040da8 Don't ignore escape characters in regex
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-22 11:46:11 +01:00
José Valim b4d465e2ed Merge pull request #3018 from lexmag/logger-metadata-fix
Fix the logging with `metadata[:function]`
2015-01-21 11:47:02 +01:00
James Fish 0bb3d1c6bd Introduce Mix.State to store internal mix state
Moves non-config state from application env to an ETS table that is
updated with an Agent.
2015-01-10 15:20:22 +00:00
James Fish 86476615ef Fix logger to keep config through config_change/3
Store deleted handlers and live config data in ETS table
2015-01-10 15:20:22 +00:00
James Fish 860b3cc5a7 Introduce IEx.Config to store internal iex state
* All config get/put moved to IEx.Config
* after_spawn stored in ETS to prevent config_change deletion
* started? checks for IEx.Config process
* Return full config on IEx.configuration/1
2015-01-10 15:20:21 +00:00
James Fish bb5e6f65f5 Introduce elixir_config to store internal state
Adds ETS table that can only be updated by the gen_server elixir_config,
which means that reads are async and writes are sync.

Stores:
* URI scheme/ports
* Compiler options
* argv
* System.at_exit funs
2015-01-10 15:20:09 +00:00
Samuel Tonini 95127516f7 add missing assert call
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 19:09:11 +01:00
James Fish ef0c495f16 Set MIX_ARCHIVES in mix tasks archive tests
Previously archive tests would fail if MIX_ARCHIVES was set.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 19:09:08 +01:00
José Valim 2472bb79ab Ensure mix compiler is able to handle nested directories
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 15:04:25 +01:00
José Valim 04b9174d75 Update CHANGELOG 2015-01-04 23:44:34 +01:00
Eric Meadows-Jönsson d31a58f0d9 Don't go through hex.pm API server to install Hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-01-04 20:33:11 +01:00
José Valim f97ab2649c Keep original stacktrace on inspect errors
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-03 20:26:37 +01:00
José Valim d969d42784 Compile docs to doc/
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-03 10:12:02 +01:00
José Valim 17c4c4d993 Do more strict matching on URI scheme
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 23:17:41 +01:00
José Valim 0f7a968389 Copyright requires just the starting year
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 21:00:26 +01:00
José Valim 272ec8ca47 Fix mix shell tests when ansi escapes are disabled
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 15:39:56 +01:00
José Valim a0e8e1c0fb Further improve URI parse docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 15:34:28 +01:00
José Valim b477dba34f Improve docs for URI
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-24 11:12:33 +01:00
José Valim 8d524ef971 Fix bootstrap issues
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-23 22:26:32 +01:00
José Valim 83cb5152fd Optimize reduce for maps
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-23 20:05:29 +01:00
José Valim 3c100575a3 Merge pull request #2953 from liveforeverx/fix_compile_error
resolves #2952

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-13 23:12:35 +01:00
José Valim d1b6161f2e Update CHANGELOG 2014-12-12 12:04:02 +01:00
José Valim 785781d089 Merge pull request #2950 from tonini/remove-dumb-terminal-warning
warning about dumb terminal just irritates users

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-12 12:01:03 +01:00
José Valim b2ac4c9394 Only shut down Logger if the application is being actually started
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-12 12:00:52 +01:00
José Valim 50ac792747 Optimize and also add completion for Erlang modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 09:42:40 +01:00
José Valim 7d433e80e5 Improve IEx autocomplete implementation
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 09:42:37 +01:00
José Valim f9cb812bc1 Always use the loaded application modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 05:01:57 +01:00
José Valim f8711f2e36 Merge pull request #2932 from tonini/add-modules-load-path-to-autocomplete
Fetch module names from load path for autocomplete

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 05:01:50 +01:00
José Valim 0251f564d9 Merge pull request #2933 from Kabie/Regex.replace
Fix and enhance &Regex.replace/4

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-08 16:20:59 +01:00
José Valim e4acc08cf5 Improve archive error on Windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-07 14:21:17 +01:00
José Valim 2ab5d25787 Update CHANGELOG 2014-12-06 22:14:22 +01:00
José Valim acde8078f8 Clean up IO.ANSI.Docs handling of `
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-06 22:12:56 +01:00
José Valim 487ac9c1c4 Merge pull request #2924 from jw2013/italic
Fix broken IO.ANSI.Docs parser
Conflicts:
	lib/elixir/lib/io/ansi/docs.ex
2014-12-06 22:12:10 +01:00
José Valim f0f65f8bec Use IO.ANSI.format/1 for escaping mix shell messages
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-04 22:17:07 +01:00
José Valim 23943b6180 Update CHANGELOG 2014-11-21 17:42:39 +01:00
José Valim d9125b0a81 Ensure compilation does not leave random spurious data on failure
Prior to this patch, the parallel compiler could leave spurious
processes and the module definition could leave stale data
in the elixir_modules table.

The issue has been fixed by emitting the proper exit signals
and by using monitors.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-21 17:41:43 +01:00
José Valim d1d46db907 Start v1.0.3-dev 2014-11-13 10:41:38 +01:00
José Valim 02056d1030 Merge pull request #2891 from chrismccord/master
Fix command_to_module_name not converting snakecased names properly

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-13 10:40:58 +01:00
José Valim 14a3686a3f Merge pull request #2882 from Frost/no-more-bright-yellow-in-ansi-docs
Make IO.ANSI.Docs readable on white background

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-09 20:56:10 -02:00
José Valim c04b182c6c Merge pull request #2886 from jw2013/CRLF
fix wrong CRLF grapheme

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-09 20:55:26 -02:00
Eric Meadows-Jönsson f5462d98e0 Don't show hex update message on mix local.hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-11-08 22:55:56 +01:00
José Valim 75d0c07b46 Merge pull request #2838 from edgurgel/patch-1
Fix typo on CHANGELOG.md
2014-10-21 21:42:11 -02:00
Eduardo Gurgel 1de8dcb695 Fix typo on CHANGELOG.md
[ci skip]
2014-10-22 10:55:19 +13:00
José Valim 0e167bef27 More updates to instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 15:32:48 -02:00
150 changed files with 2593 additions and 1261 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
/.eunit
/.release
/docs
/doc
/ebin
/lib/*/ebin/*
/lib/*/tmp
+70 -1
View File
@@ -1,5 +1,74 @@
# Changelog
## v1.0.5 (2015-06-29)
* Enhancements
* [Elixir] Support 18.0
* [IEx] Rely only on loaded applications for autocompletion on IEx
* [Record] Expand attributes and macros in record extractor
* [String] `Optimize String.rstrip/1`
* [String] `Optimize String.downcase/1`
* [String] `Optimize String.upcase/1`
* Bug fixes
* [EEx] Ensure blocks do not clobber EEx buffers
* [Enum] Ensure `Enum.take/2` does not consume one extra item when halting on the last emittable item
* [ExUnit] Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` when there are errors inside the `capture_io` block
* [Float] Avoid rounding errors on `Float.parse/1`
* [GenEvent] Fix `GenEvent` detection of modules that aren't loaded
* [IO] Read 4K blocks instead of lines in `IO.binread/2`. This fixes a bug where CRLF were being ignored and is also going to improve performance
* [Logger] Handle `:undefined` arity in `Logger.Translator` (we get :undefined when a temporary worker of a `simple_one_for_one` supervisor crashes)
* [Mix] Ensure config is escaped before being injected into escripts. This fixes a bug where escripts failed to build when containing values like maps in config files
* [Mix] Ensure we properly underscore acronyms followed by paths, for example, `HTTP.Foo`
* [Stream] Ensure `Stream.flat_map/2` does not consume more items than necessary when piped to another `Stream.flat_map/2` that halts in the inner stream
* [Version] Fix `to_string` for versions with numeric pre releases
## v1.0.4 (2015-04-07)
* Enhancements
* [Elixir] Support Erlang 17.5 and 18.0-rc
* [Mix] Support "--search PATTERN" in "mix help"
* [Mix] Support `:start_permanent` that starts the application as permanent
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
* [URI] Speed decoding operations
* Bug fixes
* [Elixir] Produce clearer error messages for syntax errors before a sigil
* [Elixir] Return value when matching on `_` in the last line of a block
* [Elixir] Ensure changes in child deps forces the parent to recompile
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
* [Mix] Store relative paths in erlang manifests
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
* [Mix] Fix missing behaviour warning when compiling Erlang modules
## v1.0.3 (2015-02-12)
* Enhancements
* [Elixir] Do not show exported vars warning
* [Elixir] Improve error messages when using invalid expressions inside maps
* [Elixir] Skip aliases and import warnings when code cannot compile
* [IEx] Improve autocompletion for Erlang/Elixir based on loaded applications
* [IO] Make `IO.ANSI.Docs` readable on white background
* [Mix] Provide clearer error message when updating archives on Windows
* [Mix] Don't go through hex.pm API server to install Hex
* Bug fixes
* [Elixir] Ensure all Elixir applications work with code upgrades
* [Elixir] Don't leave spurious processes and stale data when the parallel compiler fails
* [Inspect] Keep original stacktrace on Inspect errors
* [IO] Fix `IO.ANSI.Docs` handling of `*`, `**`, `_` where they would be activated in the middle of words
* [Logger] Fix logging with `metadata[:function]`
* [Mix] Don't show hex update message on mix `local.hex`
* [Mix] Ensure tasks are properly underscored and camelized
* [Mix] Ensure Mix does not use ANSI escapes on Windows unless ANSI is enabled
* [Mix] Only shutdown Logger if the Mix application is being actually started
* [Mix] Ensure priv/include are copied on Windows even if source did not change
* [Mix] Ensure rebar dependencies work when user path has a space on Windows
* [Regex] Fix `Regex.replace/4` failures when the number of substitutions in pattern is more than the number of matches and support `\g{...}` patterns
* [String] Fix LFCR being treated as a grapheme instead of CRLF
* [URI] Do more strict matching on URI schema
## v1.0.2 (2014-10-21)
* Bug fixes
@@ -9,7 +78,7 @@
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
* [Kernel] Improve error messages when parsing fails on aliases
* [Kernel] Improve warnings coming from compile-time evaulation of arguments
* [Kernel] Improve warnings coming from compile-time evaluation of arguments
* [String] Fix `String.replace/4` typespec
## v1.0.1 (2014-10-07)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2012-2013 Plataformatec.
Copyright 2012 Plataformatec
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+13 -12
View File
@@ -1,5 +1,6 @@
REBAR := rebar
DOCS := v1.0
CANONICAL := stable
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -125,17 +126,18 @@ clean_exbeam:
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}\c")
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
docs: compile ../ex_doc/bin/ex_doc
$(Q) rm -rf docs
$(call DOCS,Elixir,elixir,Kernel)
$(call DOCS,EEx,eex,EEx)
$(call DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit)
$(call DOCS,Logger,logger,Logger)
$(Q) rm -rf doc
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call COMPILE_DOCS,EEx,eex,EEx)
$(call COMPILE_DOCS,Mix,mix,Mix)
$(call COMPILE_DOCS,IEx,iex,IEx)
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
$(call COMPILE_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."
@@ -146,9 +148,8 @@ release_zip: compile
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
release_docs: docs
cd ../docs
rm -rf ../docs/$(DOCS)
mv docs ../docs/$(DOCS)
rm -rf ../docs/$(DOCS)/*/
mv doc/* ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
+6 -6
View File
@@ -16,9 +16,9 @@ This document simply outlines the release process:
6. Ensure minimum supported Hex works with new release (instructions upcoming)
7. Push master and the new tag
7. Push branch and the new tag
8. Release new docs with `make release_docs`, move docs to `docs/stable` if appropriate, and push
8. Release new docs with `make release_docs`, copy docs to `docs/stable` if appropriate, and push
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
@@ -28,13 +28,13 @@ This document simply outlines the release process:
## New vMAJOR.MINOR releases
11. Create a new branch "vMAJOR.MINOR"
12. Create a new branch "vMAJOR.MINOR"
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
14. `make release_docs` and push it to `elixir-lang/docs`
15. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
1.0.2
1.0.5
+4 -4
View File
@@ -98,16 +98,16 @@ defmodule EEx.Engine do
"""
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> String.Chars.to_string(unquote(expr))
tmp1 = unquote(buffer)
tmp1 <> String.Chars.to_string(unquote(expr))
end
end
def handle_expr(buffer, "", expr) do
quote do
tmp = unquote(buffer)
tmp2 = unquote(buffer)
unquote(expr)
tmp
tmp2
end
end
end
+13 -1
View File
@@ -252,7 +252,7 @@ Number <%= x %>
assert_eval expected, string
end
test "do not consider already finished functions" do
test "properly handle functions on the left side of clauses" do
expected = """
foo
@@ -287,6 +287,18 @@ foo
assert_eval "\n\n Good\n \n", string
end
test "evaluates expressions with buffers" do
string = """
<%= 123 %>
<% if true do %>
<%= 456 %>
<% end %>
<%= 789 %>
"""
assert_eval "123\n\n789\n", string
end
test "for comprehensions" do
string = """
<%= for _name <- packages || [] do %>
+4 -12
View File
@@ -51,16 +51,7 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
@doc false
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
@@ -325,7 +316,7 @@ defmodule Code do
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
:elixir_config.get :compiler_options
end
@doc """
@@ -364,7 +355,8 @@ defmodule Code do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
end
@doc """
+16 -7
View File
@@ -1261,6 +1261,15 @@ defmodule Enum do
:lists.foldl(fun, acc, collection)
end
def reduce(%{__struct__: _} = collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
def reduce(%{} = collection, acc, fun) do
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
@@ -1722,23 +1731,23 @@ defmodule Enum do
[]
end
def take(collection, count) when is_list(collection) and count > 0 do
def take(collection, count) when is_list(collection) and is_integer(count) and count > 0 do
do_take(collection, count)
end
def take(collection, count) when count > 0 do
def take(collection, count) when count > 0 and is_integer(count) do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do
{:cont, {[entry|list], count - 1}}
else
{:halt, {[entry|list], count}}
case count do
0 -> {:halt, {list, count}}
1 -> {:halt, {[entry|list], count - 1}}
_ -> {:cont, {[entry|list], count - 1}}
end
end)
:lists.reverse(res)
end
def take(collection, count) when count < 0 do
def take(collection, count) when count < 0 and is_integer(count) do
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|> elem(1) |> :lists.reverse
end
+28 -1
View File
@@ -564,6 +564,14 @@ defmodule BadStructError do
end
end
defmodule BadMapError do
defexception [term: nil]
def message(exception) do
"expected a map, got: #{inspect(exception.term)}"
end
end
defmodule MatchError do
defexception [term: nil]
@@ -669,7 +677,12 @@ defmodule KeyError do
defexception key: nil, term: nil
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
msg = "key #{inspect exception.key} not found"
if exception.term != nil do
msg <> " in: #{inspect exception.term}"
else
msg
end
end
end
@@ -768,6 +781,20 @@ defmodule ErlangError do
%MatchError{term: term}
end
def normalize({:badmap, term}, _stacktrace) do
%BadMapError{term: term}
end
def normalize({:badkey, key}, stacktrace) do
term =
case stacktrace || :erlang.get_stacktrace do
[{:maps, :update, [_, _, map], _}|_] -> map
[{:maps, :get, [_, map], _}|_] -> map
_ -> nil
end
%KeyError{key: key, term: term}
end
def normalize({:case_clause, term}, _stacktrace) do
%CaseClauseError{term: term}
end
+18 -54
View File
@@ -28,75 +28,39 @@ defmodule Float do
"""
@spec parse(binary) :: {float, binary} | :error
def parse("-" <> binary) do
case parse_unsign(binary) do
case parse_unsigned(binary) do
:error -> :error
{number, remainder} -> {-number, remainder}
end
end
def parse(binary) do
parse_unsign(binary)
parse_unsigned(binary)
end
defp parse_unsign("-" <> _), do: :error
defp parse_unsign(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
end
end
defp parse_unsigned(<<char, rest::binary>>) when char in ?0..?9, do:
parse_unsigned(rest, false, false, <<char>>)
# Dot followed by digit is required afterwards or we are done
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse_unsign(rest, char - ?0, 1, int)
end
defp parse_unsigned(binary) when is_binary(binary), do:
:error
defp parse_unsign(rest, int) do
{:erlang.float(int), rest}
end
defp parse_unsigned(<<char, rest :: binary>>, dot?, e?, acc) when char in ?0..?9, do:
parse_unsigned(rest, dot?, e?, <<acc::binary, char>>)
# Handle decimal points
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
end
defp parse_unsigned(<<?., char, rest :: binary>>, false, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, false, <<acc::binary, ?., char>>)
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
case Integer.parse after_e do
:error ->
# Note we rebuild the binary here instead of breaking it apart at
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
{exponential, after_exponential} ->
{floatify(int, float, decimal, exponential), after_exponential}
end
end
defp parse_unsigned(<<?e, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, char>>)
defp parse_unsign(bitstring, float, decimal, int) do
{floatify(int, float, decimal), bitstring}
end
defp parse_unsigned(<<?e, ?-, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, ?-, char>>)
defp floatify(int, float, decimal, exponential \\ 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
defp parse_unsigned(rest, dot?, _e?, acc), do:
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
# Try to ensure the minimum amount of rounding errors
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
# Try avoiding stuff like this:
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
decimal_power_round = :math.pow(10, final_decimal_places)
trunc(result * decimal_power_round) / decimal_power_round
else
result
end
end
defp add_dot(acc, true), do: acc
defp add_dot(acc, false), do: acc <> ".0"
@doc """
Rounds a float to the largest integer less than or equal to `num`.
+1 -1
View File
@@ -1015,7 +1015,7 @@ defmodule GenEvent do
case reason do
{:undef, [{m,f,a,_}|_]=mfas} ->
cond do
:code.is_loaded(m) ->
:code.is_loaded(m) === false ->
{:"module could not be loaded", mfas}
function_exported?(m, f, length(a)) ->
reason
+5 -2
View File
@@ -185,6 +185,8 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts)
rescue
e ->
stacktrace = System.stacktrace
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
@@ -198,9 +200,10 @@ defmodule Inspect.Algebra do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
raise ArgumentError,
reraise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
stacktrace
after
Process.delete(:inspect_trap)
end
+2 -1
View File
@@ -123,8 +123,9 @@ defmodule IO do
end
end
@read_all_size 4096
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
case :file.read(mapped_dev, @read_all_size) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
+79 -36
View File
@@ -25,10 +25,10 @@ defmodule IO.ANSI.Docs do
[enabled: true,
doc_bold: [:bright],
doc_code: [:cyan, :bright],
doc_headings: [:yellow, :bright],
doc_headings: [:yellow],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow, :bright],
doc_title: [:reverse, :yellow],
doc_underline: [:underline],
width: 80]
end
@@ -194,7 +194,7 @@ defmodule IO.ANSI.Docs do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], options)
|> handle_inline(options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
@@ -262,7 +262,7 @@ defmodule IO.ANSI.Docs do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
|> handle_inline(options)
{col, length_without_escape(col, 0)}
end
@@ -392,61 +392,104 @@ defmodule IO.ANSI.Docs do
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
@single [?`, ?_, ?*]
# ` does not require space in between
@spaced [?_, ?*]
# We have four entries: **, *, _ and `.
#
# The first three behave the same while the last one is simpler
# when it comes to delimiters. But, since the first has two
# characters, we need to handle 3 cases:
#
# 1. **
# 2. _ and *
# 3. `
#
# Where the first two should have the same code but match differently.
@single [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
# Characters that can mark the beginning or the end of a word.
# Only support the most common ones at this moment.
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
handle_inline(rest, ?d, ["**"], [], options)
end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
handle_inline(rest, mark, [<<mark>>], [], options)
end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
defp handle_inline(rest, options) do
handle_inline(rest, nil, [], [], options)
end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
# Inline delimiters
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters do
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters and mark in @single do
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options)
end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
end
# A escape is not valid inside `
# An escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
when not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, options)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
end
# Inline end
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
when delimiter in @delimiters do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
when delimiter in @delimiters and mark in @single do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
when rest == "" do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
when rest == "" and mark in @single do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
# Catch all
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options)
+7
View File
@@ -1386,7 +1386,14 @@ defmodule Kernel do
iex> "abcd" =~ "ad"
false
iex> "abcd" =~ ""
true
"""
@spec (String.t =~ (String.t | Regex.t)) :: boolean
def left =~ "" when is_binary(left), do: true
def left =~ right when is_binary(left) and is_binary(right) do
:binary.match(left, right) != :nomatch
end
+1 -1
View File
@@ -56,7 +56,7 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit)
hooks = :elixir_config.get_and_put(:at_exit, [])
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
+9 -3
View File
@@ -96,7 +96,7 @@ defmodule Kernel.ParallelCompiler do
else
:elixir_compiler.file(h, Keyword.get(options, :dest))
end
{:compiled, h}
{:shutdown, h}
catch
kind, reason ->
{:failure, kind, reason, System.stacktrace}
@@ -166,7 +166,7 @@ defmodule Kernel.ParallelCompiler do
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
if callback = Keyword.get(options, :each_file) do
callback.(file)
end
@@ -187,9 +187,15 @@ defmodule Kernel.ParallelCompiler do
defp handle_failure(ref, reason, entries, waiting, queued) do
if file = find_failure(ref, queued) do
print_failure(file, reason)
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
Enum.each queued, fn {child, _, _} ->
Process.exit(child, :kill)
end
exit({:shutdown, 1})
end
end
@@ -201,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
end
end
defp print_failure(_file, {:compiled, _}) do
defp print_failure(_file, {:shutdown, _}) do
:ok
end
+143 -80
View File
@@ -172,10 +172,22 @@ defmodule Kernel.SpecialForms do
iex> << 1, 2, 3 >>
<< 1, 2, 3 >>
## Bitstring types
## Types
A bitstring is made of many segments. Each segment has a
type, which defaults to integer:
A bitstring is made of many segments and each segment has a
type. There are 9 types used in bitstrings:
- `integer`
- `float`
- `bits` (alias for bitstring)
- `bitstring`
- `binary`
- `bytes` (alias for binary)
- `utf8`
- `utf16`
- `utf32`
When no type is specified, the default is `integer`:
iex> <<1, 2, 3>>
<<1, 2, 3>>
@@ -186,14 +198,7 @@ defmodule Kernel.SpecialForms do
iex> <<0, "foo">>
<<0, 102, 111, 111>>
Any other type needs to be explicitly tagged. For example,
in order to store a float type in the binary, one has to do:
iex> <<3.14 :: float>>
<<64, 9, 30, 184, 81, 235, 133, 31>>
This also means that variables need to be explicitly tagged,
otherwise Elixir defaults to integer:
Variables or any other type need to be explicitly tagged:
iex> rest = "oo"
iex> <<102, rest>>
@@ -201,103 +206,161 @@ defmodule Kernel.SpecialForms do
We can solve this by explicitly tagging it as a binary:
<<102, rest :: binary>>
The type can be integer, float, bitstring/bits, binary/bytes,
utf8, utf16 or utf32, e.g.:
<<102 :: float, rest :: binary>>
An integer can be any arbitrary precision integer. A float is an
IEEE 754 binary32 or binary64 floating point number. A bitstring
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
iex> rest = "oo"
iex> <<102, rest :: binary>>
"foo"
The utf8, utf16, and utf32 types are for unicode codepoints. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
<<0,102,0,111,0,111>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo" :: utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
The bits type is an alias for bitstring. The bytes type is an
alias for binary.
## Options
The signedness can also be given as signed or unsigned. The
signedness only matters for matching and relevant only for
integers. If unspecified, it defaults to unsigned. Example:
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
<<156,102,111,111>>
This match would have failed if we did not specify that the
value -100 is signed. If we're matching into a variable instead
of a value, the signedness won't be checked; rather, the number
will simply be interpreted as having the given (or implied)
signedness, e.g.:
iex> <<val, _rest :: binary>> = <<-100, "foo">>
iex> val
156
Here, `val` is interpreted as unsigned.
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Many options
can be given by using `-` as separator:
Many options can be given by using `-` as separator. Order is
arbitrary, so the following are all equivalent:
<<102 :: integer-native, rest :: binary>>
Or:
<<102 :: native-integer, rest :: binary>>
<<102 :: unsigned-big-integer, rest :: binary>>
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
<<102 :: unsigned-big-integer-8, rest :: binary>>
<<102 :: 8-integer-big-unsigned, rest :: binary>>
<<102, rest :: binary>>
And so on.
### Unit and Size
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
The length of the match is equal to the `unit` (a number of bits) times the
`size` (the number of repeated segnments of length `unit`).
Finally, we can also specify size and unit for each segment. The
unit is multiplied by the size to give the effective size of
the segment in bits. The default unit for integers, floats,
and bitstrings is 1. For binaries, it is 8.
Type | Default Unit
--------- | ------------
`integer` | 1 bit
`float` | 1 bit
`binary` | 8 bits
Since integers are default, the default unit is 1. The example below
matches because the string "foo" takes 24 bits and we match it
against a segment of 24 bits, 8 of which are taken by the integer
102 and the remaining 16 bits are specified on the rest.
Sizes for types are a bit more nuanced. The default size for integers is 8.
iex> <<102, _rest :: size(16)>> = "foo"
"foo"
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
corresponding to [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)
binary32 and binary64, respectively.
We can also match by specifying size and unit explicitly:
For binaries, the default is the size of the binary. Only the last binary in a
match can use the default size. All others must have their size specified
explicitly, even if the match is unambiguous. For example:
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo"
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"}
However, if we expect a size of 32, it won't match:
Failing to specify the size for the non-last causes compilation to fail:
iex> <<102, _rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
Size and unit are not applicable to utf8, utf16, and utf32.
The default size for integers is 8. For floats, it is 64. For
binaries, it is the size of the binary. Only the last binary
in a binary match can use the default size (all others must
have their size specified explicitly).
#### Shortcut Syntax
Size and unit can also be specified using a syntax shortcut
when passing integer values:
<< x :: 8 >> == << x :: size(8) >>
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
iex> x = 1
iex> << x :: 8 >> == << x :: size(8) >>
true
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
true
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
### Modifiers
Some types have associated modifiers to clear up ambiguity in byte
representation.
Modifier | Relevant Type(s)
-------------------- | ----------------
`signed` | `integer`
`unsigned` (default) | `integer`
`little` | `integer`, `utf16`, `utf32`
`big` (default) | `integer`, `utf16`, `utf32`
`native` | `integer`, `utf16`, `utf32`
### Sign
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
iex> <<int::integer>> = <<-100>>
<<156>>
iex> int
156
iex> <<int::integer-signed>> = <<-100>>
<<156>>
iex> int
-100
`signed` and `unsigned` are only used for matching binaries (see below) and
are only used for integers.
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
<<156, 102, 111, 111>>
### Endianness
Elixir has three options for endianness: `big`, `little`, and `native`.
The default is `big`. `native` is determined by the VM at startup.
iex> <<number::little-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
iex> <<number::big-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
1
iex> <<number::native-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
## Binary/Bitstring Matching
Binary matching is a powerful feature in Elixir that is useful for extracting
information from binaries as well as pattern matching.
Binary matching can be used by itself to extract information from binaries:
iex> <<"Hello, ", place::binary>> = "Hello, World"
"Hello, World"
iex> place
"World"
Or as a part of function definitions to pattern match:
defmodule ImageTyper
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
@jpg_signature <<255::size(8), 216::size(8)>>
def type(<<@png_signature, rest::binary>>), do: :png
def type(<<@jpg_signature, rest::binary>>), do: :jpg
def type(_), do :unknown
end
### Performance & Optimizations
The Erlang compiler can provide a number of optimizations on binary creation
and matching. To see optimization output, set the `bin_opt_info` compiler
option:
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
To learn more about specific optimizations and performance considerations,
check out
[Erlang's Efficiency Guide on handling binaries](http://www.erlang.org/doc/efficiency_guide/binaryhandling.html).
"""
defmacro unquote(:<<>>)(args)
+37 -7
View File
@@ -644,6 +644,10 @@ defmodule Kernel.Typespec do
[]
end
defp typespec_to_ast({:user_type, line, name, args}) do
typespec_to_ast({:type, line, name, args})
end
defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []}
end
@@ -677,8 +681,13 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({:type, line, :map, fields}) do
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
fields = Enum.map fields, fn
# OTP 18
{:type, _, :map_field_assoc, [k, v]} ->
{typespec_to_ast(k), typespec_to_ast(v)}
# OTP 17
{:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
end
{struct, fields} = Keyword.pop(fields, :__struct__)
@@ -821,9 +830,17 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = :lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
fields =
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
end, fields)
else
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
end
{:type, line(meta), :map, fields}
end
@@ -884,7 +901,8 @@ defmodule Kernel.Typespec do
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :range, args}
end
# Handle special forms
@@ -968,9 +986,21 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
defp typespec({:fun, meta, args}, vars, caller) do
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :fun, args}
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), name, arguments}
if :erlang.system_info(:otp_release) >= '18' do
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
else
{:type, line(meta), name, arguments}
end
end
# Handle literals
+1 -1
View File
@@ -833,7 +833,7 @@ defmodule Macro do
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{receiver, true}
aliases ->
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases)
case :lists.all(&is_atom/1, aliases) do
true ->
+5 -7
View File
@@ -684,7 +684,7 @@ defmodule Module do
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
for tuple <- tuples do
:lists.foreach(fn tuple ->
case :elixir_def.lookup_definition(module, tuple) do
false ->
{name, arity} = tuple
@@ -692,10 +692,10 @@ defmodule Module do
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do
Module.LocalsTracker.yank(module, tuple)
else
neighbours = if :elixir_compiler.get_opt(:internal) do
[]
else
Module.LocalsTracker.yank(module, tuple)
end
old = :elixir_def_overridable.overridable(module)
@@ -704,7 +704,7 @@ defmodule Module do
end, {1, clause, neighbours, false}, old)
:elixir_def_overridable.overridable(module, merged)
end
end
end, tuples)
end
@doc """
@@ -967,6 +967,4 @@ defmodule Module do
raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
end
+14 -12
View File
@@ -19,11 +19,12 @@ defmodule Protocol do
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = :lists.map(fn _ -> quote(do: term) end,
:lists.seq(2, arity))
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
:lists.seq(2, arity))
call_args = [quote(do: t) | call_args]
quote do
@@ -440,7 +441,7 @@ defmodule Protocol do
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
:lists.foreach(fn {guard, mod} ->
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -449,7 +450,7 @@ defmodule Protocol do
false -> any_impl_for
end
end
end
end, builtin)
@spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do
@@ -609,16 +610,17 @@ defmodule Protocol do
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
specs = Module.get_attribute(module, :spec)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
:lists.map(fn {:spec, expr, caller} ->
if Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
end, specs)
found != []
:lists.any(& &1 == true, found)
end
## Helpers
+7 -6
View File
@@ -45,10 +45,11 @@ defmodule Record.Extractor do
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
# includes record but with macros and other attributes expanded,
# such as `-include(...)` and `-include_lib(...)`. This is done
# by using Erlang's epp.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
case :epp.parse_file(file, []) do
{:ok, form} ->
form
other ->
@@ -80,11 +81,11 @@ defmodule Record.Extractor do
defp eval_record(cons, form) do
form = form ++
[ {:function, 0, :hello, 0, [
{:clause, 0, [], [], [ cons ]} ]} ]
[{:function, 0, :hello, 0, [
{:clause, 0, [], [], [cons]}]}]
{:function, 0, :hello, 0, [
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
{:value, record, _} = :erl_eval.expr(record_ast, [])
record
+8 -3
View File
@@ -419,8 +419,8 @@ defmodule Regex do
The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In
case `\0` is used, the whole match is inserted.
captures to be accessed via `\N` or `\g{N}`, where `N` is the
capture. In case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument
@@ -479,6 +479,11 @@ defmodule Regex do
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest :: binary>>} = pick_int(rest)
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
@@ -550,7 +555,7 @@ defmodule Regex do
cond do
is_binary(part) ->
part
part > tuple_size(indexes) ->
part >= tuple_size(indexes) ->
""
true ->
get_index(string, elem(indexes, part))
+6 -4
View File
@@ -518,11 +518,11 @@ defmodule Stream do
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
def take(_enum, 0), do: %Stream{enum: []}
def take(enum, n) when n > 0 do
def take(enum, n) when n > 0 and is_integer(n) do
lazy enum, n, fn(f1) -> R.take(f1) end
end
def take(enum, n) when n < 0 do
def take(enum, n) when n < 0 and is_integer(n) do
&do_take(enum, abs(n), &1, &2)
end
@@ -699,9 +699,11 @@ defmodule Stream do
next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace)
else
{:halted, [:outer|acc]} ->
# Only take into account outer halts when the op is not halt itself.
# Otherwise, we were the ones wishing to halt, so we should just stop.
{:halted, [:outer|acc]} when op != :halt ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:halted, [:inner|acc]} ->
{:halted, [_|acc]} ->
next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
+10 -4
View File
@@ -132,10 +132,16 @@ defmodule Stream.Reducers do
defmacro take(f \\ nil) do
quote do
fn(entry, acc(h, n, t) = orig) ->
if n >= 1 do
cont_with_acc(unquote(f), entry, h, n-1, t)
else
{:halt, orig}
case n do
0 ->
{:halt, orig}
1 ->
case cont_with_acc(unquote(f), entry, h, n-1, t) do
{:cont, acc} -> {:halt, acc}
reason -> reason
end
_ ->
cont_with_acc(unquote(f), entry, h, n-1, t)
end
end
end
+4 -3
View File
@@ -650,13 +650,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
The pattern can also be a regex. In those cases, one can give `\N` or
`\g{N}` in the `replacement` string to access a specific capture in the
regex:
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
Notice we had to escape the escape character `\`. By giving `\0`,
one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the
+72 -7
View File
@@ -20,8 +20,8 @@ defmodule Supervisor do
defmodule Stack do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
def start_link(state, opts) do
GenServer.start_link(__MODULE__, state, opts)
end
def handle_call(:pop, _from, [h|t]) do
@@ -38,16 +38,17 @@ defmodule Supervisor do
# Import helpers for defining supervisors
import Supervisor.Spec
# We are going to supervise the Stack server which will
# be started with a single argument [:hello]
# We are going to supervise the Stack server which
# will be started with a single argument [:hello]
# and the default name of :sup_stack.
children = [
worker(Stack, [[:hello]])
worker(Stack, [[:hello], [name: :sup_stack]])
]
# Start the supervisor with our one child
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
Notice that when starting the GenServer, we have registered it
Notice that when starting the GenServer, we are registering it
with name `:sup_stack`, which allows us to call it directly and
get what is on the stack:
@@ -65,7 +66,7 @@ defmodule Supervisor do
Let's try it:
GenServer.call(:sup_stack, :pop)
=ERROR REPORT====
** (exit) exited in: GenServer.call(:sup_stack, :pop, 5000)
Luckily, since the server is being supervised by a supervisor, the
supervisor will automatically start a new one, with the default stack
@@ -136,6 +137,70 @@ defmodule Supervisor do
in this module behave slightly differently when this strategy is
used.
## Simple one for one
The simple one for one supervisor is useful when you want to dynamically
start and stop supervisor children. For example, imagine you want to
dynamically create multiple stacks. We can do so by defining a simple one
for one supervisor:
# Import helpers for defining supervisors
import Supervisor.Spec
# This time, we don't pass any argument because
# the argument will be given when we start the child
children = [
worker(Stack, [], restart: :transient)
]
# Start the supervisor with our one child
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)
There are a couple differences here:
* The simple one for one specification can define only one child which
works as a template for when we call `start_child/2`
* We have define the child to have restart strategy of transient. This
means that, if the child process exits due to a `:normal`, `:shutdown`
or `{:shutdown, term}` reason, it won't be restarted. This is useful
as it allows our workers to politely shutdown and be removed from the
simple one for one supervisor, without being restarted. You can find
more information about restart strategies on `Supervisor.Spec`
With the supervisor defined, let's dynamically start stacks:
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
GenServer.call(pid, :pop) #=> :hello
GenServer.call(pid, :pop) #=> :world
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
GenServer.call(pid, :pop) #=> :something
GenServer.call(pid, :pop) #=> :else
Supervisor.count_children(sup_pid)
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
## Exit reasons
From the example above, you may have noticed that the transient restart
strategy for the worker does not restart the child in case it crashes with
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
So one may ask: which exit reason should I choose when existing my worker?
There are three options:
* `:normal` - on such cases, the exit won't be logged, there is no restart
on transient mode and linked processes do not exit
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
logged, there is no restart on transient mode and linked processes exit
with the same reason unless trapping exits
* any other term - on such cases, the exit will be logged, there are
restarts on transient mode and linked processes exit with the same reason
unless trapping exits
## Name Registration
A supervisor is bound to the same name registration rules as a `GenServer`.
+3 -3
View File
@@ -72,7 +72,7 @@ defmodule System do
"""
@spec argv() :: [String.t]
def argv do
:elixir_code_server.call :argv
:elixir_config.get(:argv)
end
@doc """
@@ -83,7 +83,7 @@ defmodule System do
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({:argv, args})
:elixir_config.put(:argv, args)
end
@doc """
@@ -224,7 +224,7 @@ defmodule System do
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}
:elixir_config.update :at_exit, &[fun|&1]
end
@doc """
+55 -36
View File
@@ -11,18 +11,17 @@ defmodule URI do
import Bitwise
@ports %{
"ftp" => 21,
"http" => 80,
"https" => 443,
"ldap" => 389,
"sftp" => 22,
"tftp" => 69,
}
@schemes [
"ftp",
"http",
"https",
"ldap",
"sftp",
"tftp",
]
Enum.each @ports, fn {scheme, port} ->
Enum.each @schemes, fn scheme ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
@@ -47,8 +46,7 @@ defmodule URI do
"""
def default_port(scheme) when is_binary(scheme) do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Map.get(dict, scheme)
:elixir_config.get({:uri, scheme})
end
@doc """
@@ -59,8 +57,7 @@ defmodule URI do
new URIs.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
:elixir_config.put({:uri, scheme}, port)
end
@doc """
@@ -98,7 +95,10 @@ defmodule URI do
"""
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
case do_decode_query(q) do
nil -> dict
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
end
end
@doc """
@@ -107,19 +107,19 @@ defmodule URI do
## Examples
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
[{"foo", "1"}, {"bar", "2"}]
"""
def query_decoder(q) when is_binary(q) do
Stream.unfold(q, &do_decoder/1)
Stream.unfold(q, &do_decode_query/1)
end
defp do_decoder("") do
defp do_decode_query("") do
nil
end
defp do_decoder(q) do
defp do_decode_query(q) do
{first, next} =
case :binary.split(q, "&") do
[first, rest] -> {first, rest}
@@ -234,7 +234,7 @@ defmodule URI do
"""
def decode(uri) do
unpercent(uri)
unpercent(uri, "", false)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
@@ -250,23 +250,25 @@ defmodule URI do
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
unpercent(str, "", true)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
unpercent(tail, <<acc::binary, ?\s>>, spaces)
end
defp unpercent(<<>>), do: <<>>
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
end
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
defp unpercent(<<head, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, head>>, spaces)
end
defp unpercent(<<>>, acc, _spaces), do: acc
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
@@ -274,12 +276,17 @@ defmodule URI do
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
Parses a well-formed URI reference into its components.
URIs have portions that are handled specially for the particular
scheme of the URI. For example, http and https have different
default ports. Such values can be accessed and registered via
`URI.default_port/1` and `URI.default_port/2`.
Note this function expects a well-formed URI and does not perform
any validation. See the examples section below of how `URI.parse/1`
can be used to parse a wide range of relative URIs.
This function uses the parsing regular expression as defined
in the Appendix B of RFC3986.
When a URI is given without a port, the values registered via
`URI.default_port/1` and `URI.default_port/2` are used.
## Examples
@@ -288,12 +295,24 @@ defmodule URI do
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80}
iex> URI.parse("//elixir-lang.org/")
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("/foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
"""
def parse(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
+27 -2
View File
@@ -51,6 +51,19 @@ defmodule Version do
"~> 2.0.0"
`~>` will never include pre-release versions of its upper bound.
It can also be used to set an upper bound on only the major
version part. See the table below for `~>` requirements and
their corresponding translation.
`~>` | Translation
:------------- | :---------------------
`~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
`~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
`~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
`~> 2.0` | `>= 2.0.0 and < 3.0.0`
`~> 2.1` | `>= 2.1.0 and < 3.0.0`
"""
import Kernel, except: [match?: 2]
@@ -61,7 +74,7 @@ defmodule Version do
@type major :: String.t | non_neg_integer
@type minor :: non_neg_integer | nil
@type patch :: non_neg_integer | nil
@type pre :: [String.t]
@type pre :: [String.t | non_neg_integer]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
@@ -506,10 +519,22 @@ end
defimpl String.Chars, for: Version do
def to_string(version) do
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
pre = pre(version.pre)
build = if build = version.build, do: "+#{build}"
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
end
defp pre([]) do
""
end
defp pre(pre) do
"-" <>
Enum.map_join(pre, ".", fn
int when is_integer(int) -> Integer.to_string(int)
string when is_binary(string) -> string
end)
end
end
defimpl Inspect, for: Version do
+28 -6
View File
@@ -29,9 +29,12 @@ start(_Type, _Args) ->
end,
ok = io:setopts(standard_io, Opts),
%% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
ok -> ok;
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
end,
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
@@ -42,10 +45,29 @@ start(_Type, _Args) ->
ok
end,
elixir_sup:start_link().
URIs = [{<<"ftp">>, 21},
{<<"sftp">>, 22},
{<<"tftp">>, 69},
{<<"http">>, 80},
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
Config = [{at_exit, []},
{compiler_options, orddict:from_list(CompilerOpts)}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
{ok, Sup} ->
{ok, Sup, Tab};
{error, _Reason} = Error ->
elixir_config:delete(Tab),
Error
end.
stop(Tab) ->
elixir_config:delete(Tab).
stop(_S) ->
ok.
config_change(_Changed, _New, _Remove) ->
ok.
+1 -1
View File
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of
{_, _} -> apply(?kernel, Fun, Args);
false -> Callback()
false -> Callback()
end.
env() ->
+39 -67
View File
@@ -6,14 +6,11 @@
-define(timeout, 30000).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
loaded=[],
at_exit=[],
pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
mod_pool={[],0},
mod_ets=dict:new(),
compilation_status=[]
}).
call(Args) ->
@@ -29,14 +26,21 @@ start_link() ->
init(ok) ->
%% We attempt to load those modules here so throughout
%% the codebase we can use a code:is_loaded/1 check on
%% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
%% the codebase we can avoid code:is_loaded/1 checks.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% The table where we store module definitions
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
{ok, #elixir_code_server{}}.
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
{Ref, New} = defmodule(Pid, Tuple, Config),
{reply, Ref, New};
handle_call({undefmodule, Ref}, _From, Config) ->
{reply, ok, undefmodule(Ref, Config)};
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -53,18 +57,6 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call(at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config};
handle_call(flush_at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
handle_call(argv, _From, Config) ->
{reply, Config#elixir_code_server.argv, Config};
handle_call(compiler_options, _From, Config) ->
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
@@ -73,20 +65,11 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
case Config#elixir_code_server.mod_pool of
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end;
handle_call(paths, _From, Config) ->
@@ -95,20 +78,11 @@ handle_call(paths, _From, Config) ->
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
CompilerOptions = elixir_config:get(compiler_options),
case orddict:find(warnings_as_errors, CompilerOptions) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
@@ -137,8 +111,8 @@ handle_cast({unload_files, Files}, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
@@ -146,7 +120,10 @@ handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
handle_info(_Request, Config) ->
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
{noreply, undefmodule(Ref, Config)};
handle_info(_Msg, Config) ->
{noreply, Config}.
terminate(_Reason, _Config) ->
@@ -158,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
+38 -5
View File
@@ -6,7 +6,7 @@
%% Public API
get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options),
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
@@ -78,7 +78,7 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = elixir_code_server:call(erl_compiler_options),
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
@@ -92,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end).
options() ->
case elixir_config:get(erl_compiler_options) of
nil ->
elixir_config:update(erl_compiler_options, fun options/1);
Opts ->
Opts
end.
options(nil) ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end;
options(Opts) ->
Opts.
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
@@ -142,8 +172,8 @@ 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)
true -> [debug_info] ++ options();
false -> options()
end,
inner_module(Forms, Final, false, E, Callback).
@@ -177,7 +207,10 @@ no_auto_import() ->
core() ->
{ok, _} = application:ensure_all_started(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
New = orddict:from_list([{docs,false},{internal,true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
core_file(File) ->
+65
View File
@@ -0,0 +1,65 @@
-module(elixir_config).
-compile({no_auto_import, [get/1]}).
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-behaviour(gen_server).
%% public api
new(Opts) ->
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
true = ets:insert_new(?MODULE, Opts),
Tab.
delete(?MODULE) ->
ets:delete(?MODULE).
put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
case ets:lookup(?MODULE, Key) of
[{_, Value}] -> Value;
[] -> nil
end.
update(Key, Fun) ->
gen_server:call(?MODULE, {update, Key, Fun}).
get_and_put(Key, Value) ->
gen_server:call(?MODULE, {get_and_put, Key, Value}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
%% gen_server api
init(Tab) ->
%% Ets table must be writable
public = ets:info(Tab, protection),
{ok, Tab}.
handle_call({put, Key, Value}, _From, Tab) ->
ets:insert(Tab, {Key, Value}),
{reply, ok, Tab};
handle_call({update, Key, Fun}, _From, Tab) ->
Value = Fun(get(Key)),
ets:insert(Tab, {Key, Value}),
{reply, Value, Tab};
handle_call({get_and_put, Key, Value}, _From, Tab) ->
OldValue = get(Key),
ets:insert(Tab, {Key, Value}),
{reply, OldValue, Tab}.
handle_cast(Cast, Tab) ->
{stop, {bad_cast, Cast}, Tab}.
handle_info(_Msg, Tab) ->
{noreply, Tab}.
code_change(_OldVsn, Tab, _Extra) ->
{ok, Tab}.
terminate(_Reason, _Tab) ->
ok.
+2 -2
View File
@@ -3,8 +3,8 @@
handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
-define(timeout, 30000). %% 30 seconds
-define(limit, 4294967295). %% 2^32 - 1
next() ->
gen_server:call(?MODULE, next, ?timeout).
+3 -3
View File
@@ -47,10 +47,10 @@ store(Module, Function, GenerateName) ->
false -> {Kind, Name}
end,
case code:is_loaded('Elixir.Module.LocalsTracker') of
{_, _} ->
case elixir_compiler:get_opt(internal) of
false ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ ->
true ->
ok
end,
+1 -1
View File
@@ -215,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
end.
caller(Line, #{module := nil} = E) ->
{elixir_compiler, '__FILE__', 2, location(Line, E)};
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
caller(Line, #{module := Module, function := nil} = E) ->
{Module, '__MODULE__', 0, location(Line, E)};
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
+11 -1
View File
@@ -55,6 +55,13 @@ parse_error(Line, File, Error, <<>>) ->
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Produce a human-readable message for errors before a sigil
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
do_raise(Line, File, 'Elixir.SyntaxError', Message);
%% Aliases are wrapped in ['']
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
Rest =
@@ -136,10 +143,13 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
ok;
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
+2 -2
View File
@@ -490,8 +490,8 @@ validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
+5 -33
View File
@@ -126,29 +126,6 @@ collect_filters([H|T], Acc) ->
collect_filters([], Acc) ->
{Acc, []}.
%% If all we have is one enum generator, we check if it is a list
%% for optimization otherwise fallback to the reduce generator.
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
case Right of
{cons, _, _, _} ->
build_comprehension(Line, Orig, Expr, Into);
{Other, _, _} when Other == tuple; Other == map ->
build_reduce(Orig, Expr, Into, Acc, S);
_ ->
Clauses = [{enum, Meta, Left, Var, Filters}],
{'case', -1, Right, [
{clause, -1,
[Var],
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
[build_comprehension(Line, Clauses, Expr, Into)]},
{clause, -1,
[Var],
[],
[build_reduce(Clauses, Expr, Into, Acc, S)]}
]}
end;
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Line, Clauses, Expr, Into);
@@ -165,7 +142,7 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
},
},
TryExpr =
{'try', Line,
@@ -199,10 +176,8 @@ build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
True = pair(Line, cont, Inner),
False = pair(Line, cont, Acc),
True = build_reduce_clause(T, Expr, Acc, Acc, S),
False = Acc,
Clauses0 =
case is_var(Left) of
@@ -218,11 +193,8 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
[Left, Acc], [],
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
%% Use -1 because in case of no returns we don't care about the result
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
+12 -10
View File
@@ -10,17 +10,19 @@
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Dest, Callback) ->
case code:is_loaded(?tracker) of
{file, _} ->
{ok, Pid} = ?tracker:start_link(Dest),
try
Callback(Pid)
after
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
unlink(Pid), ?tracker:stop(Pid)
end;
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(Dest),
try Callback(Pid) of
Res ->
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
Res
after
unlink(Pid),
?tracker:stop(Pid)
end;
true ->
Callback(nil)
end.
+3 -3
View File
@@ -72,12 +72,12 @@ rewrite_clause(Else, _) -> Else.
%% TRACKING
setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} ->
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(elixir_module:data_table(Module), {?attr, Pid}),
ok;
false ->
true ->
ok
end.
+15 -8
View File
@@ -5,9 +5,11 @@
expand_map(Meta, [{'|', UpdateMeta, [Left, Right]}], E) ->
{[ELeft|ERight], EA} = elixir_exp:expand_args([Left|Right], E),
validate_kv(Meta, ERight, Right, E),
{{'%{}', Meta, [{'|', UpdateMeta, [ELeft, ERight]}]}, EA};
expand_map(Meta, Args, E) ->
{EArgs, EA} = elixir_exp:expand_args(Args, E),
validate_kv(Meta, EArgs, Args, E),
{{'%{}', Meta, EArgs}, EA}.
expand_struct(Meta, Left, Right, E) ->
@@ -35,6 +37,15 @@ expand_struct(Meta, Left, Right, E) ->
{{'%', EMeta, [ELeft, ERight]}, EE}.
validate_kv(Meta, KV, Original, E) ->
lists:foldl(fun
({_K, _V}, Acc) -> Acc + 1;
(_, Acc) ->
compile_error(Meta, ?m(E, file),
"expected key-value pairs in a map, got: ~ts",
['Elixir.Macro':to_string(lists:nth(Acc, Original))])
end, 1, KV).
translate_map(Meta, Args, S) ->
{Assocs, TUpdate, US} = extract_assoc_update(Args, S),
translate_map(Meta, Assocs, TUpdate, US).
@@ -139,14 +150,10 @@ translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
Line = ?line(Meta),
{TArgs, SA} = lists:mapfoldl(fun
({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2};
(Other, _Acc) ->
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
['Elixir.Macro':to_string(Other)])
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2}
end, S, Assocs),
build_map(Line, TUpdate, TArgs, SA).
+11 -11
View File
@@ -51,7 +51,7 @@ do_compile(Line, Module, Block, Vars, E) ->
check_module_availability(Line, File, Module),
Docs = elixir_compiler:get_opt(docs),
{Data, Defs, Clas} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
{Data, Defs, Clas, Ref} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
@@ -92,7 +92,7 @@ do_compile(Line, Module, Block, Vars, E) ->
ets:delete(Data),
ets:delete(Defs),
ets:delete(Clas),
ets:delete(elixir_modules, Module)
elixir_code_server:call({undefmodule, Ref})
end.
%% Hook that builds both attribute and functions and set up common hooks.
@@ -110,7 +110,8 @@ build(Line, File, Module, Docs, Lexical) ->
Defs = ets:new(Module, [set, public]),
Clas = ets:new(Module, [bag, public]),
ets:insert(elixir_modules, {Module, Data, Defs, Clas, Line, File}),
Ref = elixir_code_server:call({defmodule, self(),
{Module, Data, Defs, Clas, Line, File}}),
ets:insert(Data, {before_compile, []}),
ets:insert(Data, {after_compile, []}),
@@ -132,7 +133,7 @@ build(Line, File, Module, Docs, Lexical) ->
elixir_locals:setup(Module),
elixir_def_overridable:setup(Module),
{Data, Defs, Clas}.
{Data, Defs, Clas, Ref}.
%% Receives the module representation and evaluates it.
@@ -201,8 +202,8 @@ process_external_resource(Line, File, Value) ->
%% Types
types_form(Line, File, Data, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
case elixir_compiler:get_opt(internal) of
false ->
Types0 = get_typespec(Data, type) ++ get_typespec(Data, typep)
++ get_typespec(Data, opaque),
@@ -214,7 +215,7 @@ types_form(Line, File, Data, Forms0) ->
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
{error, _} ->
true ->
Forms0
end.
@@ -245,8 +246,8 @@ typedocs_attributes(Types, Forms) ->
%% Specs
specs_form(Data, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
case elixir_compiler:get_opt(internal) of
false ->
Specs0 = get_typespec(Data, spec) ++ get_typespec(Data, callback),
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
{Kind, Expr, Caller} <- Specs0],
@@ -254,8 +255,7 @@ specs_form(Data, Defmacro, Defmacrop, Forms) ->
translate_macro_spec(Spec, Defmacro, Defmacrop)
end, Specs1),
specs_attributes(Forms, Specs2);
{error, _} ->
true ->
Forms
end.
+10 -1
View File
@@ -7,6 +7,16 @@ start_link() ->
init(ok) ->
Workers = [
{
elixir_config,
{elixir_config, start_link, []},
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_config] % Modules = [Module] | dynamic
},
{
elixir_code_server,
{elixir_code_server, start_link, []},
@@ -28,5 +38,4 @@ init(ok) ->
}
],
_ = ets:new(elixir_modules, [set, public, named_table, {read_concurrency, true}]),
{ok, {{one_for_one, 3, 10}, Workers}}.
+4 -1
View File
@@ -10,7 +10,7 @@
translate({'=', Meta, [Left, Right]}, S) ->
Return = case Left of
{'_', _, Atom} when is_atom(Atom) -> false;
{'_', _, Atom} when is_atom(Atom) -> S#elixir_scope.return;
_ -> true
end,
@@ -257,6 +257,9 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
%% the is_map/1 guard instead of matching on map. Hopefully
%% we can use a match on 17.1.
%%
%% In the future, we could also use maps:get/2 instead
%% of pattern match, reducing the AST footprint.
%%
%% http://erlang.org/pipermail/erlang-bugs/2014-April/004338.html
{{'case', -1, TLeft, [
{clause, -1,
+10
View File
@@ -156,6 +156,16 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
erl_tuple_size(Meta, Var, 3),
erl_record_compare(Meta, Var, badstruct));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadMapError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_record_compare(Meta, Var, badmap));
erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_record_compare(Meta, Var, badkey));
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
erl_or(Meta,
{erl(Meta, '=='), Meta, [Var, badarg]},
+17 -1
View File
@@ -291,7 +291,7 @@ defmodule Kernel.ExceptionTest do
test "format_fa" do
assert Exception.format_fa(fn -> end, 1) =~
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
~r"#Function<\d+\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
end
import Exception, only: [message: 1]
@@ -327,4 +327,20 @@ defmodule Kernel.ExceptionTest do
assert %ErlangError{original: :sample} |> message ==
"erlang error: :sample"
end
test "format_stacktrace from file" do
assert_raise ArgumentError, fn ->
Code.eval_string("def foo do end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (file)"
end
test "format_stacktrace from module" do
assert_raise ArgumentError, fn ->
Code.eval_string("defmodule Foo do raise ArgumentError, ~s(oops) end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (module)"
end
end
+4
View File
@@ -0,0 +1,4 @@
LF
CR CRLF
LFCR
+2
View File
@@ -299,6 +299,8 @@ defmodule Inspect.MapTest do
assert_raise ArgumentError, msg, fn ->
inspect(%Failing{})
end
assert [{Inspect.Inspect.MapTest.Failing, :inspect, 2, _}|_] = System.stacktrace
end
test :exception do
+66 -9
View File
@@ -14,24 +14,24 @@ defmodule IO.ANSI.DocsTest do
test "heading is formatted" do
result = format_heading("wibble")
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m\e[1m")
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m")
assert String.ends_with?(result, "\e[0m\n\e[0m")
assert String.contains?(result, " wibble ")
end
test "first level heading is converted" do
result = format("# wibble\n\ntext\n")
assert result == "\e[33m\e[1mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
end
test "second level heading is converted" do
result = format("## wibble\n\ntext\n")
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
end
test "third level heading is converted" do
result = format("## wibble\n\ntext\n")
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
end
test "code block is converted" do
@@ -103,6 +103,9 @@ defmodule IO.ANSI.DocsTest do
result = format("*world*")
assert result == "\e[1mworld\e[0m\n\e[0m"
result = format("*world*.")
assert result == "\e[1mworld\e[0m.\n\e[0m"
result = format("**world**")
assert result == "\e[1mworld\e[0m\n\e[0m"
@@ -127,6 +130,28 @@ defmodule IO.ANSI.DocsTest do
assert result == "\e[36mhello world\e[0m\n\e[0m"
end
test "multiple stars/underscores/backticks work" do
result = format("*hello world* *hello world*")
assert result == "\e[1mhello world\e[0m \e[1mhello world\e[0m\n\e[0m"
result = format("_hello world_ _hello world_")
assert result == "\e[4mhello world\e[0m \e[4mhello world\e[0m\n\e[0m"
result = format("`hello world` `hello world`")
assert result == "\e[36mhello world\e[0m \e[36mhello world\e[0m\n\e[0m"
end
test "multiple stars/underscores/backticks work when separated by other words" do
result = format("*hello world* unit test *hello world*")
assert result == "\e[1mhello world\e[0m unit test \e[1mhello world\e[0m\n\e[0m"
result = format("_hello world_ unit test _hello world_")
assert result == "\e[4mhello world\e[0m unit test \e[4mhello world\e[0m\n\e[0m"
result = format("`hello world` unit test `hello world`")
assert result == "\e[36mhello world\e[0m unit test \e[36mhello world\e[0m\n\e[0m"
end
test "star/underscore preceeded by space doesn't get interpreted" do
result = format("_unit _size")
assert result == "_unit _size\n\e[0m"
@@ -138,6 +163,43 @@ defmodule IO.ANSI.DocsTest do
assert result == "*unit *size\n\e[0m"
end
test "star/underscore/backtick preceeded by non-space delimiters gets interpreted" do
result = format("(`hello world`)")
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
result = format("<`hello world`>")
assert result == "<\e[36mhello world\e[0m>\n\e[0m"
result = format("(*hello world*)")
assert result == "(\e[1mhello world\e[0m)\n\e[0m"
result = format("@*hello world*@")
assert result == "@\e[1mhello world\e[0m@\n\e[0m"
result = format("(_hello world_)")
assert result == "(\e[4mhello world\e[0m)\n\e[0m"
result = format("'_hello world_'")
assert result == "'\e[4mhello world\e[0m'\n\e[0m"
end
test "star/underscore/backtick starts/ends within a word doesn't get interpreted" do
result = format("foo_bar, foo_bar_baz!")
assert result == "foo_bar, foo_bar_baz!\n\e[0m"
result = format("_foo_bar")
assert result == "_foo_bar\n\e[0m"
result = format("foo_bar_")
assert result == "foo_bar_\n\e[0m"
result = format("foo*bar, foo*bar*baz!")
assert result == "foo*bar, foo*bar*baz!\n\e[0m"
result = format("*foo*bar")
assert result == "*foo*bar\n\e[0m"
result = format("foo*bar*")
assert result == "foo*bar*\n\e[0m"
end
test "backtick preceeded by space gets interpreted" do
result = format("`unit `size")
assert result == "\e[36munit \e[0msize\n\e[0m"
@@ -200,11 +262,6 @@ defmodule IO.ANSI.DocsTest do
assert result == "\e[36m__world__\e[0m\n\e[0m"
end
test "backtick works inside parenthesis" do
result = format("(`hello world`)")
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
end
test "escaping of underlines within links" do
result = format("(http://en.wikipedia.org/wiki/ANSI_escape_code)")
assert result == "(http://en.wikipedia.org/wiki/ANSI_escape_code)\n\e[0m"
+6
View File
@@ -22,6 +22,12 @@ defmodule IOTest do
assert File.close(file) == :ok
end
test :binread_all do
{:ok, file} = File.open(Path.expand('fixtures/file.bin', __DIR__))
assert "LF\nCR\rCRLF\r\nLFCR\n\r" == IO.binread(file, :all)
assert File.close(file) == :ok
end
test :getn do
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
assert "F" == IO.getn(file, "")
@@ -151,6 +151,12 @@ defmodule Kernel.ComprehensionTest do
assert for({_,x} <- [1, 2, a: 3, b: 4, c: 5], do: x * 2) == [6, 8, 10]
end
test "list for comprehension matched to '_' on last line of block" do
assert (if true do
_ = for x <- [1, 2, 3], do: x * 2
end) == [2, 4, 6]
end
test "list for comprehensions with filters" do
assert for(x <- [1, 2, 3], x > 1, x < 3, do: x * 2) == [4]
end
+24 -2
View File
@@ -89,6 +89,12 @@ defmodule Kernel.ErrorsTest do
'+.foo'
end
test :syntax_error_before_sigil do
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: sigil ~s with content 'bar baz'",
'~s(foo) ~s(bar baz)'
end
test :compile_error_on_op_ambiguity do
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
"please use explicit parenthesis or even spaces"
@@ -231,6 +237,10 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: '{'",
'%{:a, :b}{a: :b}'
assert_compile_fail CompileError,
"nofile:1: expected key-value pairs in a map, got: put_in(foo.bar().baz(), nil)",
'foo = 1; %{put_in(foo.bar.baz, nil), :bar}'
end
test :struct_fields_on_defstruct do
@@ -324,6 +334,13 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail CompileError,
"nofile:1: invalid arguments for quote",
'quote 1'
assert_compile_fail CompileError,
"nofile:1: invalid options for quote, expected a keyword list",
'''
quote :foo do
foo
end
'''
end
test :invalid_calls do
@@ -739,8 +756,13 @@ defmodule Kernel.ErrorsTest do
end
'''
assert_compile_fail CompileError,
"nofile:2: spec for undefined function ErrorsTest.omg/0",
if :erlang.system_info(:otp_release) >= '18' do
message = "nofile:2: spec for undefined function omg/0"
else
message = "nofile:2: spec for undefined function ErrorsTest.omg/0"
end
assert_compile_fail CompileError, message,
'''
defmodule ErrorsTest do
@spec omg :: atom
@@ -304,6 +304,36 @@ defmodule Kernel.RaiseTest do
assert result == "no match of right hand side value: 0"
end
if :erlang.system_info(:otp_release) >= '18' do
test :bad_key_error do
result = try do
%{%{} | foo: :bar}
rescue
x in [KeyError] -> Exception.message(x)
end
assert result == "key :foo not found"
result = try do
%{}.foo
rescue
x in [KeyError] -> Exception.message(x)
end
assert result == "key :foo not found in: %{}"
end
test :bad_map_error do
result = try do
%{zero(0) | foo: :bar}
rescue
x in [BadMapError] -> Exception.message(x)
end
assert result == "expected a map, got: 0"
end
end
test :case_clause_error do
x = :example
result = try do
+30 -21
View File
@@ -154,15 +154,6 @@ defmodule Kernel.TypespecTest do
types(module)
end
test "@type with a range" do
module = test_module do
@type mytype :: range(1, 10)
end
assert [type: {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
types(module)
end
test "@type with a range op" do
module = test_module do
@type mytype :: 1..10
@@ -177,11 +168,19 @@ defmodule Kernel.TypespecTest do
@type mytype :: %{hello: :world}
end
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
]},
[]}] = types(module)
if :erlang.system_info(:otp_release) >= '18' do
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]}
]},
[]}] = types(module)
else
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
]},
[]}] = types(module)
end
end
test "@type with a struct" do
@@ -190,13 +189,23 @@ defmodule Kernel.TypespecTest do
@type mytype :: %TestTypespec{hello: :world}
end
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
]},
[]}] = types(module)
if :erlang.system_info(:otp_release) >= '18' do
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, [{:atom, _, :__struct__}, {:atom, _, TestTypespec}]},
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]},
{:type, _, :map_field_assoc, [{:atom, _, :other}, {:type, _, :term, []}]}
]},
[]}] = types(module)
else
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
]},
[]}] = types(module)
end
end
test "@type with undefined struct" do
+42
View File
@@ -6,11 +6,25 @@ defmodule KernelTest do
test "=~/2" do
assert ("abcd" =~ ~r/c(d)/) == true
assert ("abcd" =~ ~r/e/) == false
assert ("abcd" =~ ~R/c(d)/) == true
assert ("abcd" =~ ~R/e/) == false
string = "^ab+cd*$"
assert (string =~ "ab+") == true
assert (string =~ "bb") == false
assert ("abcd" =~ ~r//) == true
assert ("abcd" =~ ~R//) == true
assert ("abcd" =~ "") == true
assert ("" =~ ~r//) == true
assert ("" =~ ~R//) == true
assert ("" =~ "") == true
assert ("" =~ "abcd") == false
assert ("" =~ ~r/abcd/) == false
assert ("" =~ ~R/abcd/) == false
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ "hello"
end
@@ -18,6 +32,34 @@ defmodule KernelTest do
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ ~r"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ ~R"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
~r"hello" =~ "hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
~r"hello" =~ ~r"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
:abcd =~ ~r//
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
:abcd =~ ""
end
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
"abcd" =~ nil
end
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
"abcd" =~ :abcd
end
end
test "^" do
+10 -2
View File
@@ -85,8 +85,16 @@ defmodule MapTest do
test "update maps" do
assert %{two_items_map | a: 3} == %{a: 3, b: 2}
assert_raise ArgumentError, fn ->
%{two_items_map | c: 3}
# TODO: proper handling of API changes in different Erlang/OTP releases
case :erlang.system_info(:otp_release) do
'17' ->
assert_raise ArgumentError, fn ->
%{two_items_map | c: 3}
end
_ ->
assert_raise KeyError, fn ->
%{two_items_map | c: 3}
end
end
end
+12 -4
View File
@@ -121,11 +121,19 @@ defmodule ProtocolTest do
end
test "protocol defines callbacks" do
assert get_callbacks(Sample, :ok, 1) ==
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
if :erlang.system_info(:otp_release) >= '18' do
assert get_callbacks(Sample, :ok, 1) ==
[{:type, [9], :fun, [{:type, [9], :product, [{:user_type, [9], :t, []}]}, {:type, [9], :boolean, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, [16], :fun, [{:type, [16], :product, [{:user_type, [16], :t, []}]}, {:type, [16], :term, []}]}]
else
assert get_callbacks(Sample, :ok, 1) ==
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
end
end
test "protocol defines attributes" do
+4 -1
View File
@@ -52,7 +52,10 @@ defmodule RecordTest do
Record.defrecord :timestamp, [:date, :time]
Record.defrecord :user, __MODULE__, name: "john", age: 25
Record.defrecordp :file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
Record.defrecordp :file_info,
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
Record.defrecordp :certificate, :OTPCertificate,
Record.extract(:OTPCertificate, from_lib: "public_key/include/public_key.hrl")
test "records generates macros that generates tuples" do
record = user()
+4
View File
@@ -189,6 +189,10 @@ defmodule RegexTest do
assert Regex.replace(~r(b), "abc", "d") == "adc"
assert Regex.replace(~r(b), "abc", "[\\0]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\1]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\2]") == "a[]c"
assert Regex.replace(~r[(b)], "abc", "[\\3]") == "a[]c"
assert Regex.replace(~r(b), "abc", "[\\g{0}]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\g{1}]") == "a[b]c"
assert Regex.replace(~r(b), "abcbe", "d") == "adcde"
assert Regex.replace(~r(b), "abcbe", "d", global: false) == "adcbe"
+18 -4
View File
@@ -88,6 +88,8 @@ defmodule StreamTest do
[[1], [2, 2], [3], [4, 4, 6], [7, 7]]
assert stream |> Stream.take(3) |> Enum.to_list ==
[[1], [2, 2], [3]]
assert 1..10 |> Stream.chunk(2) |> Enum.take(2) ==
[[1, 2], [3, 4]]
end
test "chunk_by/2 is zippable" do
@@ -324,11 +326,23 @@ defmodule StreamTest do
assert Enum.take(stream, 5) == [1, 2, 3, 4, 3]
end
test "flat_map/2 properly halts both inner and outer stream when inner stream is halted" do
# Fixes a bug that, when the inner stream was done,
# sending it a halt would cause it to return the
# inner stream was halted, forcing flat_map to get
# the next value from the outer stream, evaluate it,
# get annother inner stream, just to halt it.
assert [1, 2] # 2 should never be used
|> Stream.flat_map(fn 1 -> Stream.repeatedly(fn -> 1 end) end)
|> Stream.flat_map(fn 1 -> Stream.repeatedly(fn -> 1 end) end)
|> Enum.take(1) == [1]
end
test "interval/1" do
stream = Stream.interval(10)
now = :erlang.now
now = :os.timestamp
assert Enum.take(stream, 5) == [0, 1, 2, 3, 4]
assert :timer.now_diff(:erlang.now, now) > 50000
assert :timer.now_diff(:os.timestamp, now) > 50000
end
test "into/2 and run/1" do
@@ -608,9 +622,9 @@ defmodule StreamTest do
test "timer/1" do
stream = Stream.timer(10)
now = :erlang.now
now = :os.timestamp
assert Enum.to_list(stream) == [0]
assert :timer.now_diff(:erlang.now, now) > 10000
assert :timer.now_diff(:os.timestamp, now) > 10000
end
test "unfold/2" do
+2 -7
View File
@@ -3,17 +3,12 @@ Code.require_file "test_helper.exs", __DIR__
defmodule StringIOTest do
use ExUnit.Case, async: true
test "start and stop" do
test "open and close" do
{:ok, pid} = StringIO.open("")
assert StringIO.close(pid) == {:ok, {"", ""}}
end
test "start_link and stop" do
{:ok, pid} = StringIO.open("")
assert StringIO.close(pid) == {:ok, {"", ""}}
end
test "peek" do
test "contents" do
{:ok, pid} = StringIO.open("abc")
IO.write(pid, "edf")
assert StringIO.contents(pid) == {"abc", "edf"}
+4 -1
View File
@@ -135,6 +135,8 @@ defmodule StringTest do
test :rstrip do
assert String.rstrip("") == ""
assert String.rstrip("1\n") == "1"
assert String.rstrip("\r\n") == ""
assert String.rstrip(" abc ") == " abc"
assert String.rstrip(" abc a") == " abc a"
assert String.rstrip("a abc a\n\n") == "a abc a"
@@ -192,6 +194,7 @@ defmodule StringTest do
assert String.reverse("Hello ∂og") == "go∂ olleH"
assert String.reverse("Ā̀stute") == "etutsĀ̀"
assert String.reverse(String.reverse("Hello World")) == "Hello World"
assert String.reverse(String.reverse("Hello \r\n World")) == "Hello \r\n World"
end
test :replace do
@@ -238,7 +241,7 @@ defmodule StringTest do
# Extended
assert String.graphemes("Ā̀stute") == ["Ā̀", "s", "t", "u", "t", "e"]
# CLRF
assert String.graphemes("\n\r\f") == ["\n\r", "\f"]
assert String.graphemes("\r\n\f") == ["\r\n", "\f"]
# Regional indicator
assert String.graphemes("\x{1F1E6}\x{1F1E7}\x{1F1E8}") == ["\x{1F1E6}\x{1F1E7}\x{1F1E8}"]
# Hangul
+12 -4
View File
@@ -142,17 +142,25 @@ defmodule URITest do
test :default_port do
assert URI.default_port("http") == 80
assert URI.default_port("unknown") == nil
try do
URI.default_port("http", 8000)
assert URI.default_port("http") == 8000
after
URI.default_port("http", 80)
end
assert URI.default_port("unknown") == nil
URI.default_port("unknown", 13)
assert URI.default_port("unknown") == 13
end
test :parse_bad_uris do
assert URI.parse("https:??@?F?@#>F//23/")
assert URI.parse("")
assert URI.parse(":https")
assert URI.parse("https")
assert URI.parse("https:??@?F?@#>F//23/")
assert URI.parse(":https").path == ":https"
assert URI.parse("https").path == "https"
assert URI.parse("ht\0tps://foo.com").path == "ht\0tps://foo.com"
end
test :ipv6_addresses do
+2
View File
@@ -72,6 +72,8 @@ defmodule VersionTest do
assert V.parse("1.0.0-dev") |> elem(1) |> to_string == "1.0.0-dev"
assert V.parse("1.0.0+lol") |> elem(1) |> to_string == "1.0.0+lol"
assert V.parse("1.0.0-dev+lol") |> elem(1) |> to_string == "1.0.0-dev+lol"
assert V.parse("1.0.0-0") |> elem(1) |> to_string == "1.0.0-0"
assert V.parse("1.0.0-rc.0") |> elem(1) |> to_string == "1.0.0-rc.0"
end
test "invalid match" do
+51 -34
View File
@@ -15,11 +15,11 @@ defmodule String.Unicode do
data_path = Path.join(__DIR__, "UnicodeData.txt")
{codes, whitespace} = Enum.reduce File.stream!(data_path), {[], []}, fn(line, {cacc, wacc}) ->
[ codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, title ] = :binary.split(line, ";", [:global])
[codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, title] = :binary.split(line, ";", [:global])
title = :binary.part(title, 0, byte_size(title) - 1)
@@ -36,42 +36,42 @@ defmodule String.Unicode do
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
[codepoint, lower, title, upper, _comment] = :binary.split(line, "; ", [:global])
key = to_binary.(codepoint)
:lists.keystore(key, 1, acc, {key, to_binary.(upper), to_binary.(lower), to_binary.(title)})
end
# Downcase
def downcase(string), do: do_downcase(string) |> IO.iodata_to_binary
def downcase(string), do: downcase(string, "")
for {codepoint, _upper, lower, _title} <- codes, lower && lower != codepoint do
defp do_downcase(unquote(codepoint) <> rest) do
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
defp downcase(unquote(codepoint) <> rest, acc) do
downcase(rest, acc <> unquote(lower))
end
end
defp do_downcase(<< char, rest :: binary >>) do
[char|do_downcase(rest)]
defp downcase(<<char, rest :: binary>>, acc) do
downcase(rest, <<acc::binary, char>>)
end
defp do_downcase(""), do: []
defp downcase("", acc), do: acc
# Upcase
def upcase(string), do: do_upcase(string) |> IO.iodata_to_binary
def upcase(string), do: upcase(string, "")
for {codepoint, upper, _lower, _title} <- codes, upper && upper != codepoint do
defp do_upcase(unquote(codepoint) <> rest) do
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
defp upcase(unquote(codepoint) <> rest, acc) do
upcase(rest, acc <> unquote(upper))
end
end
defp do_upcase(<< char, rest :: binary >>) do
[char|do_upcase(rest)]
defp upcase(<<char, rest :: binary>>, acc) do
upcase(rest, <<acc::binary, char>>)
end
defp do_upcase(""), do: []
defp upcase("", acc), do: acc
# Titlecase once
@@ -88,7 +88,6 @@ defmodule String.Unicode do
end
# Strip
def lstrip(""), do: ""
for codepoint <- whitespace do
@@ -99,27 +98,45 @@ defmodule String.Unicode do
def lstrip(other) when is_binary(other), do: other
def rstrip(string) when is_binary(string) do
do_rstrip(string, [], [])
end
@whitespace_max_size 3
for codepoint <- whitespace do
c = :binary.bin_to_list(codepoint) |> :lists.reverse
# We need to increment @whitespace_max_size as well
# as the small table (_s) if we add a new entry here.
case byte_size(codepoint) do
3 ->
defp do_rstrip_l(unquote(codepoint)), do: -3
2 ->
defp do_rstrip_l(<<_, unquote(codepoint)>>), do: -2
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
defp do_rstrip_s(unquote(codepoint)), do: <<>>
1 ->
defp do_rstrip_l(<<unquote(codepoint), unquote(codepoint), unquote(codepoint)>>), do: -3
defp do_rstrip_l(<<_, unquote(codepoint), unquote(codepoint)>>), do: -2
defp do_rstrip_l(<<_, _, unquote(codepoint)>>), do: -1
defp do_rstrip_s(<<x, unquote(codepoint)>>), do: do_rstrip_s(<<x>>)
defp do_rstrip_s(unquote(codepoint)), do: <<>>
end
end
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
do_rstrip(rest, nil, [char|acc2])
defp do_rstrip_l(_), do: 0
defp do_rstrip_s(o), do: o
def rstrip(string) when is_binary(string) do
rstrip(string, byte_size(string))
end
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
do_rstrip(rest, nil, [char|acc1])
defp rstrip(string, size) when size < @whitespace_max_size do
do_rstrip_s(string)
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> IO.iodata_to_binary
defp rstrip(string, size) do
trail = binary_part(string, size, -@whitespace_max_size)
case do_rstrip_l(trail) do
0 -> string
x -> rstrip(binary_part(string, 0, size + x), size + x)
end
end
# Split
@@ -190,7 +207,7 @@ defmodule String.Graphemes do
end
cluster = Enum.reduce File.stream!(cluster_path), HashDict.new, fn(line, dict) ->
[ _full, first, last, class ] = Regex.run(regex, line)
[_full, first, last, class] = Regex.run(regex, line)
# Skip surrogates
if first == "D800" and last == "DFFF" do
@@ -208,8 +225,8 @@ defmodule String.Graphemes do
end
# Don't break CRLF
def next_grapheme(<< ?\n, ?\r, rest :: binary >>) do
{"\n\r", rest}
def next_grapheme(<< ?\r, ?\n, rest :: binary >>) do
{"\r\n", rest}
end
# Break on control
+5 -17
View File
@@ -207,26 +207,16 @@ defmodule ExUnit.Assertions do
true
end
@doc """
Asserts `value` is true.
If it fails, it raises an expectation error
using the given `left` and `right` values.
You probably don't need to use this—the regular `assert` function
handles this for you.
## Examples
assert this > that, this, that, "more than"
"""
@doc false
def assert(value, left, right, message) when is_binary(message) do
assert(value, left: left, right: right, message: message)
end
@doc """
Asserts a message was or is going to be received. Unlike
`assert_received`, it has a default timeout of 100 milliseconds.
Asserts a message was or is going to be received.
Unlike `assert_received`, it has a default timeout
of 100 milliseconds.
The `expected` argument is a pattern.
@@ -460,8 +450,6 @@ defmodule ExUnit.Assertions do
end
@doc """
refute_receive message, timeout \\ 100, message \\ nil
Asserts `message` was not received (and won't be received) within
the `timeout` period.
+30 -13
View File
@@ -9,13 +9,17 @@ defmodule ExUnit.CaptureIO do
import ExUnit.CaptureIO
test :example do
assert capture_io(fn ->
IO.puts "a"
end) == "a\n"
test "checking the return value and the IO output" do
fun = fn ->
assert Enum.each(["some", "example"], &(IO.puts &1)) == :ok
end
assert capture_io(fun) == "some\nexample\n"
# Or use only: `capture_io(fun)` to silence the
# IO output (so only assert the return value)
end
end
"""
@doc """
@@ -91,15 +95,13 @@ defmodule ExUnit.CaptureIO do
prompt_config = Keyword.get(options, :capture_prompt, true)
input = Keyword.get(options, :input, "")
original_gl = :erlang.group_leader
original_gl = Process.group_leader()
{:ok, capture_gl} = StringIO.open(input, capture_prompt: prompt_config)
:erlang.group_leader(capture_gl, self)
try do
fun.()
StringIO.close(capture_gl) |> elem(1) |> elem(1)
Process.group_leader(self(), capture_gl)
do_capture_io(capture_gl, fun)
after
:erlang.group_leader(original_gl, self)
Process.group_leader(self(), original_gl)
end
end
@@ -119,8 +121,7 @@ defmodule ExUnit.CaptureIO do
Process.register(capture_io, device)
try do
fun.()
StringIO.close(capture_io) |> elem(1) |> elem(1)
do_capture_io(capture_io, fun)
after
try do
Process.unregister(device)
@@ -131,4 +132,20 @@ defmodule ExUnit.CaptureIO do
ExUnit.Server.remove_device(device)
end
end
defp do_capture_io(string_io, fun) do
try do
_ = fun.()
:ok
catch
kind, reason ->
stack = System.stacktrace()
_ = StringIO.close(string_io)
:erlang.raise(kind, reason, stack)
else
:ok ->
{:ok, output} = StringIO.close(string_io)
elem(output, 1)
end
end
end
+8 -6
View File
@@ -162,15 +162,17 @@ defmodule ExUnit.Case do
quote do
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
if unquote(async) do
ExUnit.Server.add_async_case(__MODULE__)
else
ExUnit.Server.add_sync_case(__MODULE__)
end
Enum.each [:ex_unit_tests, :tag, :moduletag],
&Module.register_attribute(__MODULE__, &1, accumulate: true)
if unquote(async) do
@moduletag async: true
ExUnit.Server.add_async_case(__MODULE__)
else
@moduletag async: false
ExUnit.Server.add_sync_case(__MODULE__)
end
@before_compile ExUnit.Case
use ExUnit.Callbacks
end
+7 -7
View File
@@ -9,23 +9,23 @@ defmodule ExUnit.Formatter do
The following events are possible:
* `{:suite_started, opts}` -
the suite has started with the specified options to the runner.
the suite has started with the specified options to the runner.
* `{:suite_finished, run_us, load_us}` -
the suite has finished. `run_us` and `load_us` are the run and load
times in microseconds respectively.
the suite has finished. `run_us` and `load_us` are the run and load
times in microseconds respectively.
* `{:case_started, test_case}` -
a test case has started. See `ExUnit.TestCase` for details.
a test case has started. See `ExUnit.TestCase` for details.
* `{:case_finished, test_case}` -
a test case has finished. See `ExUnit.TestCase` for details.
a test case has finished. See `ExUnit.TestCase` for details.
* `{:test_started, test_case}` -
a test case has started. See `ExUnit.Test` for details.
a test case has started. See `ExUnit.Test` for details.
* `{:test_finished, test_case}` -
a test case has finished. See `ExUnit.Test` for details.
a test case has finished. See `ExUnit.Test` for details.
"""
+7 -3
View File
@@ -47,7 +47,7 @@ defmodule ExUnit.Runner do
|> Keyword.put(:exclude, exclude)
|> Keyword.put(:include, include)
|> Keyword.put_new(:max_cases, :erlang.system_info(:schedulers_online))
|> Keyword.put_new(:seed, :erlang.now |> elem(2))
|> Keyword.put_new(:seed, :os.timestamp |> elem(2))
end
defp loop(config) do
@@ -159,7 +159,9 @@ defmodule ExUnit.Runner do
{test_case, pending} =
receive do
{^case_pid, :case_finished, test_case, tests} ->
Process.demonitor(case_ref, [:flush])
receive do
{:DOWN, ^case_ref, :process, ^case_pid, _} -> :ok
end
{test_case, tests}
{:DOWN, ^case_ref, :process, ^case_pid, error} ->
test_case = %{test_case | state: {:failed, {{:EXIT, case_pid}, error, []}}}
@@ -214,7 +216,9 @@ defmodule ExUnit.Runner do
test =
receive do
{^test_pid, :test_finished, test} ->
Process.demonitor(test_ref, [:flush])
receive do
{:DOWN, ^test_ref, :process, ^test_pid, _} -> :ok
end
test
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
+20 -6
View File
@@ -28,6 +28,25 @@ defmodule ExUnit.CaptureIOTest do
import ExUnit.CaptureIO
doctest ExUnit.CaptureIO, import: true
test "no leakage on failures" do
group_leader = Process.group_leader()
test = self()
assert_raise ArgumentError, fn ->
capture_io(fn ->
send(test, {:string_io, Process.group_leader()})
raise ArgumentError
end)
end
receive do
{:string_io, pid} ->
ref = Process.monitor(pid)
assert_receive {:DOWN, ^ref, _, _, _}
end
assert Process.group_leader() == group_leader
end
test "with no output" do
assert capture_io(fn ->
end) == ""
@@ -290,19 +309,14 @@ defmodule ExUnit.CaptureIOTest do
end
test "with assert inside" do
group_leader = :erlang.group_leader
try do
capture_io(fn ->
assert false
end)
rescue
error in [ExUnit.AssertionError] ->
"Expected truthy, got false" = error.message
assert error.message == "Expected truthy, got false"
end
# Ensure no leakage on failures
assert group_leader == :erlang.group_leader
end
test "capture :stderr by two processes" do
+1
View File
@@ -19,6 +19,7 @@ defmodule ExUnit.CaseTest do
assert context[:case] == __MODULE__
assert context[:test] == __ENV__.function |> elem(0)
assert context[:line] == line
assert context[:async] == true
assert context[:hello] == true
assert context[:world] == :good
end
+45 -78
View File
@@ -273,54 +273,35 @@ defmodule IEx do
"""
def configure(options) do
Enum.each options, fn {k, v} ->
Application.put_env(:iex, k, configure(k, v))
end
end
defp configure(k, v) when k in [:colors, :inspect] and is_list(v) do
Keyword.merge(Application.get_env(:iex, k), v)
end
defp configure(:history_size, v) when is_integer(v) do
v
end
defp configure(k, v) when k in [:default_prompt, :alive_prompt] and is_binary(v) do
v
end
defp configure(k, v) do
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect v}"
IEx.Config.configure(options)
end
@doc """
Returns IEx configuration.
"""
def configuration do
Application.get_all_env(:iex)
IEx.Config.configuration()
end
@doc """
Registers a function to be invoked after the IEx process is spawned.
"""
def after_spawn(fun) when is_function(fun) do
Application.put_env(:iex, :after_spawn, [fun|after_spawn])
IEx.Config.after_spawn(fun)
end
@doc """
Returns registered `after_spawn` callbacks.
"""
def after_spawn do
{:ok, list} = Application.fetch_env(:iex, :after_spawn)
list
IEx.Config.after_spawn()
end
@doc """
Returns `true` if IEx was started.
"""
def started? do
Application.get_env(:iex, :started, false)
IEx.Config.started?()
end
@doc """
@@ -329,37 +310,28 @@ defmodule IEx do
ANSI escapes in `string` are not processed in any way.
"""
def color(color, string) do
colors = Application.get_env(:iex, :colors)
if color_enabled?(colors[:enabled]) do
ansi = Keyword.get(colors, color, default_color(color))
IO.iodata_to_binary(IO.ANSI.format_fragment(ansi, true)) <> string <> IO.ANSI.reset
else
string
case IEx.Config.color(color) do
nil ->
string
ansi ->
IO.iodata_to_binary([IO.ANSI.format_fragment(ansi, true), string | IO.ANSI.reset])
end
end
defp color_enabled?(nil), do: IO.ANSI.enabled?
defp color_enabled?(bool) when is_boolean(bool), do: bool
@doc """
Gets the IEx width for printing.
Used by helpers and it has a maximum cap of 80 chars.
"""
def width do
case :io.columns() do
{:ok, width} -> min(width, 80)
{:error, _} -> 80
end
IEx.Config.width()
end
@doc """
Gets the options used for inspecting.
"""
def inspect_opts do
Application.get_env(:iex, :inspect) ++
[width: width(), pretty: true]
IEx.Config.inspect_opts()
end
@doc """
@@ -419,27 +391,43 @@ defmodule IEx do
Setting variables or importing modules in IEx does not
affect the caller the environment (hence it is called `pry`).
"""
defmacro pry(timeout \\ 1000) do
defmacro pry(timeout \\ 5000) do
quote do
env = __ENV__
meta = "#{inspect self} at #{Path.relative_to_cwd(env.file)}:#{env.line}"
opts = [binding: binding, dot_iex_path: "", env: env, prefix: "pry"]
res = IEx.Server.take_over("Request to pry #{meta}", opts, unquote(timeout))
# We cannot use colors because IEx may be off.
case res do
{:error, :self} = err ->
IO.puts :stdio, "IEx cannot pry itself."
{:error, :no_iex} = err ->
IO.puts :stdio, "Cannot pry #{meta}. Is an IEx shell running?"
_ ->
:ok
end
res
IEx.pry(binding, __ENV__, unquote(timeout))
end
end
@doc """
Callback for `IEx.pry/1`.
You can invoke this function directly when you are not able to invoke
`IEx.pry/1` as a macro. This function expects the binding (from
`Kernel.binding/0`), the environment (from `__ENV__`) and the timeout
(a sensible default is 5000).
"""
def pry(binding, env, timeout) do
meta = "#{inspect self} at #{Path.relative_to_cwd(env.file)}:#{env.line}"
opts = [binding: binding, dot_iex_path: "", env: env, prefix: "pry"]
res = IEx.Server.take_over("Request to pry #{meta}", opts, timeout)
# We cannot use colors because IEx may be off.
case res do
{:error, :self} ->
IO.puts :stdio, "IEx cannot pry the shell itself."
{:error, :no_iex} ->
extra =
case :os.type do
{:win32, _} -> " If you are Windows, you may need to start IEx with the --werl flag."
_ -> ""
end
IO.puts :stdio, "Cannot pry #{meta}. Is an IEx shell running?" <> extra
_ ->
:ok
end
res
end
## Callbacks
# This is a callback invoked by Erlang shell utilities
@@ -467,7 +455,6 @@ defmodule IEx do
defp start_iex() do
unless started? do
{:ok, _} = Application.ensure_all_started(:iex)
Application.put_env(:iex, :started, true)
end
:ok
@@ -503,24 +490,4 @@ defmodule IEx do
_ = for fun <- Enum.reverse(after_spawn), do: fun.()
:ok
end
# Used by default on evaluation cycle
defp default_color(:eval_interrupt), do: [:yellow]
defp default_color(:eval_result), do: [:yellow]
defp default_color(:eval_error), do: [:red]
defp default_color(:eval_info), do: [:normal]
defp default_color(:stack_app), do: [:red, :bright]
defp default_color(:stack_info), do: [:red]
# Used by ls
defp default_color(:ls_directory), do: [:blue]
defp default_color(:ls_device), do: [:green]
# Used by ansi docs
defp default_color(:doc_bold), do: [:bright]
defp default_color(:doc_code), do: [:cyan, :bright]
defp default_color(:doc_headings), do: [:yellow, :bright]
defp default_color(:doc_inline_code), do: [:cyan]
defp default_color(:doc_underline), do: [:underline]
defp default_color(:doc_title), do: [:reverse, :yellow, :bright]
end
+25
View File
@@ -0,0 +1,25 @@
defmodule IEx.App do
@moduledoc false
use Application
def start(_type, _args) do
import Supervisor.Spec
children = [worker(IEx.Config, [])]
options = [strategy: :one_for_one, name: IEx.Supervisor]
tab = IEx.Config.new()
case Supervisor.start_link(children, options) do
{:ok, pid} ->
{:ok, pid, tab}
{:error, _} = error ->
IEx.Config.delete(tab)
error
end
end
def stop(tab) do
IEx.Config.delete(tab)
end
end
+70 -79
View File
@@ -10,12 +10,12 @@ defmodule IEx.Autocomplete do
h === ?. and t != []->
expand_dot(reduce(t))
h === ?: ->
expand_erlang_modules
expand_erlang_modules()
identifier?(h) ->
expand_expr(reduce(expr))
(h == ?/) and t != [] and identifier?(hd(t)) ->
expand_expr(reduce(t))
h in '(+[' ->
h in '([{' ->
expand('')
true ->
no()
@@ -40,32 +40,26 @@ defmodule IEx.Autocomplete do
defp expand_expr(expr) do
case Code.string_to_quoted expr do
{:ok, atom} when is_atom(atom) ->
expand_erlang_modules Atom.to_string(atom)
expand_erlang_modules(Atom.to_string(atom))
{:ok, {atom, _, nil}} when is_atom(atom) ->
expand_import Atom.to_string(atom)
expand_import(Atom.to_string(atom))
{:ok, {:__aliases__, _, [root]}} ->
expand_elixir_modules [], Atom.to_string(root)
expand_elixir_modules([], Atom.to_string(root))
{:ok, {:__aliases__, _, [h|_] = list}} when is_atom(h) ->
hint = Atom.to_string(List.last(list))
list = Enum.take(list, length(list) - 1)
expand_elixir_modules list, hint
expand_elixir_modules(list, hint)
{:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) ->
expand_call mod, Atom.to_string(fun)
expand_call(mod, Atom.to_string(fun))
_ ->
no()
end
end
defp reduce(expr) do
last_token(Enum.reverse(expr), [' ', '(', '[', '+', '-'])
end
defp last_token(s, []) do
s
end
defp last_token(s, [h|t]) do
last_token(List.last(:string.tokens(s, h)), t)
Enum.reverse Enum.reduce [' ', '(', '[', '{'], expr, fn token, acc ->
hd(:string.tokens(acc, token))
end
end
defp yes(hint, entries) do
@@ -93,42 +87,22 @@ defmodule IEx.Autocomplete do
length = byte_size(hint)
prefix = :binary.longest_common_prefix(binary)
if prefix in [0, length] do
entries = Enum.reduce(entries, [], fn e, acc -> to_entries(e) ++ acc end)
yes("", entries)
yes("", Enum.flat_map(entries, &to_entries/1))
else
yes(:binary.part(first.name, prefix, length-prefix), [])
end
end
## Root Modules
defp root_modules do
Enum.reduce :code.all_loaded, [], fn {m, _}, acc ->
mod = Atom.to_string(m)
case mod do
"Elixir" <> _ ->
tokens = String.split(mod, ".")
if length(tokens) == 2 do
[%{kind: :module, name: List.last(tokens), type: :elixir}|acc]
else
acc
end
_ ->
[%{kind: :module, name: mod, type: :erlang}|acc]
end
end
end
## Expand calls
# :atom.fun
defp expand_call(mod, hint) when is_atom(mod) do
expand_require mod, hint
expand_require(mod, hint)
end
# Elixir.fun
defp expand_call({:__aliases__, _, list}, hint) do
expand_require Module.concat(list), hint
expand_require(Module.concat(list), hint)
end
defp expand_call(_, _) do
@@ -136,13 +110,13 @@ defmodule IEx.Autocomplete do
end
defp expand_require(mod, hint) do
format_expansion module_funs(mod, hint), hint
format_expansion match_module_funs(mod, hint), hint
end
defp expand_import(hint) do
funs = module_funs(IEx.Helpers, hint) ++
module_funs(Kernel, hint) ++
module_funs(Kernel.SpecialForms, hint)
funs = match_module_funs(IEx.Helpers, hint) ++
match_module_funs(Kernel, hint) ++
match_module_funs(Kernel.SpecialForms, hint)
format_expansion funs, hint
end
@@ -152,55 +126,72 @@ defmodule IEx.Autocomplete do
format_expansion match_erlang_modules(hint), hint
end
defp match_erlang_modules("") do
Enum.filter root_modules, fn m -> m.type === :erlang end
end
defp match_erlang_modules(hint) do
Enum.filter root_modules, fn m -> String.starts_with?(m.name, hint) end
for mod <- match_modules(hint, true) do
%{kind: :module, name: mod, type: :erlang}
end
end
## Elixir modules
defp expand_elixir_modules(list, hint) do
mod = Module.concat(list)
format_expansion elixir_submodules(mod, hint, list == []) ++ module_funs(mod, hint), hint
format_expansion match_elixir_modules(mod, hint, list == []) ++
match_module_funs(mod, hint), hint
end
defp elixir_submodules(mod, hint, root) do
modname = Atom.to_string(mod)
depth = length(String.split(modname, ".")) + 1
base = modname <> "." <> hint
defp match_elixir_modules(module, hint, root) do
module = Atom.to_string(module)
depth = length(String.split(module, ".")) + 1
base = module <> "." <> hint
Enum.reduce modules_as_lists(root), [], fn(m, acc) ->
if String.starts_with?(m, base) do
tokens = String.split(m, ".")
if length(tokens) == depth do
name = List.last(tokens)
[%{kind: :module, type: :elixir, name: name}|acc]
else
acc
end
else
acc
end
for mod <- match_modules(base, root),
tokens = String.split(mod, "."),
length(tokens) == depth do
name = List.last(tokens)
%{kind: :module, type: :elixir, name: name}
end
end
defp modules_as_lists(true) do
["Elixir.Elixir"] ++ modules_as_lists(false)
end
defp modules_as_lists(false) do
Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
end
## Helpers
defp module_funs(mod, hint) do
defp match_modules(hint, root) do
get_modules(root)
|> :lists.usort()
|> Enum.drop_while(& not String.starts_with?(&1, hint))
|> Enum.take_while(& String.starts_with?(&1, hint))
end
defp get_modules(true) do
["Elixir.Elixir"] ++ get_modules(false)
end
defp get_modules(false) do
modules = Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
if :code.get_mode() === :interactive do
modules ++ get_modules_from_applications()
else
modules
end
end
defp get_modules_from_applications do
for [app] <- loaded_applications(),
{_, modules} = :application.get_key(app, :modules),
module <- modules do
Atom.to_string(module)
end
end
defp loaded_applications do
:ets.match(:ac_tab, {{:loaded, :"$1"}, :_})
end
defp match_module_funs(mod, hint) do
case ensure_loaded(mod) do
{:module, _} ->
falist = get_funs(mod)
falist = get_module_funs(mod)
list = Enum.reduce falist, [], fn {f, a}, acc ->
case :lists.keyfind(f, 1, acc) do
@@ -211,15 +202,15 @@ defmodule IEx.Autocomplete do
for {fun, arities} <- list,
name = Atom.to_string(fun),
hint == "" or String.starts_with?(name, hint) do
String.starts_with?(name, hint) do
%{kind: :function, name: name, arities: arities}
end
end |> :lists.sort()
_ ->
[]
end
end
defp get_funs(mod) do
defp get_module_funs(mod) do
if function_exported?(mod, :__info__, 1) do
if docs = Code.get_docs(mod, :docs) do
for {tuple, _line, _kind, _sign, doc} <- docs, doc != false, do: tuple
@@ -241,7 +232,7 @@ defmodule IEx.Autocomplete do
end
defp to_entries(%{kind: :function, name: name, arities: arities}) do
for a <- arities, do: "#{name}/#{a}"
for a <- :lists.sort(arities), do: "#{name}/#{a}"
end
defp to_uniq_entries(%{kind: :module}) do
+5 -8
View File
@@ -53,12 +53,9 @@ defmodule IEx.CLI do
if tty_works? do
:user_drv.start([:"tty_sl -c -e", tty_args])
else
:application.set_env(:stdlib, :shell_prompt_func,
:application.set_env(:stdlib, :shell_prompt_func,
{__MODULE__, :prompt})
:user.start()
unless match? {:win32, _}, :os.type do
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
end
local_start()
end
end
@@ -99,7 +96,7 @@ defmodule IEx.CLI do
end
def local_start do
IEx.start(config(), {:elixir, :start_cli, []})
IEx.start(options(), {:elixir, :start_cli, []})
end
def remote_start(parent, ref) do
@@ -113,7 +110,7 @@ defmodule IEx.CLI do
defp remote_start_function do
ref = make_ref
config = config()
opts = options()
parent = spawn_link fn ->
receive do
@@ -124,11 +121,11 @@ defmodule IEx.CLI do
end
fn ->
IEx.start(config, {__MODULE__, :remote_start, [parent, ref]})
IEx.start(opts, {__MODULE__, :remote_start, [parent, ref]})
end
end
defp config do
defp options do
[dot_iex_path: find_dot_iex(:init.get_plain_arguments)]
end
+184
View File
@@ -0,0 +1,184 @@
defmodule IEx.Config do
@moduledoc false
@table __MODULE__
@agent __MODULE__
@keys [:colors, :inspect, :history_size, :default_prompt, :alive_prompt]
@colors [:eval_interrupt, :eval_result, :eval_error, :eval_info, :stack_app,
:stack_info, :ls_directory, :ls_device]
def new() do
tab = :ets.new(@table, [:named_table, :public])
true = :ets.insert_new(tab, [after_spawn: []])
tab
end
def delete(__MODULE__) do
:ets.delete(__MODULE__)
end
def configure(options) do
Agent.update(@agent, __MODULE__, :handle_configure, [options])
end
def handle_configure(tab, options) do
options = :lists.ukeysort(1, options)
get_config()
|> Keyword.merge(options, &merge_option/3)
|> put_config()
tab
end
defp get_config() do
Application.get_all_env(:iex)
|> Keyword.take(@keys)
end
defp put_config(config) do
put = fn({key, value}) when key in @keys ->
Application.put_env(:iex, key, value)
end
Enum.each(config, put)
end
defp merge_option(:colors, old, new) when is_list(new), do: Keyword.merge(old, new)
defp merge_option(:inspect, old, new) when is_list(new), do: Keyword.merge(old, new)
defp merge_option(:history_size, _old, new) when is_integer(new), do: new
defp merge_option(:default_prompt, _old, new) when is_binary(new), do: new
defp merge_option(:alive_prompt, _old, new) when is_binary(new), do: new
defp merge_option(k, _old, new) do
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect new}"
end
def configuration() do
Keyword.merge(default_config(), get_config(), &merge_option/3)
end
defp default_config() do
Enum.map(@keys, &{&1, default_option(&1)})
end
defp default_option(:colors), do: [{:enabled, IO.ANSI.enabled?} | default_colors()]
defp default_option(:inspect), do: []
defp default_option(:history_size), do: 20
defp default_option(prompt) when prompt in [:default_prompt, :alive_prompt] do
"%prefix(%counter)>"
end
defp default_colors() do
Enum.map(@colors, &{&1, default_color(&1)}) ++ default_doc_colors()
end
defp default_doc_colors() do
Keyword.drop(IO.ANSI.Docs.default_options(), [:enabled, :width])
end
# Used by default on evaluation cycle
defp default_color(:eval_interrupt), do: [:yellow]
defp default_color(:eval_result), do: [:yellow]
defp default_color(:eval_error), do: [:red]
defp default_color(:eval_info), do: [:normal]
defp default_color(:stack_app), do: [:red, :bright]
defp default_color(:stack_info), do: [:red]
# Used by ls
defp default_color(:ls_directory), do: [:blue]
defp default_color(:ls_device), do: [:green]
# Used by ansi docs
defp default_color(doc_color) do
IO.ANSI.Docs.default_options()
|> Keyword.fetch!(doc_color)
end
def color(color) do
colors = Application.get_env(:iex, :colors, [])
if colors_enabled?(colors) do
case Keyword.fetch(colors, color) do
{:ok, value} ->
value
:error ->
default_color(color)
end
else
nil
end
end
defp colors_enabled?(colors \\ Application.get_env(:iex, :colors, [])) do
case Keyword.fetch(colors, :enabled) do
{:ok, enabled} ->
enabled
:error ->
IO.ANSI.enabled?()
end
end
def ansi_docs() do
colors = Application.get_env(:iex, :colors, [])
if enabled = colors_enabled?(colors) do
[width: width(), enabled: enabled] ++ colors
end
end
def inspect_opts() do
inspect_options = Application.get_env(:iex, :inspect, []) ++ [pretty: true]
cond do
Keyword.has_key?(inspect_options, :width) ->
inspect_options
colors_enabled? ->
[width: width()] ++ inspect_options
true ->
inspect_options
end
end
def width() do
case :io.columns() do
{:ok, width} -> min(width, 80)
{:error, _} -> 80
end
end
def after_spawn(fun) do
Agent.update(@agent, __MODULE__, :handle_after_spawn, [fun])
end
def handle_after_spawn(tab, fun) do
:ets.update_element(tab, :after_spawn, {2, [fun | after_spawn()]})
end
def after_spawn() do
:ets.lookup_element(@table, :after_spawn, 2)
end
def started?() do
Process.whereis(@agent) !== nil
end
def history_size(), do: get(:history_size)
def default_prompt(), do: get(:default_prompt)
def alive_prompt(), do: get(:alive_prompt)
defp get(key) do
case Application.fetch_env(:iex, key) do
{:ok, value} ->
value
:error ->
default_option(key)
end
end
def start_link() do
Agent.start_link(__MODULE__, :init, [@table], [name: @agent])
end
def init(tab) do
:public = :ets.info(tab, :protection)
tab
end
end
+34 -35
View File
@@ -25,8 +25,8 @@ defmodule IEx.Evaluator do
defp loop(server) do
receive do
{:eval, ^server, code, config} ->
send server, {:evaled, self, eval(code, config)}
{:eval, ^server, code, state} ->
send server, {:evaled, self, eval(code, state)}
loop(server)
{:done, ^server} ->
IEx.History.reset
@@ -36,9 +36,9 @@ defmodule IEx.Evaluator do
@doc """
Locates and loads an .iex.exs file from one of predefined locations.
Returns the new config.
Returns the new state.
"""
def load_dot_iex(config, path \\ nil) do
def load_dot_iex(state, path \\ nil) do
candidates = if path do
[path]
else
@@ -48,22 +48,22 @@ defmodule IEx.Evaluator do
path = Enum.find candidates, &File.regular?/1
if is_nil(path) do
config
state
else
eval_dot_iex(config, path)
eval_dot_iex(state, path)
end
end
defp eval_dot_iex(config, path) do
defp eval_dot_iex(state, path) do
try do
code = File.read!(path)
env = :elixir.env_for_eval(config.env, file: path, line: 1)
env = :elixir.env_for_eval(state.env, file: path, line: 1)
# Evaluate the contents in the same environment server_loop will run in
{_result, binding, env, _scope} =
:elixir.eval(String.to_char_list(code), config.binding, env)
:elixir.eval(String.to_char_list(code), state.binding, env)
%{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex", line: 1)}
%{state | binding: binding, env: :elixir.env_for_eval(env, file: "iex", line: 1)}
catch
kind, error ->
io_result "Error while evaluating: #{path}"
@@ -80,63 +80,62 @@ defmodule IEx.Evaluator do
# line in IEx). In case of any other error, we let :elixir_translator
# to re-raise it.
#
# Returns updated config.
# Returns updated state.
#
# The first two clauses provide support for the break-trigger allowing to
# break out from a pending incomplete expression. See
# https://github.com/elixir-lang/elixir/issues/1089 for discussion.
@break_trigger '#iex:break\n'
defp eval(code, config) do
defp eval(code, state) do
try do
do_eval(String.to_char_list(code), config)
do_eval(String.to_char_list(code), state)
catch
kind, error ->
print_error(kind, error, System.stacktrace)
%{config | cache: ''}
%{state | cache: ''}
end
end
defp do_eval(@break_trigger, config=%IEx.Config{cache: ''}) do
config
defp do_eval(@break_trigger, state=%IEx.State{cache: ''}) do
state
end
defp do_eval(@break_trigger, config) do
:elixir_errors.parse_error(config.counter, "iex", "incomplete expression", "")
defp do_eval(@break_trigger, state) do
:elixir_errors.parse_error(state.counter, "iex", "incomplete expression", "")
end
defp do_eval(latest_input, config) do
code = config.cache ++ latest_input
line = config.counter
handle_eval(Code.string_to_quoted(code, [line: line, file: "iex"]), code, line, config)
defp do_eval(latest_input, state) do
code = state.cache ++ latest_input
line = state.counter
handle_eval(Code.string_to_quoted(code, [line: line, file: "iex"]), code, line, state)
end
defp handle_eval({:ok, forms}, code, line, config) do
defp handle_eval({:ok, forms}, code, line, state) do
{result, new_binding, env, scope} =
:elixir.eval_forms(forms, config.binding, config.env, config.scope)
:elixir.eval_forms(forms, state.binding, state.env, state.scope)
unless result == IEx.dont_display_result, do: io_inspect(result)
update_history(line, code, result)
%{config | env: env,
cache: '',
scope: scope,
binding: new_binding,
counter: config.counter + 1}
%{state | env: env,
cache: '',
scope: scope,
binding: new_binding,
counter: state.counter + 1}
end
defp handle_eval({:error, {_, _, ""}}, code, _line, config) do
# Update config.cache so that IEx continues to add new input to
defp handle_eval({:error, {_, _, ""}}, code, _line, state) do
# Update state.cache so that IEx continues to add new input to
# the unfinished expression in `code`
%{config | cache: code}
%{state | cache: code}
end
defp handle_eval({:error, {line, error, token}}, _code, _line, _config) do
defp handle_eval({:error, {line, error, token}}, _code, _line, _state) do
# Encountered malformed expression
:elixir_errors.parse_error(line, "iex", error, token)
end
defp update_history(counter, cache, result) do
IEx.History.append({counter, cache, result}, counter,
Application.get_env(:iex, :history_size))
IEx.History.append({counter, cache, result}, counter, IEx.Config.history_size)
end
defp io_inspect(result) do
+1 -7
View File
@@ -175,15 +175,9 @@ defmodule IEx.Introspection do
end
defp ansi_docs() do
opts = Application.get_env(:iex, :colors)
if color_enabled?(opts[:enabled]) do
[width: IEx.width] ++ opts
end
IEx.Config.ansi_docs()
end
defp color_enabled?(nil), do: IO.ANSI.enabled?
defp color_enabled?(bool) when is_boolean(bool), do: bool
@doc """
Print types in module.
"""
+20 -20
View File
@@ -1,4 +1,4 @@
defmodule IEx.Config do
defmodule IEx.State do
@moduledoc false
defstruct binding: nil, cache: '', counter: 1, prefix: "iex", scope: nil, env: nil
@type t :: %__MODULE__{}
@@ -115,7 +115,7 @@ defmodule IEx.Server do
self_leader = Process.group_leader
evaluator = opts[:evaluator] || spawn(fn -> IEx.Evaluator.start(self_pid, self_leader) end)
Process.put(:evaluator, evaluator)
loop(run_config(opts), evaluator, Process.monitor(evaluator))
loop(run_state(opts), evaluator, Process.monitor(evaluator))
end
defp reset_loop(opts, evaluator, evaluator_ref) do
@@ -136,23 +136,23 @@ defmodule IEx.Server do
:ok
end
defp loop(config, evaluator, evaluator_ref) do
defp loop(state, evaluator, evaluator_ref) do
self_pid = self()
counter = config.counter
prefix = if config.cache != [], do: "...", else: config.prefix
counter = state.counter
prefix = if state.cache != [], do: "...", else: state.prefix
input = spawn(fn -> io_get(self_pid, prefix, counter) end)
wait_input(config, evaluator, evaluator_ref, input)
wait_input(state, evaluator, evaluator_ref, input)
end
defp wait_input(config, evaluator, evaluator_ref, input) do
defp wait_input(state, evaluator, evaluator_ref, input) do
receive do
{:input, ^input, code} when is_binary(code) ->
send evaluator, {:eval, self, code, config}
send evaluator, {:eval, self, code, state}
wait_eval(evaluator, evaluator_ref)
{:input, ^input, {:error, :interrupted}} ->
io_error "** (EXIT) interrupted"
loop(%{config | cache: ''}, evaluator, evaluator_ref)
loop(%{state | cache: ''}, evaluator, evaluator_ref)
{:input, ^input, :eof} ->
exit_loop(evaluator, evaluator_ref)
{:input, ^input, {:error, :terminated}} ->
@@ -160,15 +160,15 @@ defmodule IEx.Server do
msg ->
handle_take_over(msg, evaluator, evaluator_ref, input, fn ->
wait_input(config, evaluator, evaluator_ref, input)
wait_input(state, evaluator, evaluator_ref, input)
end)
end
end
defp wait_eval(evaluator, evaluator_ref) do
receive do
{:evaled, ^evaluator, config} ->
loop(config, evaluator, evaluator_ref)
{:evaled, ^evaluator, state} ->
loop(state, evaluator, evaluator_ref)
msg ->
handle_take_over(msg, evaluator, evaluator_ref, nil,
fn -> wait_eval(evaluator, evaluator_ref) end)
@@ -231,9 +231,9 @@ defmodule IEx.Server do
IO.gets(:stdio, message) =~ ~r/^(Y(es)?)?$/i
end
## Config
## State
defp run_config(opts) do
defp run_state(opts) do
locals = Keyword.get(opts, :delegate_locals_to, IEx.Helpers)
env =
@@ -247,11 +247,11 @@ defmodule IEx.Server do
binding = Keyword.get(opts, :binding, [])
prefix = Keyword.get(opts, :prefix, "iex")
config = %IEx.Config{binding: binding, scope: scope, prefix: prefix, env: env}
state = %IEx.State{binding: binding, scope: scope, prefix: prefix, env: env}
case opts[:dot_iex_path] do
"" -> config
path -> IEx.Evaluator.load_dot_iex(config, path)
"" -> state
path -> IEx.Evaluator.load_dot_iex(state, path)
end
end
@@ -265,12 +265,12 @@ defmodule IEx.Server do
defp prompt(prefix, counter) do
{mode, prefix} =
if Node.alive? do
{:alive, prefix || remote_prefix}
{:alive_prompt, prefix || remote_prefix}
else
{:default, prefix || "iex"}
{:default_prompt, prefix || "iex"}
end
prompt = Application.get_env(:iex, :"#{mode}_prompt")
prompt = apply(IEx.Config, mode, [])
|> String.replace("%counter", to_string(counter))
|> String.replace("%prefix", to_string(prefix))
|> String.replace("%node", to_string(node))
+3 -2
View File
@@ -8,8 +8,9 @@ defmodule IEx.Mixfile do
end
def application do
[env: [
after_spawn: [],
[registered: [IEx.Supervisor, IEx.Config],
mod: {IEx.App, []},
env: [
colors: [],
inspect: [],
history_size: 20,
+38 -32
View File
@@ -7,40 +7,54 @@ defmodule IEx.AutocompleteTest do
IEx.Autocomplete.expand(Enum.reverse expr)
end
test :erlang_module_simple_completion do
assert expand(':z') == {:yes, 'lib.', []}
test :erlang_module_completion do
assert expand(':zl') == {:yes, 'ib.', []}
end
test :erlang_module_no_completion do
assert expand(':x') == {:no, '', []}
assert expand('x.Foo') == {:no, '', []}
end
test :erlang_module_common_prefix_completion do
assert expand(':us') == {:yes, 'er', []}
assert expand(':unknown') == {:no, '', []}
end
test :erlang_module_multiple_values_completion do
{:yes, '', list} = expand(':user')
assert length(list) > 1
assert 'user' in list
assert 'user_drv' in list
end
test :elixir_simple_completion do
test :erlang_root_completion do
{:yes, '', list} = expand(':')
assert is_list(list)
assert 'lists' in list
end
test :elixir_proxy do
{:yes, '', list} = expand('E')
assert 'Elixir' in list
end
test :elixir_completion do
assert expand('En') == {:yes, 'um', []}
assert expand('Enumera') == {:yes, 'ble.', []}
end
test :elixir_auto_completion_with_self do
test :elixir_completion_with_self do
assert expand('Enumerable') == {:yes, '.', []}
end
test :elixir_completion_on_modules_from_load_path do
assert expand('Str') == {:yes, [], ['Stream', 'String', 'StringIO']}
assert expand('Ma') == {:yes, '', ['Macro', 'Map', 'MatchError']}
assert expand('Dic') == {:yes, 't.', []}
assert expand('Ex') == {:yes, [], ['ExUnit', 'Exception']}
end
test :elixir_no_completion do
assert expand('.') == {:no, '', []}
assert expand('Xyz') == {:no, '', []}
assert expand('x.Foo') == {:no, '', []}
end
test :elixir_root_submodule_completion do
_ = [foo: 1][:foo]
assert expand('Elixir.Acce') == {:yes, 'ss.', []}
end
@@ -52,51 +66,43 @@ defmodule IEx.AutocompleteTest do
assert expand('IEx.Xyz') == {:no, '', []}
end
test :elixir_function_completion do
test :function_completion do
assert expand('System.ve') == {:yes, 'rsion', []}
assert expand(':ets.fun2') == {:yes, 'ms', []}
end
test :elixir_function_completion_with_arity do
test :function_completion_with_arity do
assert expand('String.printable?') == {:yes, '', ['printable?/1']}
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
end
test :elixir_macro_completion do
test :macro_completion do
{:yes, '', list} = expand('Kernel.is_')
assert is_list(list)
end
test :elixir_root_completion do
test :imports_completion do
{:yes, '', list} = expand('')
assert is_list(list)
assert 'h/1' in list
assert 'unquote/1' in list
assert 'pwd/0' in list
end
test :elixir_kernel_completion do
test :kernel_import_completion do
assert expand('defstru') == {:yes, 'ct', []}
assert expand('put_') == {:yes, '', ['put_elem/3', 'put_in/2', 'put_in/3']}
end
test :elixir_special_form_completion do
test :kernel_special_form_completion do
assert expand('unquote_spl') == {:yes, 'icing', []}
end
test :elixir_proxy do
{:yes, '', list} = expand('E')
assert 'Elixir' in list
end
test :elixir_erlang_module_root_completion do
{:yes, '', list} = expand(':')
assert is_list(list)
assert 'lists' in list
end
test :completion_inside_expression do
assert expand('1+En') == {:yes, 'um', []}
assert expand('1 En') == {:yes, 'um', []}
assert expand('Test(En') == {:yes, 'um', []}
assert expand('Test :z') == {:yes, 'lib.', []}
assert expand('[:z') == {:yes, 'lib.', []}
assert expand('Test :zl') == {:yes, 'ib.', []}
assert expand('[:zl') == {:yes, 'ib.', []}
assert expand('{:zl') == {:yes, 'ib.', []}
end
end
+7 -5
View File
@@ -1,5 +1,5 @@
Application.start(:iex)
Application.put_env(:iex, :colors, [enabled: false])
:ok = Application.start(:iex)
IEx.configure([colors: [enabled: false]])
ExUnit.start [trace: "--trace" in System.argv]
defmodule IEx.Case do
@@ -31,11 +31,13 @@ defmodule IEx.Case do
end
end
@iex_app_env [:default_prompt, :alive_prompt, :inspect, :colors, :history_size]
setup do
opts = IEx.configuration |>
Keyword.take([:default_prompt, :alive_prompt, :inspect, :colors, :history_size])
opts = Application.get_all_env(:iex)
|> Keyword.take(@iex_app_env)
on_exit fn ->
Enum.each opts, fn {k, v} -> Application.put_env(:iex, k, v) end
Enum.each @iex_app_env, fn k -> Application.delete_env(:iex, k) end
IEx.configure(opts)
end
:ok
end
+8 -2
View File
@@ -474,8 +474,8 @@ defmodule Logger do
defp macro_log(level, chardata, metadata, caller) do
min_level = Application.get_env(:logger, :compile_time_purge_level, :debug)
if compare_levels(level, min_level) != :lt do
%{module: module, function: function, line: line} = caller
caller = [module: module, function: function, line: line]
%{module: module, function: fun, line: line} = caller
caller = [module: module, function: form_fa(fun), line: line]
quote do
Logger.log(unquote(level), unquote(chardata), unquote(caller) ++ unquote(metadata))
end
@@ -491,6 +491,12 @@ defmodule Logger do
defp truncate(data, n),
do: Logger.Utils.truncate(to_string(data), n)
defp form_fa({name, arity}) do
Atom.to_string(name) <> "/" <> Integer.to_string(arity)
end
defp form_fa(nil), do: nil
defp notify(:sync, msg), do: GenEvent.sync_notify(Logger, msg)
defp notify(:async, msg), do: GenEvent.notify(Logger, msg)
end
+38 -28
View File
@@ -21,49 +21,59 @@ defmodule Logger.App do
{otp_reports?, sasl_reports?, threshold}, :link],
[id: Logger.ErrorHandler, function: :watcher])]
config = Logger.Config.new()
case Supervisor.start_link(children, options) do
{:ok, _} = ok ->
deleted = delete_error_logger_handler(otp_reports?, :error_logger_tty_h, [])
deleted = delete_error_logger_handler(sasl_reports?, :sasl_report_tty_h, deleted)
store_deleted_handlers(deleted)
ok
{:ok, sup} ->
handlers = [error_logger_tty_h: otp_reports?,
sasl_logger_tty_h: sasl_reports?]
delete_handlers(handlers)
{:ok, sup, config}
{:error, _} = error ->
Logger.Config.delete(config)
error
end
end
@doc false
def stop(_) do
Application.get_env(:logger, :deleted_handlers)
|> Enum.each(&:error_logger.add_report_handler/1)
def stop(config) do
Logger.Config.deleted_handlers()
|> add_handlers()
Logger.Config.delete(config)
end
# We need to do this in another process as the Application
# Controller is currently blocked shutting down this app.
spawn_link(fn -> Logger.Config.clear_data end)
:ok
@doc false
def config_change(_changed, _new, _removed) do
Logger.Config.configure([])
end
@doc """
Stops the application without sending messages to error logger.
"""
def stop() do
set = Application.get_env(:logger, :deleted_handlers)
Application.put_env(:logger, :deleted_handlers, HashSet.new)
_ = Application.stop(:logger)
Enum.each(set, &:error_logger.add_report_handler/1)
end
defp store_deleted_handlers(list) do
Application.put_env(:logger, :deleted_handlers, Enum.into(list, HashSet.new))
end
defp delete_error_logger_handler(should_delete?, handler, deleted) do
if should_delete? and
:error_logger.delete_report_handler(handler) != {:error, :module_not_found} do
[handler|deleted]
try do
Logger.Config.deleted_handlers([])
catch
:exit, {:noproc, _} ->
{:error, {:not_started, :logger}}
else
deleted
deleted_handlers ->
result = Application.stop(:logger)
add_handlers(deleted_handlers)
result
end
end
defp delete_handlers(handlers) do
deleted? = fn({handler, delete?}) ->
delete? && :error_logger.delete_report_handler(handler) != {:error, :module_not_found}
end
[] = Enum.filter_map(handlers, deleted?, fn({handler, _}) -> handler end)
|> Logger.Config.deleted_handlers()
:ok
end
defp add_handlers(handlers) do
Enum.each(handlers, &:error_logger.add_report_handler/1)
end
end
+39 -7
View File
@@ -4,7 +4,9 @@ defmodule Logger.Config do
use GenEvent
@name __MODULE__
@table __MODULE__
@data :__data__
@deleted_handlers :__deleted_handlers__
def start_link do
GenServer.start_link(__MODULE__, :ok, name: @name)
@@ -44,22 +46,46 @@ defmodule Logger.Config do
def translate_backend(other), do: other
def __data__() do
if data = Application.get_env(:logger, @data) do
data
try do
:ets.lookup_element(@table, @data, 2)
rescue
ArgumentError ->
raise "Cannot use Logger, the :logger application is not running"
else
raise "Cannot use Logger, the :logger application is not running"
nil ->
raise "Cannot use Logger, the :logger application is not running"
data ->
data
end
end
def clear_data() do
Application.delete_env(:logger, @data)
def deleted_handlers() do
try do
:ets.lookup_element(@table, @deleted_handlers, 2)
rescue
ArgumentError ->
[]
end
end
def deleted_handlers(handlers) do
GenEvent.call(Logger, @name, {:deleted_handlers, handlers})
end
def new() do
tab = :ets.new(@table, [:named_table, :public, {:read_concurrency, true}])
true = :ets.insert_new(@table, [{@data, nil}, {@deleted_handlers, []}])
tab
end
def delete(@table) do
:ets.delete(@table)
end
## Callbacks
def init(_) do
# Use previous data if available in case this handler crashed.
state = Application.get_env(:logger, @data) || compute_state(:async)
state = :ets.lookup_element(@table, @data, 2) || compute_state(:async)
{:ok, state}
end
@@ -111,6 +137,12 @@ defmodule Logger.Config do
{:ok, :ok, state}
end
def handle_call({:deleted_handlers, new}, state) do
old = deleted_handlers()
true = :ets.update_element(@table, @deleted_handlers, {2, new})
{:ok, old, state}
end
## Helpers
defp compute_mode(state) do
@@ -159,7 +191,7 @@ defmodule Logger.Config do
end
defp persist(state) do
Application.put_env(:logger, @data, state)
:ets.update_element(@table, @data, {2, state})
state
end
end
+16 -9
View File
@@ -53,6 +53,11 @@ defmodule Logger.Translator do
"** (exit) " <> Exception.format_exit(reason)
{:ok, msg}
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
msg = ["Process ", inspect(pid), " raised an exception\n" |
Exception.format(:error, reason, stack)]
{:ok, msg}
_ ->
:none
end
@@ -82,8 +87,8 @@ defmodule Logger.Translator do
defp translate_supervisor(min_level,
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:pid, pid}, {:name, name} | offender]])
when is_pid(pid) and context !== :shutdown do
offender: [{:pid, pid}, {name_or_id, name} | offender]])
when is_pid(pid) and context !== :shutdown and name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context), ?\n,
"Pid: ", inspect(pid), ?\n,
@@ -95,7 +100,7 @@ defmodule Logger.Translator do
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:pid, _pid},
{:name, name} | offender]]) do
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context), ?\n,
child_info(min_level, offender), ?\n,
@@ -117,7 +122,7 @@ defmodule Logger.Translator do
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:nb_children, n},
{:name, name} | offender]]) do
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
{:ok, ["Children ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context), ?\n,
"Number: ", inspect(n), ?\n,
@@ -134,7 +139,7 @@ defmodule Logger.Translator do
defp translate_progress(min_level,
[supervisor: sup,
started: [{:pid, pid}, {:name, name} | started]]) do
started: [{:pid, pid}, {name_or_id, name} | started]]) when name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), " started\n",
"Pid: ", inspect(pid), ?\n |
@@ -162,12 +167,12 @@ defmodule Logger.Translator do
defp sup_context(:shutdown_error), do: "shutdown abnormally"
defp child_info(min_level, [{:mfargs, {mod, fun, args}} | debug]) do
["Start Call: ", Exception.format_mfa(mod, fun, args) |
["Start Call: ", format_mfa(mod, fun, args) |
child_debug(min_level, debug)]
end
defp child_info(min_level, [{:mfa, {mod, fun, args}} | debug]) do
["Start Call: ", Exception.format_mfa(mod, fun, args) |
["Start Call: ", format_mfa(mod, fun, args) |
child_debug(min_level, debug)]
end
@@ -248,11 +253,11 @@ defmodule Logger.Translator do
end
defp crash_call(mod, fun, arity) when is_integer(arity) do
Exception.format_mfa(mod, fun, arity)
format_mfa(mod, fun, arity)
end
defp crash_call(mod, fun, args) do
Exception.format_mfa(mod, fun, length(args))
format_mfa(mod, fun, length(args))
end
defp crash_debug(:debug,
@@ -295,4 +300,6 @@ defmodule Logger.Translator do
crash_info(min_level, info, [prefix | prefix])]
end
defp format_mfa(mod, fun, :undefined), do: [inspect(mod), ?., to_string(fun) | "/?"]
defp format_mfa(mod, fun, args), do: Exception.format_mfa(mod, fun, args)
end
@@ -43,8 +43,19 @@ defmodule Logger.Backends.ConsoleTest do
Logger.metadata(user_id: 13)
assert capture_log(fn ->
Logger.debug("user_id=13 hello")
end) =~ "hello"
Logger.debug("hello")
end) =~ "user_id=13 hello"
end
test "metadata defaults" do
Logger.configure_backend(:console,
format: "$metadata", metadata: [:module, :function, :line])
%{module: mod, function: {name, arity}, line: line} = __ENV__
assert capture_log(fn ->
Logger.debug("hello")
end) =~ "module=#{mod} function=#{name}/#{arity} line=#{line + 3}"
end
test "can configure level" do
+10 -10
View File
@@ -39,17 +39,17 @@ defmodule Logger.ErrorHandlerTest do
assert error_log(:error_report, :special, :hello) == ""
end
test "formats error_logger warning message" do
# Warnings by default are logged as errors by Erlang
assert error_log(:warning_msg, "hello", []) =~ msg("[error] hello")
assert error_log(:warning_msg, "~p~n", [:hello]) =~ msg("[error] :hello")
end
if :erlang.system_info(:otp_release) >= '18' do
test "formats error_logger warning message" do
assert error_log(:warning_msg, "hello", []) =~ msg("[warn] hello")
assert error_log(:warning_msg, "~p~n", [:hello]) =~ msg("[warn] :hello")
end
test "formats error_logger warning report" do
# Warnings by default are logged as errors by Erlang
assert error_log(:warning_report, "hello") =~ msg("[error] \"hello\"")
assert error_log(:warning_report, :hello) =~ msg("[error] :hello")
assert error_log(:warning_report, :special, :hello) == ""
test "formats error_logger warning report" do
assert error_log(:warning_report, "hello") =~ msg("[warn] \"hello\"")
assert error_log(:warning_report, :hello) =~ msg("[warn] :hello")
assert error_log(:warning_report, :special, :hello) == ""
end
end
defp error_log(fun, format) do
@@ -154,6 +154,21 @@ defmodule Logger.TranslatorTest do
"""
end
test "translates Process crashes" do
if :erlang.system_info(:otp_release) >= '18' do
assert capture_log(:info, fn ->
{_, ref} = spawn_monitor(fn() -> raise "oops" end)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
# Even though the monitor has been received the emulator may not have
# sent the message to the error logger
:timer.sleep(200)
end) =~ ~r"""
\[error\] Process #PID<\d+\.\d+\.\d+>\ raised an exception
\*\* \(RuntimeError\) oops
"""
end
end
test "translates :proc_lib crashes with name" do
{:ok, pid} = Task.start_link(__MODULE__, :task,
[self(), fn() ->
@@ -476,6 +491,17 @@ defmodule Logger.TranslatorTest do
"""
end
test "handles :undefined MFA properly" do
children = [Supervisor.Spec.worker(GenServer, [], restart: :temporary)]
opts = [strategy: :simple_one_for_one]
{:ok, sup} = Supervisor.start_link(children, opts)
assert capture_log(:info, fn ->
{:ok, pid} = Supervisor.start_child(sup, [MyGenServer, []])
catch_exit(GenServer.call(pid, :error))
[] = Supervisor.which_children(sup)
end) =~ "Start Call: GenServer.start_link/?"
end
def task(parent, fun \\ (fn() -> raise "oops" end)) do
Process.unlink(parent)
receive do: (:go -> fun.())
+11
View File
@@ -234,4 +234,15 @@ defmodule LoggerTest do
wait_for_handler(Logger, Logger.Config)
wait_for_handler(:error_logger, Logger.ErrorHandler)
end
test "Logger.Config updates config on config_change/3" do
:ok = Logger.configure([level: :debug])
try do
Application.put_env(:logger, :level, :error)
assert Logger.App.config_change([level: :error], [], []) === :ok
assert Logger.level() === :error
after
Logger.configure([level: :debug])
end
end
end
+19 -9
View File
@@ -161,19 +161,29 @@ defmodule Mix do
def start(_type, []) do
import Supervisor.Spec
state = [shell: Mix.Shell.IO,
env: String.to_atom(System.get_env("MIX_ENV") || "dev"),
scm: [Mix.SCM.Git, Mix.SCM.Path]]
tab = Mix.State.new(state)
children = [
worker(Mix.State, [tab]),
worker(Mix.TasksServer, []),
worker(Mix.ProjectStack, [])
]
opts = [strategy: :one_for_one, name: Mix.Supervisor]
stat = Supervisor.start_link(children, opts)
if env = System.get_env("MIX_ENV") do
env(String.to_atom env)
case Supervisor.start_link(children, opts) do
{:ok, pid} ->
{:ok, pid, tab}
{:error, _} = error ->
Mix.State.delete(tab)
error
end
end
stat
@doc false
def stop(tab) do
Mix.State.delete(tab)
end
@doc """
@@ -181,7 +191,7 @@ defmodule Mix do
"""
def env do
# env is not available on bootstrapping, so set a :dev default
Application.get_env(:mix, :env, :dev)
Mix.State.get(:env, :dev)
end
@doc """
@@ -191,7 +201,7 @@ defmodule Mix do
configuration won't be reloaded.
"""
def env(env) when is_atom(env) do
Application.put_env(:mix, :env, env)
Mix.State.put(:env, env)
end
@doc """
@@ -218,14 +228,14 @@ defmodule Mix do
messages to the current process.
"""
def shell do
Application.get_env(:mix, :shell, Mix.Shell.IO)
Mix.State.get(:shell, Mix.Shell.IO)
end
@doc """
Sets the current shell.
"""
def shell(shell) do
Application.put_env(:mix, :shell, shell)
Mix.State.put(:shell, shell)
end
@doc """
+6 -1
View File
@@ -19,10 +19,10 @@ defmodule Mix.CLI do
end
defp proceed(args) do
_ = Mix.Tasks.Local.Hex.ensure_updated?()
load_dot_config()
args = load_mixfile(args)
{task, args} = get_task(args)
ensure_hex(task)
change_env(task)
run_task(task, args)
end
@@ -69,6 +69,11 @@ defmodule Mix.CLI do
end
end
defp ensure_hex("local.hex"),
do: :ok
defp ensure_hex(_task),
do: Mix.Tasks.Local.Hex.ensure_updated?()
defp change_env(task) do
if is_nil(System.get_env("MIX_ENV")) &&
(env = preferred_cli_env(task)) do
+25 -9
View File
@@ -14,8 +14,9 @@ defmodule Mix.Compilers.Elixir do
have changed at runtime.
"""
def compile(manifest, srcs, skip, exts, dest, force, on_start) do
all = Mix.Utils.extract_files(srcs -- skip, exts)
{all_entries, skip_entries} = read_manifest(manifest, dest, skip)
keep = srcs -- skip
all = Mix.Utils.extract_files(keep, exts)
{all_entries, skip_entries} = read_manifest(manifest, dest, keep)
removed =
for {_b, _m, source, _d, _f, _bin} <- all_entries, not(source in all), do: source
@@ -51,6 +52,19 @@ defmodule Mix.Compilers.Elixir do
removed != [] ->
write_manifest(manifest, entries ++ skip_entries)
:ok
File.exists?(Mix.Project.app_path) ->
# If we have an app path but a noop result, we rebuild
# the structure because priv/include needs to be recopied when
# symlink is not available. We do this in the Elixir compiler
# instead of the main compiler one because other compilers may
# return :ok, saying they did some work, but they may have not
# built a structure at all.
#
# This implies the Elixir compiler is somewhat required, even
# for Erlang projects, which is an ok assumption to make (we
# ship with both Elixir and Erlang compilers by default).
Mix.Project.build_structure()
:noop
true ->
:noop
end
@@ -85,6 +99,8 @@ defmodule Mix.Compilers.Elixir do
defp compile_manifest(manifest, entries, stale, dest, on_start) do
Mix.Project.build_structure
true = Code.prepend_path(dest)
on_start.()
cwd = File.cwd!
@@ -181,21 +197,21 @@ defmodule Mix.Compilers.Elixir do
# Reads the manifest returning the results as tuples.
# The beam files are read, removed and stored in memory.
defp read_manifest(manifest, dest, skip_paths) do
defp read_manifest(manifest, dest, keep_paths) do
initial = {[], []}
case File.read(manifest) do
{:ok, contents} ->
skip_paths = Enum.map(skip_paths, &(&1 <> "/"))
keep_paths = Enum.map(keep_paths, &(&1 <> "/"))
Enum.reduce String.split(contents, "\n"), initial, fn x, acc ->
read_manifest_entry(String.split(x, "\t"), acc, dest, skip_paths)
read_manifest_entry(String.split(x, "\t"), acc, dest, keep_paths)
end
{:error, _} ->
initial
end
end
defp read_manifest_entry([_beam, module, source|deps], {keep, skip}, dest, skip_paths) do
defp read_manifest_entry([_beam, module, source|deps], {keep, skip}, dest, keep_paths) do
{deps, files} =
case Enum.split_while(deps, &(&1 != "Elixir")) do
{deps, ["Elixir"|files]} -> {deps, files}
@@ -208,10 +224,10 @@ defmodule Mix.Compilers.Elixir do
entry = {Path.join(dest, module <> ".beam"), module, source,
deps, files, nil}
if String.starts_with?(source, skip_paths) do
{keep, [entry|skip]}
else
if String.starts_with?(source, keep_paths) do
{[entry|keep], skip}
else
{keep, [entry|skip]}
end
end

Some files were not shown because too many files have changed in this diff Show More