Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9033b217d9 | ||
|
|
1d9548fd28 | ||
|
|
9a6c195880 | ||
|
|
3eb938a0ba | ||
|
|
66f1e74e0c | ||
|
|
7c382b94af | ||
|
|
9297a9167e | ||
|
|
0410a1cec9 | ||
|
|
b545a1e491 | ||
|
|
c08b5b11c6 | ||
|
|
8939b39fc2 | ||
|
|
e12a0c561e | ||
|
|
61c3e82123 | ||
|
|
d43b41695d | ||
|
|
fdca8b2657 | ||
|
|
dec06d8c82 | ||
|
|
11c84cbd2e | ||
|
|
67501bcddc | ||
|
|
1f05e7ce73 | ||
|
|
02c279af12 | ||
|
|
2c2fb820ab | ||
|
|
1ad1d6d6b0 | ||
|
|
83d8161959 | ||
|
|
b87ea61713 | ||
|
|
c355ab6482 | ||
|
|
f0ed2d48ce | ||
|
|
d23a46fa9f | ||
|
|
6c0cbbd73e | ||
|
|
d58ef57e13 | ||
|
|
1a9493534e | ||
|
|
45fe1cb084 | ||
|
|
3f0e7c5613 | ||
|
|
49e76833d6 | ||
|
|
7963e44a5e | ||
|
|
3ba5e44dae | ||
|
|
1d74ddac78 | ||
|
|
c1e3d4f976 | ||
|
|
b4fc2b35fc | ||
|
|
18a5d8affb | ||
|
|
cd80994a70 | ||
|
|
e02d7bf913 | ||
|
|
fe5ff41514 | ||
|
|
e9887a760e | ||
|
|
0559fe5927 | ||
|
|
1a05bff45d | ||
|
|
4d6670342b | ||
|
|
193e85882e | ||
|
|
a58c007e0c | ||
|
|
ce6f4e6d15 | ||
|
|
321096665a | ||
|
|
b93aae9f86 | ||
|
|
a503069479 | ||
|
|
120ea0fc8a | ||
|
|
d725c66d93 | ||
|
|
fab917dba3 | ||
|
|
23fb92f444 | ||
|
|
db61109495 | ||
|
|
c4e8ad0d65 | ||
|
|
ce77fa54b9 | ||
|
|
eee921bf7b | ||
|
|
73c6a0be8b | ||
|
|
7644bd87a5 | ||
|
|
9e2c5a29a4 | ||
|
|
09498422d0 | ||
|
|
a697bab7f2 | ||
|
|
36e81d587e | ||
|
|
17a0abd57a | ||
|
|
e6cfc20556 | ||
|
|
906f03102b | ||
|
|
6a594e2bba | ||
|
|
10dd8f843b | ||
|
|
53ecea8b03 | ||
|
|
efba8d8bfa | ||
|
|
5a201065dc | ||
|
|
307675284c | ||
|
|
38a9513111 | ||
|
|
578f798cc1 | ||
|
|
efbdf4b9b0 | ||
|
|
9679bed4d7 | ||
|
|
b6c0045fc2 | ||
|
|
10612ddf22 | ||
|
|
cbcbbcf876 | ||
|
|
95b81c2a13 | ||
|
|
a525f71e13 | ||
|
|
750d2a3f7a | ||
|
|
61e9fafb73 | ||
|
|
aa48f6a881 | ||
|
|
b5e2d45b7d | ||
|
|
29f2eb7dab | ||
|
|
a3754e3841 | ||
|
|
337c040da8 | ||
|
|
b4d465e2ed | ||
|
|
0bb3d1c6bd | ||
|
|
86476615ef | ||
|
|
860b3cc5a7 | ||
|
|
bb5e6f65f5 | ||
|
|
95127516f7 | ||
|
|
ef0c495f16 | ||
|
|
2472bb79ab | ||
|
|
04b9174d75 | ||
|
|
d31a58f0d9 | ||
|
|
f97ab2649c | ||
|
|
d969d42784 | ||
|
|
17c4c4d993 | ||
|
|
0f7a968389 | ||
|
|
272ec8ca47 | ||
|
|
a0e8e1c0fb | ||
|
|
b477dba34f | ||
|
|
8d524ef971 | ||
|
|
83cb5152fd | ||
|
|
3c100575a3 | ||
|
|
d1b6161f2e | ||
|
|
785781d089 | ||
|
|
b2ac4c9394 | ||
|
|
50ac792747 | ||
|
|
7d433e80e5 | ||
|
|
f9cb812bc1 | ||
|
|
f8711f2e36 | ||
|
|
0251f564d9 | ||
|
|
e4acc08cf5 | ||
|
|
2ab5d25787 | ||
|
|
acde8078f8 | ||
|
|
487ac9c1c4 | ||
|
|
f0f65f8bec | ||
|
|
23943b6180 | ||
|
|
d9125b0a81 | ||
|
|
d1d46db907 | ||
|
|
02056d1030 | ||
|
|
14a3686a3f | ||
|
|
c04b182c6c | ||
|
|
f5462d98e0 | ||
|
|
75d0c07b46 | ||
|
|
1de8dcb695 | ||
|
|
0e167bef27 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
/.eunit
|
||||
/.release
|
||||
/docs
|
||||
/doc
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
|
||||
+70
-1
@@ -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,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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]} ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() ->
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}}.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
LF
|
||||
CR
CRLF
|
||||
LFCR
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -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, []}}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.())
|
||||
|
||||
@@ -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
@@ -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 """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user