Compare commits

...
39 Commits
Author SHA1 Message Date
José Valim 737ac62926 Release v1.2.3 2016-02-21 22:28:30 +01:00
José Valim 9dcc0e140b Support canonical URLs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 21:42:01 +01:00
José Valim e4e5481ca1 Update CHANGELOG 2016-02-21 17:57:10 +01:00
José Valim c3df6806b3 Simplify padding rules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 17:56:14 +01:00
Paulo Almeida 6dc523074d Add option to omit / ignore padding
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 17:56:08 +01:00
Daniel Perez 5746cbf1e3 Add doc about ignore: :whitespace to Base.decode64.
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 17:56:05 +01:00
Artur Cygan 51966e92a7 Update base.ex
Clean up doc

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 17:56:01 +01:00
Daniel Perez 498057c51b Add option to ignore whitespace to base64 decoding. Close #4186.
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 17:55:02 +01:00
José Valim b3c26a8633 Improve helpers docs 2016-02-17 19:18:10 +01:00
José Valim 5e218e77bf Update CHANGELOG 2016-02-17 00:39:19 +01:00
José Valim 56fbf2a399 Add Mix.Project.deps_paths
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-17 00:34:59 +01:00
José Valim c3582f9617 Reject non fullfilled optional dependencies later on
Imagine this dependency tree:

    * parent
      * child
        * ecto
        * postgrex
      * ja_serializer
        * ecto

where postgrex is an optional dependency of Ecto.

Because optional dependencies were rejected later on,
ecto would have its postgrex dependency stripped and
later failed to top-sort. This commit changes it to
so optional dependencies are rejected only later on.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-17 00:34:52 +01:00
José Valim bd83a45215 Fix typespecs table
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-16 19:44:05 +01:00
José Valim 52dc944cfb Do not allow negative line numbers without generated annotation 2016-02-15 11:47:46 +01:00
José Valim 3d27c749e1 Remove outdated debug_info test 2016-02-01 09:45:23 +01:00
José Valim 75d679cdc8 Merge pull request #4247 from stephenmoloney/rstrip-bug
String.replace_trailing bug fix.
2016-02-01 09:22:05 +01:00
José Valim 2c9f28ecc3 Remove debug_info from unicode 2016-02-01 09:21:38 +01:00
José Valim 9decf4c78a Release v1.2.2 2016-01-31 10:17:08 +01:00
José Valim e2463a5589 Limit the list of attributes we consider reserved 2016-01-31 09:30:51 +01:00
José Valim a825f5a0c8 Force recompilation if dependency was recently fetched 2016-01-29 13:20:47 +01:00
José Valim 3c0d26cb74 Raise if trying to override reserved tag, closes #4236 2016-01-29 00:32:00 +01:00
José Valim f7a31ac804 Update CHANGELOG 2016-01-27 13:05:25 +01:00
José Valim 31aebcdc59 Automatically merge manager according to internal priority, closes #4230 2016-01-27 12:56:16 +01:00
José Valim afbac81918 Do not require all compilers available on manifest
Closes #4228

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-27 10:11:38 +01:00
José Valim 0b78f86f08 Do not rely on compiled functions in Mix for autoload
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 10:00:55 +01:00
José Valim 7180b98f47 Support disabling autoload after compilation
Useful for delaying loading of modules that may depend
on NIFs until necessary.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 00:13:37 +01:00
José Valim 021cca436d Do not include debug_info in metadata String modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 00:13:28 +01:00
José Valim a3d88dc406 Release v1.2.1 2016-01-14 19:25:11 +01:00
Daniel Azuma 58a6291f17 Fix a crash in Macro.to_string if a tree looks like a sigil but the function is not an atom
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-13 11:19:58 +01:00
Aleksei Magusev 81884247d7 Warn when defimp is called for consolidated protocol
Conflicts:
	lib/elixir/lib/protocol.ex
2016-01-12 10:50:29 +01:00
Adrien Moreau c07a34f9a8 Correct the type definition of ExUnit.state for the failed state
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-12 09:20:45 +01:00
José Valim e05bfc78cb Improve docs and error handling for Access
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-10 22:52:08 +01:00
Aleksei Magusev cb430702c9 Preserve variable metadata during collection from assertion pattern
Closes #4174.
2016-01-09 20:47:46 +01:00
Aleksei Magusev 6aaa4bb06d Do not warn in match assertion if variable is reused in pattern
Having the folowing assertion `assert {var, var} = {1, 1}`,
the code produced previously:

  [var] = case(right) do
    {var, var} ->
      #...
      [var]
    _ ->
      #...
  end

And with the patch:

  [var, var] = case(right) do
    {var, var} ->
      #...
      [var, var]
    _ ->
      #...
  end
2016-01-06 23:56:44 +01:00
Thomas Fisher a6b80a19c5 Fixes bug in IEx.Config.configuration
IEx.Config.configuration was raising a FunctionClauseError since
it did not have a default value for width

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:58:42 +01:00
Aleksei Magusev 8be0ad0499 Correct Macro.to_string/1 formatting for capture operator
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:58:07 +01:00
José Valim 8da4936ac3 Ensure dependencies are properly skipped when running in another environment
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:57:54 +01:00
James Fish bcc92ccc40 Support remote pids/ports with IEx helper i/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-02 18:54:07 +01:00
José Valim d64d4b009c v1.2 branch 2016-01-01 11:51:36 +01:00
41 changed files with 1048 additions and 303 deletions
+41
View File
@@ -85,6 +85,47 @@ ask Mix to use Rebar 3 to compile it by passing the `manager: :rebar3` option.
Once configured, Mix will prompt you to install Rebar 3 if it is not yet
available.
## v1.2.3 (2016-02-21)
### 1. Enhancements
* [Base] Add `:ignore` and `:padding` option to encoding/decoding functions
* [Mix] Add `Mix.Projects.deps_paths` that returns the dependencies path as a map
### 2. Bug fixes
* [ExUnit] Do not provide negative line numbers without generated annotation (for compatibility with Erlang 19)
* [Mix] Reject non fullfilled optional dependencies later on in the convergence resolution for proper dependency sorting
* [String] Fix incomplete data trimming on both `String.replace_trailing` and `String.rstrip`
* [String] Attach debug_info back into Unicode modules for Dialyzer support
## v1.2.2 (2016-01-31)
### 1. Enhancements
* [Kernel] Support `@compile {:autoload, false}` to disable automatic loading after compilation
### 2. Bug fixes
* [ExUnit] Raise if trying to override reserved tag in `setup` blocks
* [Mix] Ensure retrieve compile manifests do fail if some compilers are not yet available
* [Mix] Automatically merge managers according to the mix > rebar3 > rebar > make order
* [Mix] Force recompilation if dependency was recently fetched
## v1.2.1 (2016-01-14)
### 1. Enhancements
* [IEx] Support remote pids/ports with IEx helper `i/1`
* [Protocol] Warn when `defimpl` is called for a consolidated protocol
### 2. Bug fixes
* [ExUnit] Ensure `assert` macros can be used from quoted code
* [ExUnit] Do not warn in match assertion if variable is reused in pattern
* [Macro] Fix a bug in `Macro.to_string/1` where a remote function could be accidentally interpreted as a sigil
* [Mix] Ensure dependencies are properly skipped when `--only` option is given to `mix deps.get`
## v1.2.0 (2016-01-01)
### 1. Enhancements
+3 -2
View File
@@ -1,6 +1,7 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
DOCS := master
DOCS := v1.2
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
@@ -128,7 +129,7 @@ clean_exbeam:
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\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 "$(call SOURCE_REF)" $(call LOGO_PATH) -o doc/$(2) -p http://elixir-lang.org/docs.html $(4)
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 $(4)
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
+1 -3
View File
@@ -30,12 +30,10 @@ This document simply outlines the release process:
12. Create a new branch "vMAJOR.MINOR"
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable`
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable` (change index.html accordingly)
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
15. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
* VERSION (make sure there is no newline in this file)
+1 -1
View File
@@ -1 +1 @@
1.2.0
1.2.3
+83 -8
View File
@@ -1,15 +1,16 @@
defmodule Access do
@moduledoc """
Dictionary-like access to data structures via the `foo[bar]` syntax.
Key-based access to data structures via the `foo[bar]` syntax.
This module also empowers `Kernel`s nested update functions
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
`Kernel.get_and_update_in/3`.
Elixir provides two syntaxes for accessing values. `user[:name]`
is used by dynamic structures, like maps and keywords, while
`user.name` is used by structs. The main difference is that
`user[:name]` won't raise if the key `:name` is missing but
`user.name` will raise if there is no `:name` key.
## Examples
## Key-based lookups
Out of the box, Access works with built-in dictionaries: `Keyword`
and `Map`:
Out of the box, Access works with `Keyword` and `Map`:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -23,13 +24,67 @@ defmodule Access do
iex> star_ratings[1.5]
"★☆"
Access can be combined with `Kernel.put_in/3` to put a value
in a given key:
iex> map = %{a: 1, b: 2}
iex> put_in map[:a], 3
%{a: 3, b: 2}
This syntax is very convenient as it can be nested arbitrarily:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in users["john"][:age], 28
%{"john" => %{age: 28}, "meg" => %{age: 23}}
Furthermore, Access transparently ignores `nil` values:
iex> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil
The key comparison must be implemented using the `===` operator.
Since Access is a behaviour, it can be implemented to key-value
data structures. Access requires the key comparison to be
implemented using the `===` operator.
## Field-based lookups
The Access syntax (`foo[bar]`) cannot be used to access fields in
structs. That's by design, as Access is meant to be used for
dynamic key-value structures, like maps and keywords, and not
by static ones like structs.
However Elixir already provides a field-based lookup for structs.
Imagine a struct named `User` with name and age fields. The
following would raise:
user = %User{name: "john"}
user[:name]
** (UndefinedFunctionError) undefined function User.fetch/2
(User does not implement the Access behaviour)
Structs instead use the `user.name` syntax:
user.name
#=> "john"
The same `user.name` syntax can also be used by `Kernel.put_in/2`
to for updating structs fields:
put_in user.name, "mary"
%User{name: "mary"}
Differently from `user[:name]`, `user.name` cannot be extended by
the developers, and will be always restricted to only maps and
structs.
Summing up:
* `user[:name]` is used by dynamic structures, is extensible and
does not raise on missing keys
* `user.name` is used by static structures, it is not extensible
and it will raise on missing keys
"""
@type t :: list | map | nil
@@ -39,6 +94,20 @@ defmodule Access do
@callback fetch(t, key) :: {:ok, value} | :error
@callback get_and_update(t, key, (value -> {value, value})) :: {value, t}
defmacrop raise_undefined_behaviour(e, struct, top) do
quote do
stacktrace = System.stacktrace
e =
case stacktrace do
[unquote(top)|_] ->
%{unquote(e) | reason: "#{inspect unquote(struct)} does not implement the Access behaviour"}
_ ->
unquote(e)
end
reraise e, stacktrace
end
end
@doc """
Fetches the container's value for the given key.
"""
@@ -47,6 +116,9 @@ defmodule Access do
def fetch(%{__struct__: struct} = container, key) do
struct.fetch(container, key)
rescue
e in UndefinedFunctionError ->
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
end
def fetch(%{} = map, key) do
@@ -96,6 +168,9 @@ defmodule Access do
def get_and_update(%{__struct__: struct} = container, key, fun) do
struct.get_and_update(container, key, fun)
rescue
e in UndefinedFunctionError ->
raise_undefined_behaviour e, struct, {^struct, :get_and_update, [^container, ^key, ^fun], _}
end
def get_and_update(%{} = map, key, fun) do
+244 -127
View File
@@ -138,6 +138,15 @@ defmodule Base do
defp from_mixed(char),
do: char
defp maybe_pad(subject, false, _, _),
do: subject
defp maybe_pad(subject, _, group_size, pad) do
case rem(byte_size(subject), group_size) do
0 -> subject
x -> subject <> String.duplicate(pad, group_size - x)
end
end
@doc """
Encodes a binary string into a base 16 encoded string.
@@ -225,29 +234,56 @@ defmodule Base do
@doc """
Encodes a binary string into a base 64 encoded string.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.encode64("foobar")
"Zm9vYmFy"
iex> Base.encode64("foob")
"Zm9vYg=="
iex> Base.encode64("foob", padding: false)
"Zm9vYg"
"""
@spec encode64(binary) :: binary
def encode64(data) when is_binary(data) do
do_encode64(data)
@spec encode64(binary, Keyword.t) :: binary
def encode64(data, opts \\ []) when is_binary(data) do
pad_flag = Keyword.get(opts, :padding, true)
do_encode64(data, pad_flag)
end
@doc """
Decodes a base 64 encoded string into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.decode64("Zm9vYmFy")
{:ok, "foobar"}
iex> Base.decode64("Zm9vYmFy\\n", ignore: :whitespace)
{:ok, "foobar"}
iex> Base.decode64("Zm9vYg==")
{:ok, "foob"}
iex> Base.decode64("Zm9vYg", padding: false)
{:ok, "foob"}
"""
@spec decode64(binary) :: {:ok, binary} | :error
def decode64(string) when is_binary(string) do
{:ok, decode64!(string)}
@spec decode64(binary, Keyword.t) :: {:ok, binary} | :error
def decode64(string, opts \\ []) when is_binary(string) do
{:ok, decode64!(string, opts)}
rescue
ArgumentError -> :error
end
@@ -255,7 +291,11 @@ defmodule Base do
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -265,44 +305,72 @@ defmodule Base do
iex> Base.decode64!("Zm9vYmFy")
"foobar"
iex> Base.decode64!("Zm9vYmFy\\n", ignore: :whitespace)
"foobar"
iex> Base.decode64!("Zm9vYg==")
"foob"
iex> Base.decode64!("Zm9vYg", padding: false)
"foob"
"""
@spec decode64!(binary) :: binary
def decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
do_decode64(string)
end
def decode64!(string) when is_binary(string) do
raise ArgumentError, "incorrect padding"
@spec decode64!(binary, Keyword.t) :: binary
def decode64!(string, opts \\ []) when is_binary(string) do
pad_flag = Keyword.get(opts, :padding, true)
string |> filter_ignored(opts[:ignore]) |> do_decode64(pad_flag)
end
@doc """
Encodes a binary string into a base 64 encoded string with URL and filename
safe alphabet.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.url_encode64(<<255, 127, 254, 252>>)
"_3_-_A=="
iex> Base.url_encode64(<<255, 127, 254, 252>>, padding: false)
"_3_-_A"
"""
@spec url_encode64(binary) :: binary
def url_encode64(data) when is_binary(data) do
do_encode64url(data)
@spec url_encode64(binary, Keyword.t) :: binary
def url_encode64(data, opts \\ []) when is_binary(data) do
pad_flag = Keyword.get(opts, :padding, true)
do_encode64url(data, pad_flag)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.url_decode64("_3_-_A==")
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A==\\n", ignore: :whitespace)
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A", padding: false)
{:ok, <<255, 127, 254, 252>>}
"""
@spec url_decode64(binary) :: {:ok, binary} | :error
def url_decode64(string) when is_binary(string) do
{:ok, url_decode64!(string)}
@spec url_decode64(binary, Keyword.t) :: {:ok, binary} | :error
def url_decode64(string, opts \\ []) when is_binary(string) do
{:ok, url_decode64!(string, opts)}
rescue
ArgumentError -> :error
end
@@ -311,6 +379,12 @@ defmodule Base do
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -319,14 +393,18 @@ defmodule Base do
iex> Base.url_decode64!("_3_-_A==")
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A==\\n", ignore: :whitespace)
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A", padding: false)
<<255, 127, 254, 252>>
"""
@spec url_decode64!(binary) :: binary
def url_decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
do_decode64url(string)
end
def url_decode64!(string) when is_binary(string) do
raise ArgumentError, "incorrect padding"
@spec url_decode64!(binary, Keyword.t) :: binary
def url_decode64!(string, opts \\ []) when is_binary(string) do
pad_flag = Keyword.get(opts, :padding, true)
string |> filter_ignored(opts[:ignore]) |> do_decode64url(pad_flag)
end
@doc """
@@ -335,6 +413,9 @@ defmodule Base do
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.encode32("foobar")
@@ -343,12 +424,16 @@ defmodule Base do
iex> Base.encode32("foobar", case: :lower)
"mzxw6ytboi======"
iex> Base.encode32("foobar", padding: false)
"MZXW6YTBOI"
"""
@spec encode32(binary) :: binary
@spec encode32(binary, Keyword.t) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(case, data)
pad_flag = Keyword.get(opts, :padding, true)
do_encode32(case, data, pad_flag)
end
@doc """
@@ -358,6 +443,9 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.decode32("MZXW6YTBOI======")
@@ -369,6 +457,9 @@ defmodule Base do
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
{:ok, "foobar"}
iex> Base.decode32("MZXW6YTBOI", padding: false)
{:ok, "foobar"}
"""
@spec decode32(binary) :: {:ok, binary} | :error
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
@@ -385,6 +476,9 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -399,18 +493,16 @@ defmodule Base do
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
"foobar"
iex> Base.decode32!("MZXW6YTBOI", padding: false)
"foobar"
"""
@spec decode32!(binary) :: binary
@spec decode32!(binary, Keyword.t) :: binary
def decode32!(string, opts \\ [])
def decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
def decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode32(case, string)
end
def decode32!(string, _opts) when is_binary(string) do
raise ArgumentError, "incorrect padding"
pad_flag = Keyword.get(opts, :padding, true)
do_decode32(case, string, pad_flag)
end
@doc """
@@ -420,6 +512,9 @@ defmodule Base do
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.hex_encode32("foobar")
@@ -428,12 +523,16 @@ defmodule Base do
iex> Base.hex_encode32("foobar", case: :lower)
"cpnmuoj1e8======"
iex> Base.hex_encode32("foobar", padding: false)
"CPNMUOJ1E8"
"""
@spec hex_encode32(binary) :: binary
@spec hex_encode32(binary, Keyword.t) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_hex_encode32(case, data)
pad_flag = Keyword.get(opts, :padding, true)
do_hex_encode32(case, data, pad_flag)
end
@doc """
@@ -444,6 +543,9 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
@@ -455,6 +557,9 @@ defmodule Base do
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
{:ok, "foobar"}
iex> Base.hex_decode32("CPNMUOJ1E8", padding: false)
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
@@ -472,6 +577,9 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -486,18 +594,21 @@ defmodule Base do
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
"foobar"
iex> Base.hex_decode32!("CPNMUOJ1E8", padding: false)
"foobar"
"""
@spec hex_decode32!(binary) :: binary
@spec hex_decode32!(binary, Keyword.t) :: binary
def hex_decode32!(string, opts \\ [])
def hex_decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_hex_decode32(case, string)
pad_flag = Keyword.get(opts, :padding, true)
do_hex_decode32(case, string, pad_flag)
end
def hex_decode32!(string, _opts) when is_binary(string) do
raise ArgumentError, "incorrect padding"
defp filter_ignored(string, nil), do: string
defp filter_ignored(string, :whitespace) do
for <<c::8 <- string>>, not c in '\s\t\r\n', into: <<>>, do: <<c::8>>
end
defp do_encode16(_, <<>>), do: <<>>
@@ -525,213 +636,219 @@ defmodule Base do
end
end
defp do_encode64(<<>>), do: <<>>
defp do_encode64(data) do
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, pad_flag) do
split = 3 * div(byte_size(data), 3)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64(c)::8>>
case rest do
tail = case rest do
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc64(c1)::8, enc64(c2)::8, enc64(bsl(c3, 2))::8, ?=>>
<<enc64(c1)::8, enc64(c2)::8, enc64(bsl(c3, 2))::8>>
<<c1::6, c2::2>> ->
<<main::binary, enc64(c1)::8, enc64(bsl(c2, 4))::8, ?=, ?=>>
<<enc64(c1)::8, enc64(bsl(c2, 4))::8>>
<<>> ->
main
<<>>
end
main <> maybe_pad(tail, pad_flag, 4, "=")
end
defp do_decode64(<<>>), do: <<>>
defp do_decode64(string) do
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, false) do
maybe_pad(string, true, 4, "=") |> do_decode64(true)
end
defp do_decode64(string, _pad_flag) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64(c)::6>>
case rest do
tail = case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec64(c1)::6, bsr(dec64(c2), 4)::2>>
<<dec64(c1)::6, bsr(dec64(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec64(c1)::6, dec64(c2)::6, bsr(dec64(c3), 2)::4>>
<<dec64(c1)::6, dec64(c2)::6, bsr(dec64(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec64(c1)::6, dec64(c2)::6, dec64(c3)::6, dec64(c4)::6>>
<<dec64(c1)::6, dec64(c2)::6, dec64(c3)::6, dec64(c4)::6>>
<<>> ->
main
<<>>
end
main <> tail
end
defp do_decode64(_, _) do
raise ArgumentError, "incorrect padding"
end
defp do_encode64url(<<>>), do: <<>>
defp do_encode64url(data) do
defp do_encode64url(<<>>, _), do: <<>>
defp do_encode64url(data, pad_flag) do
split = 3 * div(byte_size(data), 3)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64url(c)::8>>
case rest do
tail = case rest do
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc64url(c1)::8, enc64url(c2)::8, enc64url(bsl(c3, 2))::8, ?=>>
<<enc64url(c1)::8, enc64url(c2)::8, enc64url(bsl(c3, 2))::8>>
<<c1::6, c2::2>> ->
<<main::binary, enc64url(c1)::8, enc64url(bsl(c2, 4))::8, ?=, ?=>>
<<enc64url(c1)::8, enc64url(bsl(c2, 4))::8>>
<<>> ->
main
<<>>
end
main <> maybe_pad(tail, pad_flag, 4, "=")
end
defp do_decode64url(<<>>), do: <<>>
defp do_decode64url(string) do
defp do_decode64url(<<>>, _), do: <<>>
defp do_decode64url(string, false) do
maybe_pad(string, true, 4, "=") |> do_decode64url(true)
end
defp do_decode64url(string, _pad_flag) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64url(c)::6>>
case rest do
tail = case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec64url(c1)::6, bsr(dec64url(c2), 4)::2>>
<<dec64url(c1)::6, bsr(dec64url(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, bsr(dec64url(c3), 2)::4>>
<<dec64url(c1)::6, dec64url(c2)::6, bsr(dec64url(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, dec64url(c3)::6, dec64url(c4)::6>>
<<dec64url(c1)::6, dec64url(c2)::6, dec64url(c3)::6, dec64url(c4)::6>>
<<>> ->
main
<<>>
end
main <> tail
end
defp do_decode64url(_, _) do
raise ArgumentError, "incorrect padding"
end
defp do_encode32(_, <<>>), do: <<>>
defp do_encode32(_, <<>>, _), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_encode32(unquote(case), data) do
defp do_encode32(unquote(case), data, pad_flag) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32(c))::8>>
case rest do
tail = case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
<<unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
unquote(fun)(enc32(c5))::8, unquote(fun)(enc32(c6))::8,
unquote(fun)(enc32(bsl(c7, 3)))::8, ?=>>
unquote(fun)(enc32(bsl(c7, 3)))::8>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
<<unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
unquote(fun)(enc32(bsl(c5, 1)))::8, ?=, ?=, ?=>>
unquote(fun)(enc32(bsl(c5, 1)))::8>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(bsl(c4, 4)))::8,
?=, ?=, ?=, ?=>>
<<unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(bsl(c4, 4)))::8>>
<<c1::5, c2::3>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(bsl(c2, 2)))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(bsl(c2, 2)))::8>>
<<>> ->
main
<<>>
end
main <> maybe_pad(tail, pad_flag, 8, "=")
end
end
defp do_decode32(_, <<>>), do: <<>>
defp do_decode32(_, <<>>, _), do: <<>>
defp do_decode32(case, string, false),
do: do_decode32(case, maybe_pad(string, true, 8, "="), true)
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_decode32(unquote(case), string) do
defp do_decode32(unquote(case), string, _pad_flag) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32(unquote(fun)(c))::5>>
case rest do
tail = case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec32(unquote(fun)(c1))::5,
bsr(dec32(unquote(fun)(c2)), 2)::3>>
<<dec32(unquote(fun)(c1))::5, bsr(dec32(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
<<dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, bsr(dec32(unquote(fun)(c4)), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
<<dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
bsr(dec32(unquote(fun)(c5)), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
<<dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
bsr(dec32(unquote(fun)(c7)), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
<<dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
dec32(unquote(fun)(c7))::5, dec32(unquote(fun)(c8))::5>>
<<>> ->
main
<<>>
end
main <> tail
end
end
defp do_hex_encode32(_, <<>>), do: <<>>
defp do_decode32(_, _, _),
do: raise ArgumentError, "incorrect padding"
defp do_hex_encode32(_, <<>>, _), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_hex_encode32(unquote(case), data) do
defp do_hex_encode32(unquote(case), data, pad_flag) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32hex(c))::8>>
case rest do
tail = case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
<<unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
unquote(fun)(enc32hex(c5))::8, unquote(fun)(enc32hex(c6))::8,
unquote(fun)(enc32hex(bsl(c7, 3)))::8, ?=>>
unquote(fun)(enc32hex(bsl(c7, 3)))::8>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
<<unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
unquote(fun)(enc32hex(bsl(c5, 1)))::8, ?=, ?=, ?=>>
unquote(fun)(enc32hex(bsl(c5, 1)))::8>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(bsl(c4, 4)))::8,
?=, ?=, ?=, ?=>>
<<unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(bsl(c4, 4)))::8>>
<<c1::5, c2::3>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(bsl(c2, 2)))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(bsl(c2, 2)))::8>>
<<>> ->
main
<<>>
end
main <> maybe_pad(tail, pad_flag, 8, "=")
end
end
defp do_hex_decode32(_, <<>>), do: <<>>
defp do_hex_decode32(_, <<>>, _), do: <<>>
defp do_hex_decode32(case, string, false),
do: do_hex_decode32(case, maybe_pad(string, true, 8, "="), true)
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_hex_decode32(unquote(case), string) do
defp do_hex_decode32(unquote(case), string, _pad_flag) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32hex(unquote(fun)(c))::5>>
case rest do
tail = case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec32hex(unquote(fun)(c1))::5,
bsr(dec32hex(unquote(fun)(c2)), 2)::3>>
<<dec32hex(unquote(fun)(c1))::5, bsr(dec32hex(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
<<dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, bsr(dec32hex(unquote(fun)(c4)), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
<<dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
bsr(dec32hex(unquote(fun)(c5)), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
<<dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
bsr(dec32hex(unquote(fun)(c7)), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
<<dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
dec32hex(unquote(fun)(c7))::5, dec32hex(unquote(fun)(c8))::5>>
<<>> ->
main
<<>>
end
main <> tail
end
end
defp do_hex_decode32(_, _, _),
do: raise ArgumentError, "incorrect padding"
end
+4
View File
@@ -640,6 +640,10 @@ defmodule UndefinedFunctionError do
"undefined function " <> Exception.format_mfa(module, function, arity) <>
" (function #{fa} is not available)"
end
def message(%{reason: reason, module: module, function: function, arity: arity}) do
"undefined function " <> Exception.format_mfa(module, function, arity) <> " (#{reason})"
end
end
defmodule FunctionClauseError do
+5 -1
View File
@@ -571,6 +571,10 @@ defmodule Macro do
fun.(ast, "&" <> to_string(mod, fun) <> "." <> Atom.to_string(name) <> "/" <> to_string(arity, fun))
end
def to_string({:&, _, [arg]} = ast, fun) when not is_integer(arg) do
fun.(ast, "&(" <> to_string(arg, fun) <> ")")
end
# Unary ops
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
@@ -691,7 +695,7 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_list(args) do
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_atom(func) and is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
+13
View File
@@ -540,6 +540,7 @@ defmodule Protocol do
"the dict[key] syntax, please implement the Dict behaviour instead"
else
Protocol.assert_protocol!(protocol)
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
end
defmodule name do
@@ -580,6 +581,7 @@ defmodule Protocol do
defp derive(protocol, for, struct, opts, env) do
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra)
__ensure_defimpl__(protocol, for, env)
assert_impl!(protocol, Any, extra)
# Clean up variables from eval context
@@ -608,6 +610,17 @@ defmodule Protocol do
end)
end
@doc false
def __ensure_defimpl__(protocol, for, env) do
if Protocol.consolidated?(protocol) do
message =
"the #{inspect protocol} protocol has already been consolidated" <>
", an implementation for #{inspect for} has no effect"
:elixir_errors.warn(env.line, env.file, message)
end
:ok
end
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
+3 -3
View File
@@ -8,10 +8,10 @@ defmodule String do
The functions in this module act according to the Unicode
Standard, version 6.3.0.
As per the standard, a codepoint is a single Unicode Character,
which may be represented by one or more bytes.
For example, the codepoint "é" is two bytes:
iex> byte_size("é")
@@ -661,7 +661,7 @@ defmodule String do
replace_trailing(string, match, replacement, prefix_size, suffix_size, "")
end
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc) when prefix_size > 0 do
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc) when prefix_size >= 0 do
case string do
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when suffix == match ->
replace_trailing(prefix, match, replacement, prefix_size - suffix_size, suffix_size, acc <> replacement)
+1 -1
View File
@@ -100,8 +100,8 @@ Built-in type | Defined as
`list()` | `[any()]`
`maybe_improper_list()` | `maybe_improper_list(any(), any())`
`nonempty_list()` | `nonempty_list(any())`
`iolist()` | `maybe_improper_list(byte() \| binary() \| iolist(), binary() \| [])`
`iodata()` | `iolist()` \| `binary()`
`iolist()` | `maybe_improper_list(byte()` \| `binary()` \| `iolist(), binary()` \| `[])`
`module()` | `atom()` \| `tuple()`
`arity()` | `0..255`
`mfa()` | `{atom(), atom(), arity()}`
+22 -13
View File
@@ -78,9 +78,8 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
ErlOpts = options() -- [native, warn_missing_spec],
inner_module(Form, ErlOpts, [{bootstrap, true}], E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -169,22 +168,32 @@ allows_fast_compilation(_) -> false.
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ options();
false -> options()
ErlOpts =
case proplists:get_value(debug_info, Opts) of
true -> [debug_info];
false -> [];
undefined ->
case get_opt(debug_info) of
true -> [debug_info];
false -> []
end
end,
inner_module(Forms, Final, false, E, Callback).
inner_module(Forms, ErlOpts ++ options(), Opts, E, Callback).
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
inner_module(Forms, ErlOpts, ExOpts, #{file := File} = E, Callback) when
is_list(Forms), is_list(ErlOpts), is_list(ExOpts), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
Autoload = proplists:get_value(autoload, ExOpts, true),
Bootstrap = proplists:get_value(bootstrap, ExOpts, false),
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|Options]) of
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|ErlOpts]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
{module, Module} =
case Autoload of
true -> code:load_binary(Module, beam_location(E), Binary);
false -> {module, Module}
end,
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
+1 -1
View File
@@ -331,7 +331,7 @@ spec_for_macro(Else) -> Else.
compile_opts(Module) ->
case ets:lookup(data_table(Module), compile) of
[{compile, Opts}] when is_list(Opts) -> Opts;
[{compile, Opts}] when is_list(Opts) -> lists:flatten(Opts);
[] -> []
end.
+16
View File
@@ -59,4 +59,20 @@ defmodule AccessTest do
assert Access.get_and_update(%{}, :foo, fn nil -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
assert Access.get_and_update(%{foo: :bar}, :foo, fn :bar -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
end
test "for struct" do
defmodule Sample do
defstruct [:name]
end
assert_raise UndefinedFunctionError,
"undefined function AccessTest.Sample.fetch/2 (AccessTest.Sample does not implement the Access behaviour)", fn ->
Access.fetch(struct(Sample, []), :name)
end
assert_raise UndefinedFunctionError,
"undefined function AccessTest.Sample.get_and_update/3 (AccessTest.Sample does not implement the Access behaviour)", fn ->
Access.get_and_update(struct(Sample, []), :name, fn nil -> {:ok, :baz} end)
end
end
end
+280
View File
@@ -93,6 +93,18 @@ defmodule BaseTest do
assert "MDEyMzQ1Njc4OSFAIzBeJiooKTs6PD4sLiBbXXt9" == encode64(<<"0123456789!@#0^&*();:<>,. []{}">>)
end
test "encode64 omit padding one pad" do
assert "SGVsbG8gV29ybGQ" == encode64("Hello World", padding: false)
end
test "encode64 omit padding two pads" do
assert "QWxhZGRpbjpvcGVuIHNlc2FtZQ" == encode64("Aladdin:open sesame", padding: false)
end
test "encode64 omit padding no pad" do
assert "QWxhZGRpbjpvcGVuIHNlc2Ft" == encode64("Aladdin:open sesam", padding: false)
end
test "decode64 empty" do
assert {:ok, ""} == decode64("")
end
@@ -135,6 +147,18 @@ defmodule BaseTest do
end
end
test "decode64 whitespace" do
assert :error == decode64("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
assert {:ok, "Aladdin:open sesam"} == decode64("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace)
end
test "decode64! whitespace" do
assert_raise ArgumentError, "non-alphabet digit found: \"\\n\" (byte 10)", fn ->
decode64!("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
end
assert "Aladdin:open sesam" == decode64!("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace)
end
test "decode64 incorrect padding" do
assert :error == decode64("SGVsbG8gV29ybGQ")
end
@@ -145,6 +169,38 @@ defmodule BaseTest do
end
end
test "decode64 ignore padding two pads" do
assert {:ok, "Aladdin:open sesame"} == decode64("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false)
end
test "decode64! ignore padding two pads" do
assert "Aladdin:open sesame" == decode64!("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false)
end
test "decode64 ignore padding one pad" do
assert {:ok, "Hello World"} == decode64("SGVsbG8gV29ybGQ", padding: false)
end
test "decode64! ignore padding one pad" do
assert "Hello World" == decode64!("SGVsbG8gV29ybGQ", padding: false)
end
test "decode64 ignore padding no pad" do
assert {:ok, "Aladdin:open sesam"} == decode64("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false)
end
test "decode64! ignore padding no pad" do
assert "Aladdin:open sesam" == decode64!("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false)
end
test "decode64 ignore padding incorrect padding" do
assert {:ok, "Hello World"} == decode64("SGVsbG8gV29ybGQ", padding: false)
end
test "decode64! ignore padding incorrect padding" do
assert "Hello World" == decode64!("SGVsbG8gV29ybGQ", padding: false)
end
test "url_encode64 empty" do
assert "" == url_encode64("")
end
@@ -162,6 +218,18 @@ defmodule BaseTest do
assert "MDEyMzQ1Njc4OSFAIzBeJiooKTs6PD4sLiBbXXt9" == url_encode64(<<"0123456789!@#0^&*();:<>,. []{}">>)
end
test "url_encode64 omit padding two pads" do
assert "QWxhZGRpbjpvcGVuIHNlc2FtZQ" == url_encode64("Aladdin:open sesame", padding: false)
end
test "url_encode64 omit padding one pad" do
assert "SGVsbG8gV29ybGQ" == url_encode64("Hello World", padding: false)
end
test "url_encode64 omit padding no pad" do
assert "QWxhZGRpbjpvcGVuIHNlc2Ft" == url_encode64("Aladdin:open sesam", padding: false)
end
test "url_encode64 no URL unsafe characters" do
refute "/3/+/A==" == url_encode64(<<255, 127, 254, 252>>)
assert "_3_-_A==" == url_encode64(<<255, 127, 254, 252>>)
@@ -199,6 +267,18 @@ defmodule BaseTest do
assert "Aladdin:open sesam" == url_decode64!("QWxhZGRpbjpvcGVuIHNlc2Ft")
end
test "url_decode64 whitespace" do
assert :error == url_decode64("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
assert {:ok, "Aladdin:open sesam"} == url_decode64("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace)
end
test "url_decode64! whitespace" do
assert_raise ArgumentError, "non-alphabet digit found: \"\\n\" (byte 10)", fn ->
url_decode64!("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
end
assert "Aladdin:open sesam" == url_decode64!("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace)
end
test "url_decode64 non-alphabet digit" do
assert :error == url_decode64("Zm9)")
end
@@ -219,6 +299,38 @@ defmodule BaseTest do
end
end
test "url_decode64 ignore padding two pads" do
assert {:ok, "Aladdin:open sesame"} == url_decode64("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false)
end
test "url_decode64! ignore padding two pads" do
assert "Aladdin:open sesame" == url_decode64!("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false)
end
test "url_decode64 ignore padding one pad" do
assert {:ok, "Hello World"} == url_decode64("SGVsbG8gV29ybGQ", padding: false)
end
test "url_decode64! ignore padding one pad" do
assert "Hello World" == url_decode64!("SGVsbG8gV29ybGQ", padding: false)
end
test "url_decode64 ignore padding no pad" do
assert {:ok, "Aladdin:open sesam"} == url_decode64("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false)
end
test "url_decode64! ignore padding no pad" do
assert "Aladdin:open sesam" == url_decode64!("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false)
end
test "url_decode64 ignore padding incorrect padding" do
assert {:ok, "Hello World"} == url_decode64("SGVsbG8gV29ybGQ", padding: false)
end
test "url_decode64! ignore padding incorrect padding" do
assert "Hello World" == url_decode64!("SGVsbG8gV29ybGQ", padding: false)
end
test "encode32 empty" do
assert "" == encode32("")
end
@@ -244,6 +356,26 @@ defmodule BaseTest do
assert "MZXW6YTB" == encode32("fooba")
end
test "encode32 omit padding one pad" do
assert "MZXW6YQ" == encode32("foob", padding: false)
end
test "encode32 omit padding three pads" do
assert "MZXW6" == encode32("foo", padding: false)
end
test "encode32 omit padding four pads" do
assert "MZXQ" == encode32("fo", padding: false)
end
test "encode32 omit padding six pads" do
assert "MZXW6YTBOI" == encode32("foobar", padding: false)
end
test "encode32 omit padding no pads" do
assert "MZXW6YTB" == encode32("fooba", padding: false)
end
test "encode32 lowercase" do
assert "mzxw6ytb" == encode32("fooba", case: :lower)
end
@@ -342,6 +474,70 @@ defmodule BaseTest do
end
end
test "decode32 ignore padding one pad" do
assert {:ok, "foob"} == decode32("MZXW6YQ", padding: false)
end
test "decode32! ignore padding one pad" do
assert "foob" == decode32!("MZXW6YQ", padding: false)
end
test "decode32 ignore padding three pads" do
assert {:ok, "foo"} == decode32("MZXW6", padding: false)
end
test "decode32! ignore padding three pads" do
assert "foo" == decode32!("MZXW6", padding: false)
end
test "decode32 ignore padding four pads" do
assert {:ok, "fo"} == decode32("MZXQ", padding: false)
end
test "decode32! ignore padding four pads" do
assert "fo" == decode32!("MZXQ", padding: false)
end
test "decode32 ignore padding lowercase" do
assert {:ok, "fo"} == decode32("mzxq", case: :lower, padding: false)
end
test "decode32! ignore padding lowercase" do
assert "fo" == decode32!("mzxq", case: :lower, padding: false)
end
test "decode32 ignore padding mixed case" do
assert {:ok, "fo"} == decode32("mZXq", case: :mixed, padding: false)
end
test "decode32! ignore padding mixed case" do
assert "fo" == decode32!("mZXq", case: :mixed, padding: false)
end
test "decode32 ignore padding six pads" do
assert {:ok, "foobar"} == decode32("MZXW6YTBOI", padding: false)
end
test "decode32! ignore padding six pads" do
assert "foobar" == decode32!("MZXW6YTBOI", padding: false)
end
test "decode32 ignore padding no pads" do
assert {:ok, "fooba"} == decode32("MZXW6YTB", padding: false)
end
test "decode32! ignore padding no pads" do
assert "fooba" == decode32!("MZXW6YTB", padding: false)
end
test "decode32 ignore padding incorrect padding" do
assert {:ok, "foob"} == decode32("MZXW6YQ", padding: false)
end
test "decode32! ignore padding incorrect padding" do
"foob" = decode32!("MZXW6YQ", padding: false)
end
test "hex_encode32 empty" do
assert "" == hex_encode32("")
end
@@ -367,6 +563,26 @@ defmodule BaseTest do
assert "CPNMUOJ1" == hex_encode32("fooba")
end
test "hex_encode32 omit padding one pad" do
assert "CPNMUOG" == hex_encode32("foob", padding: false)
end
test "hex_encode32 omit padding three pads" do
assert "CPNMU" == hex_encode32("foo", padding: false)
end
test "hex_encode32 omit padding four pads" do
assert "CPNG" == hex_encode32("fo", padding: false)
end
test "hex_encode32 omit padding six pads" do
assert "CPNMUOJ1E8" == hex_encode32("foobar", padding: false)
end
test "hex_encode32 omit padding no pads" do
assert "CPNMUOJ1" == hex_encode32("fooba", padding: false)
end
test "hex_encode32 lowercase" do
assert "cpnmuoj1" == hex_encode32("fooba", case: :lower)
end
@@ -470,4 +686,68 @@ defmodule BaseTest do
decode16!("012" <> <<0>>)
end
end
test "hex_decode32 ignore padding one pad" do
assert {:ok, "foob"} == hex_decode32("CPNMUOG", padding: false)
end
test "hex_decode32! ignore padding one pad" do
assert "foob" == hex_decode32!("CPNMUOG", padding: false)
end
test "hex_decode32 ignore padding three pads" do
assert {:ok, "foo"} == hex_decode32("CPNMU", padding: false)
end
test "hex_decode32! ignore padding three pads" do
assert "foo" == hex_decode32!("CPNMU", padding: false)
end
test "hex_decode32 ignore padding four pads" do
assert {:ok, "fo"} == hex_decode32("CPNG", padding: false)
end
test "hex_decode32! ignore padding four pads" do
assert "fo" == hex_decode32!("CPNG", padding: false)
end
test "hex_decode32 ignore padding six pads" do
assert {:ok, "foobar"} == hex_decode32("CPNMUOJ1E8", padding: false)
end
test "hex_decode32! ignore padding six pads" do
assert "foobar" == hex_decode32!("CPNMUOJ1E8", padding: false)
end
test "hex_decode32 ignore padding no pads" do
assert {:ok, "fooba"} == hex_decode32("CPNMUOJ1", padding: false)
end
test "hex_decode32! ignore padding no pads" do
assert "fooba" == hex_decode32!("CPNMUOJ1", padding: false)
end
test "hex_decode32 ignore padding incorrect padding" do
assert {:ok, "foob"} == hex_decode32("CPNMUOG", padding: false)
end
test "hex_decode32! ignore padding incorrect padding" do
"foob" = hex_decode32!("CPNMUOG", padding: false)
end
test "hex_decode32 ignoring padding lowercase" do
assert {:ok, "fo"} == hex_decode32("cpng", case: :lower, padding: false)
end
test "hex_decode32! ignore padding lowercase" do
assert "fo" == hex_decode32!("cpng", case: :lower, padding: false)
end
test "hex_decode32 ignore padding mixed case" do
assert {:ok, "fo"} == hex_decode32("cPNg====", case: :mixed, padding: false)
end
test "hex_decode32! ignore padding mixed case" do
assert "fo" == hex_decode32!("cPNg", case: :mixed, padding: false)
end
end
+3 -1
View File
@@ -290,6 +290,8 @@ defmodule MacroTest do
assert Macro.to_string(quote do: ~R"123") == ~s/~R"123"/
assert Macro.to_string(quote do: ~R"123"u) == ~s/~R"123"u/
assert Macro.to_string(quote do: ~R"\n123") == ~s/~R"\\\\n123"/
assert Macro.to_string(quote do: Foo.bar(<<>>, [])) == "Foo.bar(<<>>, [])"
end
test "arrow to string" do
@@ -387,6 +389,7 @@ defmodule MacroTest do
assert Macro.to_string(quote do: &foo/0) == "&foo/0"
assert Macro.to_string(quote do: &Foo.foo/0) == "&Foo.foo/0"
assert Macro.to_string(quote do: & &1 + &2) == "&(&1 + &2)"
assert Macro.to_string(quote do: & &1) == "&(&1)"
end
test "containers to string" do
@@ -422,7 +425,6 @@ defmodule MacroTest do
assert Macro.to_string(quote do: !(foo > bar)) == "!(foo > bar)"
assert Macro.to_string(quote do: @foo(bar)) == "@foo(bar)"
assert Macro.to_string(quote do: identity(&1)) == "identity(&1)"
assert Macro.to_string(quote do: identity(&foo)) == "identity(&foo)"
end
test "access to string" do
+7
View File
@@ -144,6 +144,13 @@ defmodule ModuleTest do
assert [3, 2, 1] = @other_attribute
end
test "@compile autoload attribute" do
defmodule NoAutoload do
@compile {:autoload, false}
end
refute :code.is_loaded(NoAutoload)
end
## Naming
test "concat" do
+11
View File
@@ -319,6 +319,17 @@ defmodule Protocol.ConsolidationTest do
refute Protocol.consolidated?(Enumerable)
end
test "consolidation prevents new implementations" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
defimpl WithAny, for: Integer do
def ok(_any), do: :ok
end
end) =~ ~r"warning: the .+WithAny protocol has already been consolidated"
after
:code.purge(WithAny.Atom)
:code.delete(WithAny.Atom)
end
test "consolidated implementations without any" do
assert is_nil Sample.impl_for(:foo)
assert is_nil Sample.impl_for(fn(x) -> x end)
+14
View File
@@ -160,6 +160,17 @@ defmodule StringTest do
assert String.capitalize("fin") == "Fin"
end
test "replace_trailing" do
assert String.replace_trailing(" abc aa", "a", "") == " abc "
assert String.replace_trailing(" abc __", "_", "") == " abc "
assert String.replace_trailing(" aaaaaaaaa", "a", "") == " "
assert String.replace_trailing("aaaaaaaaaa", "a", "") == ""
assert String.replace_trailing("]]]]]]]]]]", "]", "") == ""
assert String.replace_trailing(" cat 猫猫", "猫", "") == " cat "
assert String.replace_trailing("test", "t", "") == "tes"
assert String.replace_trailing("t", "t", "") == ""
end
test "rstrip" do
assert String.rstrip("") == ""
assert String.rstrip("1\n") == "1"
@@ -172,6 +183,9 @@ defmodule StringTest do
assert String.rstrip("a abc a" <> <<194, 133>>) == "a abc a"
assert String.rstrip(" abc aa", ?a) == " abc "
assert String.rstrip(" abc __", ?_) == " abc "
assert String.rstrip(" aaaaaaaaa", ?a) == " "
assert String.rstrip("aaaaaaaaaa", ?a) == ""
assert String.rstrip("]]]]]]]]]]", ?]) == ""
assert String.rstrip(" cat 猫猫", ?猫) == " cat "
end
+1 -1
View File
@@ -58,7 +58,7 @@ defmodule ExUnit do
@typedoc "The error state returned by ExUnit.Test and ExUnit.TestCase"
@type state :: nil | {:failed, failed} | {:skip, binary} | {:invalid, module}
@type failed :: {Exception.kind, reason :: term, stacktrace :: [tuple]}
@type failed :: [{Exception.kind, reason :: term, stacktrace :: [tuple]}]
defmodule Test do
@moduledoc """
+25 -22
View File
@@ -102,15 +102,16 @@ defmodule ExUnit.Assertions do
# is not nil nor false. We need to rewrite the if
# to avoid silly warnings though.
return =
no_warning(quote do
if right do
right
else
no_warning(quote do
case right do
x when x in [nil, false] ->
raise ExUnit.AssertionError,
expr: expr,
message: "Expected truthy, got #{inspect right}"
end
end)
_ ->
:ok
end
end)
quote do
right = unquote(right)
@@ -401,24 +402,26 @@ defmodule ExUnit.Assertions do
end
defp collect_vars_from_pattern(expr) do
{_, vars} =
Macro.prewalk(expr, [], fn
{:::, _, [left, _]}, acc ->
{[left], acc}
{skip, _, [_]}, acc when skip in [:^, :@] ->
{:ok, acc}
{:_, _, context}, acc when is_atom(context) ->
{:ok, acc}
{name, _, context}, acc when is_atom(name) and is_atom(context) ->
{:ok, [{name, [generated: true], context}|acc]}
node, acc ->
{node, acc}
end)
Enum.uniq(vars)
Macro.prewalk(expr, [], fn
{:::, _, [left, _]}, acc ->
{[left], acc}
{skip, _, [_]}, acc when skip in [:^, :@] ->
{:ok, acc}
{:_, _, context}, acc when is_atom(context) ->
{:ok, acc}
{name, meta, context}, acc when is_atom(name) and is_atom(context) ->
{:ok, [{name, [generated: true] ++ meta, context} | acc]}
node, acc ->
{node, acc}
end)
|> elem(1)
end
defp no_warning({name, meta, args}) do
{name, [line: -1] ++ meta, args}
defp no_warning({name, meta, [expr, [do: clauses]]}) do
clauses = Enum.map clauses, fn {:->, meta, args} ->
{:->, [generated: true] ++ Keyword.put(meta, :line, -1), args}
end
{name, meta, [expr, [do: clauses]]}
end
@doc """
+16 -5
View File
@@ -146,6 +146,8 @@ defmodule ExUnit.Callbacks do
## Helpers
@reserved ~w(case test line file)a
@doc false
def __merge__(_mod, context, :ok) do
{:ok, context}
@@ -163,12 +165,16 @@ defmodule ExUnit.Callbacks do
raise_merge_failed!(mod, data)
end
defp context_merge(_mod, context, %{} = data) do
Map.merge(context, data)
defp context_merge(mod, context, %{} = data) do
Map.merge(context, data, fn
_, v, v -> v
k, _, v when k in @reserved -> raise_merge_reserved!(mod, k, v)
_, _, v -> v
end)
end
defp context_merge(_mod, context, data) when is_list(data) do
Enum.into(data, context)
defp context_merge(mod, context, data) when is_list(data) do
context_merge(mod, context, Map.new(data))
end
defp context_merge(mod, _context, data) do
@@ -177,7 +183,12 @@ defmodule ExUnit.Callbacks do
defp raise_merge_failed!(mod, data) do
raise "expected ExUnit callback in #{inspect mod} to return :ok " <>
" or {:ok, keyword | map}, got #{inspect data} instead"
"or {:ok, keyword | map}, got #{inspect data} instead"
end
defp raise_merge_reserved!(mod, key, value) do
raise "expected ExUnit callback in #{inspect mod} is trying to set " <>
"reserved field #{inspect key} to #{inspect value}"
end
defp escape(contents) do
+19 -17
View File
@@ -114,10 +114,11 @@ defmodule ExUnit.Case do
The following tags are set automatically by ExUnit and are
therefore reserved:
* `:case` - the test case module
* `:test` - the test name
* `:line` - the line on which the test was defined
* `:file` - the file on which the test was defined
* `:case` - the test case module
* `:test` - the test name
* `:line` - the line on which the test was defined
* `:file` - the file on which the test was defined
* `:async` - if the test case is in async mode
The following tags customize how tests behaves:
@@ -174,7 +175,7 @@ defmodule ExUnit.Case do
This default can be overriden by `@tag capture_log: false` or `@moduletag capture_log: false`.
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
between tests) are never captured. If you want to suppress these messages as well, remove the
console backend globally:
@@ -193,20 +194,16 @@ defmodule ExUnit.Case do
end
quote do
async = !!unquote(async)
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
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
@moduletag async: async
@before_compile ExUnit.Case
@ex_unit_test_names %{}
@ex_unit_async async
use ExUnit.Callbacks
end
@@ -254,7 +251,7 @@ defmodule ExUnit.Case do
quote bind_quoted: binding do
test = :"test #{message}"
ExUnit.Case.__on_definition__(__ENV__, test)
ExUnit.Case.__on_definition__(__ENV__, test, [])
def unquote(test)(unquote(var)), do: unquote(contents)
end
end
@@ -284,6 +281,12 @@ defmodule ExUnit.Case do
@doc false
defmacro __before_compile__(_) do
quote do
if @ex_unit_async do
ExUnit.Server.add_async_case(__MODULE__)
else
ExUnit.Server.add_sync_case(__MODULE__)
end
def __ex_unit__(:case) do
%ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
end
@@ -291,8 +294,7 @@ defmodule ExUnit.Case do
end
@doc false
def __on_definition__(env, name, tags \\ []) do
mod = env.module
def __on_definition__(%{module: mod, file: file, line: line}, name, tags) do
moduletag = Module.get_attribute(mod, :moduletag)
unless moduletag do
@@ -304,7 +306,7 @@ defmodule ExUnit.Case do
(tags ++ Module.get_attribute(mod, :tag) ++ moduletag)
|> normalize_tags
|> validate_tags
|> Map.merge(%{line: env.line, file: env.file})
|> Map.merge(%{line: line, file: file})
test = %ExUnit.Test{name: name, case: mod, tags: tags}
test_names = Module.get_attribute(mod, :ex_unit_test_names)
@@ -11,6 +11,16 @@ alias ExUnit.AssertionsTest.Value
defmodule ExUnit.AssertionsTest do
use ExUnit.Case, async: true
defmacrop assert_ok(arg) do
quote do
assert {:ok, val} = ok(unquote(arg))
end
end
test "assert inside macro" do
assert_ok 42
end
test "assert with true value" do
true = assert Value.truthy
end
+53 -4
View File
@@ -152,7 +152,7 @@ defmodule ExUnitTest do
refute output =~ "[debug] four"
end
test "it supports multi errors" do
test "supports multi errors" do
capture_io :stderr, fn ->
defmodule MultiTest do
use ExUnit.Case
@@ -187,7 +187,7 @@ defmodule ExUnitTest do
assert output =~ "Failure #2"
end
test "it registers only the first test with any given name" do
test "registers only the first test with any given name" do
capture_io :stderr, fn ->
defmodule TestWithSameNames do
use ExUnit.Case
@@ -207,7 +207,7 @@ defmodule ExUnitTest do
end) =~ "1 test, 0 failure"
end
test "it produces error on not implemented tests" do
test "produces error on not implemented tests" do
defmodule TestNotImplemented do
use ExUnit.Case
@@ -227,7 +227,7 @@ defmodule ExUnitTest do
assert output =~ "1 test, 1 failure"
end
test "it skips tagged test with skip" do
test "skips tagged test with skip" do
defmodule TestSkipped do
use ExUnit.Case
@@ -250,6 +250,55 @@ defmodule ExUnitTest do
assert output =~ "2 tests, 0 failures, 2 skipped"
end
test "raises on reserved tag in module" do
assert_raise RuntimeError, "cannot set tag :file because it is reserved by ExUnit", fn ->
defmodule ReservedTag do
use ExUnit.Case
setup do
{:ok, file: :foo}
end
@tag file: "oops"
test "sample", do: :ok
end
end
end
test "raises on reserved tag in setup" do
defmodule ReservedSetupTag do
use ExUnit.Case
setup do
{:ok, file: :foo}
end
test "sample", do: :ok
end
output = capture_io(fn ->
assert ExUnit.run == %{failures: 1, skipped: 0, total: 1}
end)
assert output =~ "trying to set reserved field :file"
end
test "does not raise on reserved tag in setup_all (lower priority)" do
defmodule ReservedSetupAllTag do
use ExUnit.Case
setup_all do
{:ok, file: :foo}
end
test "sample", do: :ok
end
capture_io(fn ->
assert ExUnit.run == %{failures: 0, skipped: 0, total: 1}
end)
end
defp run_with_filter(filters, {async, sync, load_us}) do
opts = Keyword.merge(ExUnit.configuration, filters)
output = capture_io fn ->
+1
View File
@@ -63,6 +63,7 @@ defmodule IEx.Config do
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(:width), do: width()
defp default_option(prompt) when prompt in [:default_prompt, :alive_prompt] do
"%prefix(%counter)>"
+1 -2
View File
@@ -27,9 +27,9 @@ defmodule IEx.Helpers do
* `cd/1` - changes the current directory
* `clear/0` - clears the screen
* `flush/0` - flushes all messages sent to the shell
* `i/1` - prints information about the given data type
* `h/0` - prints this help message
* `h/1` - prints help for the given module, function or macro
* `i/1` - prints information about the given data type
* `import_file/1` - evaluates the given file in the shell's context
* `l/1` - loads the given module's beam code
* `ls/0` - lists the contents of the current directory
@@ -42,7 +42,6 @@ defmodule IEx.Helpers do
* `t/1` — prints type information
* `v/0` — retrieves the last value from the history
* `v/1` — retrieves the nth value from the history
* `import_file/1` — evaluates the given file in the shell's context
Help for all of those functions can be consulted directly from
the command line using the `h` helper itself. Try:
+11 -9
View File
@@ -227,13 +227,14 @@ defimpl IEx.Info, for: PID do
def info(pid) do
extra =
if info = Process.info(pid, @keys) do
["Alive": true,
"Name": process_name(info[:registered_name]),
"Links": links(info[:links]),
"Message queue length": info[:message_queue_len]]
else
["Alive": false]
case :rpc.pinfo(pid, @keys) do
[_|_] = info ->
["Alive": true,
"Name": process_name(info[:registered_name]),
"Links": links(info[:links]),
"Message queue length": info[:message_queue_len]]
_ ->
["Alive": false]
end
["Data type": "PID"] ++ extra ++
@@ -257,9 +258,10 @@ end
defimpl IEx.Info, for: Port do
def info(port) do
port_info = Port.info(port)
connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected])
["Data type": "Port",
"Open": port_info != nil,
"Open": match?({:connected, _}, connected),
"Reference modules": "Port"]
end
end
+9
View File
@@ -0,0 +1,9 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.ConfigTest do
use ExUnit.Case, async: true
test "configuration sets a default for width" do
assert IEx.Config.configuration[:width] == 80
end
end
+7 -10
View File
@@ -137,29 +137,26 @@ defmodule Mix.Compilers.Elixir do
|> List.delete(module)
|> Enum.reject(&match?("elixir_" <> _, Atom.to_string(&1)))
attributes = module.__info__(:attributes)
kind = detect_kind(attributes)
kind = detect_kind(module)
source = Path.relative_to(source, cwd)
files = get_external_resources(attributes, cwd)
files = get_external_resources(module, cwd)
tuple = {beam, module, kind, source, compile, runtime, files, binary}
Agent.cast pid, &:lists.keystore(beam, 1, &1, tuple)
end
defp detect_kind(attributes) do
defp detect_kind(module) do
cond do
impl = attributes[:impl] ->
impl = Module.get_attribute(module, :impl) ->
{:impl, impl[:protocol]}
attributes[:protocol] ->
Module.get_attribute(module, :protocol) ->
:protocol
true ->
:module
end
end
defp get_external_resources(attributes, cwd) do
for {:external_resource, values} <- attributes,
file <- values,
defp get_external_resources(module, cwd) do
for file <- Module.get_attribute(module, :external_resource),
File.regular?(file),
relative = Path.relative_to(file, cwd),
Path.type(relative) == :relative,
+59 -40
View File
@@ -49,16 +49,12 @@ defmodule Mix.Dep.Converger do
end
defp all(acc, lock, opts, callback) do
deps = Mix.Dep.Loader.children()
deps = Enum.map(deps, &(%{&1 | top_level: true}))
lock_given? = !!lock
main = Mix.Dep.Loader.children()
main = Enum.map(main, &(%{&1 | top_level: true}))
apps = Enum.map(main, &(&1.app))
# Filter the dependencies per environment. We pass the ones
# left out as accumulator and upper breadth to help catch
# inconsistencies across environment, specially regarding
# the :only option. They are filtered again later.
current = Enum.map(deps, &(&1.app))
{main, only} = Mix.Dep.Loader.partition_by_env(deps, opts)
lock_given? = !!lock
env = opts[:env]
# If no lock was given, let's read one to fill in the deps
lock = lock || Mix.Dep.Lock.read
@@ -68,7 +64,7 @@ defmodule Mix.Dep.Converger do
# lazily loaded, we need to check for it on every
# iteration.
{deps, rest, lock} =
all(main, only, [], current, callback, acc, lock, fn dep ->
all(main, apps, callback, acc, lock, env, fn dep ->
if (remote = Mix.RemoteConverger.get) && remote.remote?(dep) do
{:loaded, dep}
else
@@ -76,9 +72,6 @@ defmodule Mix.Dep.Converger do
end
end)
# Filter deps per environment once more. If the filtered
# dependencies had no conflicts, they are removed now.
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, opts)
diverged? = Enum.any?(deps, &Mix.Dep.diverged?/1)
# Run remote converger if one is available and rerun Mix's
@@ -99,19 +92,29 @@ defmodule Mix.Dep.Converger do
# In case no lock was given, we will use the local lock
# which is potentially stale. So remote.deps/2 needs to always
# check if the data it finds in the lock is actually valid.
all(main, [], [], Enum.map(main, &(&1.app)), callback, rest, lock, fn dep ->
cond do
cached = deps[dep.app] ->
{deps, rest, lock} =
all(main, apps, callback, rest, lock, env, fn dep ->
if cached = deps[dep.app] do
{:loaded, cached}
true ->
else
{:unloaded, dep, remote.deps(dep, lock)}
end
end)
end
end)
{reject_non_fullfilled_optional(deps), rest, lock}
else
{deps, rest, lock}
{reject_non_fullfilled_optional(deps), rest, lock}
end
end
defp all(main, apps, callback, rest, lock, env, cache) do
{deps, rest, lock} = all(main, [], [], apps, callback, rest, lock, env, cache)
# When traversing dependencies, we keep skipped ones to
# find conflicts. We remove them now after traversal.
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, env)
{deps, rest, lock}
end
# We traverse the tree of dependencies in a breadth-first
# fashion. The reason for this is that we converge
# dependencies, but allow the parent to override any
@@ -151,10 +154,14 @@ defmodule Mix.Dep.Converger do
# Now, since "d" was specified in a parent project, no
# exception is going to be raised since d is considered
# to be the authoritative source.
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, cache) do
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, env, cache) do
cond do
new_acc = diverged_deps(acc, upper_breadths, dep) ->
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, cache)
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, env, cache)
Mix.Dep.Loader.skip?(dep, env) ->
# We still keep skipped dependencies around to detect conflicts.
# They must be rejected after every all iteration.
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
true ->
dep =
case cache.(dep) do
@@ -169,13 +176,16 @@ defmodule Mix.Dep.Converger do
Mix.Dep.Loader.load(dep, children)
end
dep = %{dep | deps: reject_non_fullfilled_optional(dep.deps, current_breadths)}
{acc, rest, lock} = all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, cache)
all(dep.deps, acc, current_breadths, Enum.map(dep.deps, &(&1.app)) ++ current_breadths, callback, rest, lock, cache)
{acc, rest, lock} =
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
deps = reject_non_fullfilled_optional(dep.deps, current_breadths)
new_breadths = Enum.map(deps, &(&1.app)) ++ current_breadths
all(deps, acc, current_breadths, new_breadths, callback, rest, lock, env, cache)
end
end
defp all([], acc, _upper, _current, _callback, rest, lock, _cache) do
defp all([], acc, _upper, _current, _callback, rest, lock, _env, _cache) do
{acc, rest, lock}
end
@@ -206,7 +216,8 @@ defmodule Mix.Dep.Converger do
in_upper? && other_opts[:override] ->
{other |> with_matching_only(dep, in_upper?), true}
converge?(other, dep) ->
{other |> with_matching_only(dep, in_upper?) |> with_matching_req(dep) |> merge_manager(dep), true}
{other |> with_matching_only(dep, in_upper?)
|> with_matching_req(dep) |> merge_manager(dep, in_upper?), true}
true ->
tag = if in_upper?, do: :overridden, else: :diverged
{%{other | status: {tag, dep}}, true}
@@ -269,32 +280,40 @@ defmodule Mix.Dep.Converger do
end
end
defp converge?(%Mix.Dep{scm: scm1, manager: manager1, opts: opts1},
%Mix.Dep{scm: scm2, manager: manager2, opts: opts2}) do
scm1 == scm2 and
manager_equal?(manager1, manager2) and
opts_equal?(opts1, opts2) and
scm1.equal?(opts1, opts2)
defp converge?(%Mix.Dep{scm: scm1, opts: opts1}, %Mix.Dep{scm: scm2, opts: opts2}) do
scm1 == scm2 and opts_equal?(opts1, opts2) and scm1.equal?(opts1, opts2)
end
defp manager_equal?(manager, manager), do: true
defp manager_equal?(_, nil), do: true
defp manager_equal?(nil, _), do: true
defp manager_equal?(_, _), do: false
defp opts_equal?(opts1, opts2) do
keys = ~w(app env compile)a
Enum.all?(keys, &(Keyword.fetch(opts1, &1) == Keyword.fetch(opts2, &1)))
end
defp reject_non_fullfilled_optional(deps) do
apps = Enum.map(deps, & &1.app)
for dep <- deps do
update_in dep.deps, &reject_non_fullfilled_optional(&1, apps)
end
end
defp reject_non_fullfilled_optional(children, upper_breadths) do
Enum.reject children, fn %Mix.Dep{app: app, opts: opts} ->
opts[:optional] && not(app in upper_breadths)
end
end
defp merge_manager(other, dep) do
%{other | manager: other.manager || dep.manager}
defp merge_manager(%{manager: other_manager} = other, %{manager: manager}, in_upper?) do
%{other | manager: sort_manager(other_manager, manager, in_upper?)}
end
@managers [:mix, :rebar3, :rebar, :make]
defp sort_manager(other_manager, manager, true) do
other_manager || manager
end
defp sort_manager(other_manager, manager, false) do
priority = @managers -- (@managers -- (List.wrap(other_manager) ++ List.wrap(manager)))
List.first(priority) || other_manager || manager
end
defp with_matching_req(%Mix.Dep{} = other, %Mix.Dep{} = dep) do
+1 -3
View File
@@ -96,9 +96,7 @@ defmodule Mix.Dep.Fetcher do
# it for compilation too, this is our best to try to solve the
# maximum we can at each deps.get and deps.update.
if Enum.all?(all_deps, &available?/1) do
deps = (with_depending(deps, all_deps) ++
Enum.filter(all_deps, fn dep -> not ok?(dep) end))
|> Enum.uniq(&(&1.app))
deps = Enum.uniq_by(with_depending(deps, all_deps), &(&1.app))
end
# Merge the new lock on top of the old to guarantee we don't
+14 -14
View File
@@ -21,18 +21,18 @@ defmodule Mix.Dep.Loader do
@doc """
Partitions loaded dependencies by environment.
"""
def partition_by_env(deps, opts) do
if env = opts[:env] do
Enum.partition(deps, fn
%Mix.Dep{status: {:divergedonly, _}} ->
true
%Mix.Dep{opts: opts} ->
only = opts[:only] |> List.wrap |> validate_only!
only == [] or env in List.wrap(only)
end)
else
{deps, []}
end
def partition_by_env(deps, nil), do: {deps, []}
def partition_by_env(deps, env), do: Enum.partition(deps, &not skip?(&1, env))
@doc """
Check if a dependency must be skipped according to the environment.
"""
def skip?(_dep, nil), do: false
def skip?(%Mix.Dep{status: {:divergedonly, _}}, _), do: false
def skip?(%Mix.Dep{opts: opts}, env) do
only = opts[:only]
validate_only!(only)
only != nil and not env in List.wrap(only)
end
@doc """
@@ -283,7 +283,7 @@ defmodule Mix.Dep.Loader do
end
defp validate_only!(only) do
for entry <- only, not is_atom(entry) do
for entry <- List.wrap(only), not is_atom(entry) do
Mix.raise "Expected :only in dependency to be an atom or a list of atoms, got: #{inspect only}"
end
only
@@ -293,7 +293,7 @@ defmodule Mix.Dep.Loader do
from = Path.absname("mix.exs")
(Mix.Project.config[:deps] || [])
|> Enum.map(&to_dep(&1, from))
|> partition_by_env(opts)
|> partition_by_env(opts[:env])
|> elem(0)
end
+16
View File
@@ -236,6 +236,22 @@ defmodule Mix.Project do
Path.expand config[:deps_path]
end
@doc """
Returns the full path of all dependencies as a map.
## Examples
Mix.Project.deps_paths
#=> %{foo: "deps/foo", bar: "custom/path/dep"}
"""
@spec deps_paths() :: %{atom => Path.t}
def deps_paths do
Enum.reduce Mix.Dep.loaded(env: Mix.env), %{}, fn
%{app: app, opts: opts}, acc -> Map.put acc, app, opts[:dest]
end
end
@doc """
Returns the build path for this project.
+2 -2
View File
@@ -127,8 +127,8 @@ defmodule Mix.Tasks.Compile do
"""
def manifests do
Enum.flat_map(compilers(), fn(compiler) ->
module = Mix.Task.get!("compile.#{compiler}")
if function_exported?(module, :manifests, 0) do
module = Mix.Task.get("compile.#{compiler}")
if module && function_exported?(module, :manifests, 0) do
module.manifests
else
[]
+6 -1
View File
@@ -25,7 +25,7 @@ defmodule Mix.Tasks.Deps.Compile do
"""
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
format_dep: 1, make?: 1, mix?: 1, rebar?: 1]
format_dep: 1, make?: 1, mix?: 1]
@spec run(OptionParser.argv) :: :ok
def run(args) do
@@ -100,6 +100,11 @@ defmodule Mix.Tasks.Deps.Compile do
"but you are running on v#{System.version}"
end
# Force recompilation on compile status
if dep.status == :compile do
Mix.Dep.Lock.touch_manifest
end
try do
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
:ok in List.wrap(res)
+29 -4
View File
@@ -53,6 +53,17 @@ defmodule Mix.DepTest do
end
end
test "extracts all dependencies paths from the given project" do
Mix.Project.push DepsApp
in_fixture "deps_status", fn ->
paths = Mix.Project.deps_paths
assert map_size(paths) == 6
assert paths[:ok] =~ "deps/ok"
assert paths[:uncloned] =~ "deps/uncloned"
end
end
test "fails on invalid dependencies" do
assert_wrong_dependency [{:ok}]
assert_wrong_dependency [{:ok, nil}]
@@ -144,7 +155,22 @@ defmodule Mix.DepTest do
end
end
test "nested deps convergence with optional dependencies" do
test "nested deps with convergence and managers" do
Process.put(:custom_deps_git_repo_opts, [manager: :make])
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo", manager: :rebar},
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
with_deps deps, fn ->
in_fixture "deps_status", fn ->
[dep1, dep2] = Mix.Dep.loaded([])
assert dep1.manager == nil
assert dep2.manager == :rebar
end
end
end
test "nested deps with convergence and optional dependencies" do
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
@@ -255,9 +281,8 @@ defmodule Mix.DepTest do
deps = Mix.Dep.loaded([])
assert length(deps) == 2
deps = Mix.Dep.loaded([env: :prod])
assert length(deps) == 1
assert Enum.find deps, &match?(%Mix.Dep{app: :foo}, &1)
assert [dep] = Mix.Dep.loaded([env: :prod])
assert dep.app == :foo
end
end
end
+6
View File
@@ -26,6 +26,12 @@ defmodule Mix.Tasks.CompileTest do
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]}
end
test "compile does not require all compilers available on manifest" do
Mix.Project.push CustomCompilers
assert Mix.Tasks.Compile.manifests |> Enum.map(&Path.basename/1) ==
[".compile.elixir"]
end
test "compile a project with mixfile" do
in_fixture "no_mixfile", fn ->
assert Mix.Tasks.Compile.run([]) == :ok
-3
View File
@@ -146,9 +146,6 @@ defmodule Mix.Tasks.DepsGitTest do
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
# Clear up to prepare for the update
File.rm("_build/dev/lib/git_repo/ebin/Elixir.GitRepo.beam")
File.rm("_build/dev/lib/git_repo/.compile.elixir")
File.rm("deps/git_repo/.fetch")
Mix.Task.clear
Mix.shell.flush
purge [A, B, C, GitRepo]
+8 -4
View File
@@ -113,15 +113,19 @@ defmodule Mix.UmbrellaTest do
end
"""
# Should work across all environments
# Does not fetch when filtered
Mix.Tasks.Deps.Get.run ["--only", "dev"]
refute_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
# But works across all environments
Mix.Tasks.Deps.Get.run []
assert_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
# Works on the current environment only
# Does not show by default
Mix.Tasks.Deps.run []
refute_received {:mix_shell, :info, ["* git_repo " <> _]}
refute_received {:mix_shell, :info, ["* git_repo" <> _]}
# Works on the other environment only
# But shows on proper environment
Mix.env(:other)
Mix.Tasks.Deps.run []
assert_received {:mix_shell, :info, ["* git_repo " <> _]}
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.2.0"},
{vsn, "1.2.3"},
{modules, [
elixir
]},