Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0caa75760b | ||
|
|
87da507c24 | ||
|
|
f73e6f8711 | ||
|
|
3610d59214 | ||
|
|
6243fb2755 | ||
|
|
7c5abe35e6 | ||
|
|
fc8314508f | ||
|
|
091ce84e18 | ||
|
|
bbce69b58e | ||
|
|
56779fc25b | ||
|
|
13b432c48d | ||
|
|
effb685854 | ||
|
|
a51c1beb77 | ||
|
|
8a226218e0 | ||
|
|
a9559ef045 | ||
|
|
cb4310111b | ||
|
|
46cab56dfd | ||
|
|
7aebe55236 | ||
|
|
8aab3ce628 | ||
|
|
59d6c803df | ||
|
|
72eb5c73a5 | ||
|
|
982aba3519 | ||
|
|
56f1037973 | ||
|
|
e5fcef3832 | ||
|
|
9081a7aad2 | ||
|
|
328f581ef5 | ||
|
|
aa695875fe | ||
|
|
aa039ce9f9 | ||
|
|
2fdf96e747 | ||
|
|
494bded12a | ||
|
|
2b0fc529ee | ||
|
|
aaa775a3bf | ||
|
|
6647b00fcf | ||
|
|
d149c4c1e0 | ||
|
|
58810bd33c | ||
|
|
050cb45664 | ||
|
|
7f59aeeb6f | ||
|
|
e2abd35b3c | ||
|
|
cc35c8fc22 | ||
|
|
2718631eb6 | ||
|
|
9d9aa8bff8 | ||
|
|
65aee00809 | ||
|
|
9f028834d7 | ||
|
|
44ef53ec2b | ||
|
|
d1290d62db | ||
|
|
301aa6eddc | ||
|
|
098b2d7cfd | ||
|
|
d92b7a331b | ||
|
|
8930ea7598 | ||
|
|
01a1acd27b | ||
|
|
127691d4ad | ||
|
|
e848e87f52 | ||
|
|
bcadd96e31 | ||
|
|
99a0bfa074 | ||
|
|
5d00232633 | ||
|
|
c319bc450e | ||
|
|
a9ed654ae2 | ||
|
|
6dacf53268 | ||
|
|
5e22b12272 | ||
|
|
d4501893c7 | ||
|
|
744859ee42 | ||
|
|
4596baab3f | ||
|
|
c065041263 | ||
|
|
95bff23ede | ||
|
|
0bc04195f2 | ||
|
|
d195b75aa3 | ||
|
|
dc06a18287 | ||
|
|
54c0574be2 | ||
|
|
8d50e1fa95 | ||
|
|
e70d9723eb | ||
|
|
3ce029615d | ||
|
|
416bf41a03 | ||
|
|
c758c5e401 | ||
|
|
7b4576f0fc | ||
|
|
9192907e68 | ||
|
|
5533cc4665 | ||
|
|
873dbc16ff | ||
|
|
84c91ef914 | ||
|
|
a977afcd6c | ||
|
|
8ed30ad926 | ||
|
|
8496d52df5 | ||
|
|
977bb418a1 | ||
|
|
735296a4ab | ||
|
|
e34dde79ea | ||
|
|
1eb11374c3 | ||
|
|
9c2ec6a260 | ||
|
|
92cf02321b | ||
|
|
2b5e69aa05 | ||
|
|
a56606d5d6 | ||
|
|
b2329d11a8 | ||
|
|
88b3e864d5 | ||
|
|
efaca6c3e9 | ||
|
|
67aabe0bac | ||
|
|
8506cc68bf | ||
|
|
a1bbc1bc20 | ||
|
|
18dfca21f0 |
+104
-3
@@ -102,11 +102,11 @@ In any case, the previous behaviour can be brought back with the `--verbose` fla
|
|||||||
|
|
||||||
### mix xref
|
### mix xref
|
||||||
|
|
||||||
Speaking about warnings, Mix v1.3 includes a new task called `xref` that performs cross reference checks in your code. One of such checks is the ability to find calls to modules and functions that do not exist. For example, if in your library code you call `ThisModuleDoesNotExist.foo(1, 2, 3)`, `mix xref --unreachable` will be able to find such code and let you know about it.
|
Speaking about warnings, Mix v1.3 includes a new task called `xref` that performs cross reference checks in your code. One of such checks is the ability to find calls to modules and functions that do not exist. For example, if in your library code you call `ThisModuleDoesNotExist.foo(1, 2, 3)`, `mix xref unreachable` will be able to find such code and let you know about it.
|
||||||
|
|
||||||
Since such checks can discover possible bugs in your codebase, a new compiler called `xref` has been added to `Mix.compilers/0`, so they run by default every time you compile your code.
|
Since such checks can discover possible bugs in your codebase, a new compiler called `xref` has been added to `Mix.compilers/0`, so they run by default every time you compile your code.
|
||||||
|
|
||||||
We have included other modes in `xref`, such as `mix xref --callers Foo`, to find all places in your code that a function from the module `Foo` is called. We hope other tools and text editors can leverage such features to provide useful functionality for their users.
|
We have included other modes in `xref`, such as `mix xref callers Foo`, to find all places in your code that a function from the module `Foo` is called. We hope other tools and text editors can leverage such features to provide useful functionality for their users.
|
||||||
|
|
||||||
### Better dependency tracking
|
### Better dependency tracking
|
||||||
|
|
||||||
@@ -268,7 +268,107 @@ end
|
|||||||
|
|
||||||
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
|
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
|
||||||
|
|
||||||
## v1.3.0-rc.1 (2016-08-06)
|
## v1.3.3 (2016-09-17)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [DateTime] Support negative integer in `DateTime.from_unix/2`
|
||||||
|
* [Kernel.LexicalTracker] Do not consider remote typespecs as a compile-time dependency
|
||||||
|
* [Kernel.ParallelCompiler] Do not emit deadlock messages when the process is waiting on itself
|
||||||
|
* [Kernel.Typespec] Mark struct update syntax as generated to avoid false positives from dialyzer
|
||||||
|
|
||||||
|
#### ExUnit
|
||||||
|
|
||||||
|
* [ExUnit] Make ExUnit server timeout configurable
|
||||||
|
|
||||||
|
#### Logger
|
||||||
|
|
||||||
|
* [Logger] Use `:ansi_color` if one is available in metadata
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Add support for the `:sparse` option in `Mix.SCM.Git`
|
||||||
|
* [Mix] Skip dependendency loading if `MIX_NO_DEPS` is set to `1`
|
||||||
|
|
||||||
|
### 2. Bug fixes
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [System] Use `NUL` instead of `/dev/null` on Windows when building `System.build_info`
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx.Autocomplete] Resolves issue with autocompletion on structs not working
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Also store external resources that are not part of the current working directory in compilation manifest
|
||||||
|
* [Mix] Always include the compiled file source in manifests
|
||||||
|
|
||||||
|
## v1.3.2 (2016-07-15)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [Kernel] Support guards in `else` clauses in `with`
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Add `MIX_NO_DEPS` env var for disabling dep loading. Used for third-party scripts and tools like Nix package manager
|
||||||
|
* [Mix] Add `mix test --listen-on-stdin` that automatically reruns tests on stdin
|
||||||
|
* [Mix] Disable `--warnings-as-errors` when compiling dependencies
|
||||||
|
* [Mix] Add `--filter` option to `mix deps.unlock` to unlock only matching dependencies
|
||||||
|
|
||||||
|
### 2. Bug fixes
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [Enum] Return `nil` if enumerable halts in `Enum.find_index/3`
|
||||||
|
* [Kernel] Do not attempt to load modules that have not been required when invoking regular functions, otherwise this invalidates the `@compile {:autoload, false}` directive.
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Ensure missing protocol dependencies are discarded in umbrella projects with shared build
|
||||||
|
|
||||||
|
#### ExUnit
|
||||||
|
|
||||||
|
* [ExUnit.Diff] Ensure no leading or trailing when diffing some maps
|
||||||
|
|
||||||
|
## v1.3.1 (2016-06-28)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx.Helpers] `Add import_file_if_available` for importing files only if they are available
|
||||||
|
* [IEx.Helpers] `Add import_if_available` for importing modules only if they are available
|
||||||
|
|
||||||
|
### 2. Bug fixes
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [Kernel] Ensure structs can be expanded in dynamic module names
|
||||||
|
* [Kernel] Ensure aliases warnings are not accidentally discarded when the same module is imported
|
||||||
|
* [Kernel.ParallelCompiler] Ensure two modules with cyclic struct dependencies cannot run into a deadlock when compiling
|
||||||
|
* [Kernel.Typespec] Support module attributes in remote types
|
||||||
|
* [Module] Do not expect stacktraces to be always present when dispatching to locals during the module compilation
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx.Helpers] Fix `h` helper for operators
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Do not load modules for xref purposes, instead use BEAM info
|
||||||
|
* [Mix] Ensure `deps.check` does not check archives (that's done in loadpaths)
|
||||||
|
* [Mix] Validate application properties before traversing them
|
||||||
|
* [Mix] Check for proper Makefile when compiling on Windows
|
||||||
|
* [Mix] Enforce space after comma in `mix do`
|
||||||
|
|
||||||
|
## v1.3.0 (2016-06-21)
|
||||||
|
|
||||||
### 1. Enhancements
|
### 1. Enhancements
|
||||||
|
|
||||||
@@ -283,6 +383,7 @@ By restricting hierarchies in favor of named setups, it is straight-forward for
|
|||||||
* [CLI] Add `--logger-otp-reports BOOL` and `--logger-sasl-reports BOOL` switches
|
* [CLI] Add `--logger-otp-reports BOOL` and `--logger-sasl-reports BOOL` switches
|
||||||
* [Compiler] Emit a summary of compilation errors when modules are missing
|
* [Compiler] Emit a summary of compilation errors when modules are missing
|
||||||
* [Enum] Add `Enum.group_by/3` that allows developers to map on the value being grouped
|
* [Enum] Add `Enum.group_by/3` that allows developers to map on the value being grouped
|
||||||
|
* [Enum] Make list values in maps returned by `Enum.group_by/2` and `Enum.group_by/3` preserve the order of the input enumerable instead of reversing it.
|
||||||
* [Enum] Add `Enum.drop_every/2` that drops every `nth`, including the first one
|
* [Enum] Add `Enum.drop_every/2` that drops every `nth`, including the first one
|
||||||
* [Exception] Suggest possible functions on `UndefinedFunctionError` for existing modules
|
* [Exception] Suggest possible functions on `UndefinedFunctionError` for existing modules
|
||||||
* [Exception] Warn if unknown fields are given to `raise/2`
|
* [Exception] Warn if unknown fields are given to `raise/2`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
REBAR ?= "$(CURDIR)/rebar"
|
REBAR ?= "$(CURDIR)/rebar"
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
DOCS := master
|
DOCS := v1.3
|
||||||
CANONICAL := master
|
CANONICAL := stable
|
||||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||||
ERLC := erlc -I lib/elixir/include
|
ERLC := erlc -I lib/elixir/include
|
||||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ This document simply outlines the release process:
|
|||||||
|
|
||||||
11. Create a new branch "vMAJOR.MINOR"
|
11. Create a new branch "vMAJOR.MINOR"
|
||||||
|
|
||||||
12. Move docs generation to `docs/vMAJOR.MINOR` in Makefile, set CANONICAL reference and copy them to `docs/stable` (change index.html accordingly)
|
12. Move docs generation to `docs/vMAJOR.MINOR` in Makefile, set CANONICAL to stable and copy them to `docs/stable` (change index.html accordingly)
|
||||||
|
|
||||||
13. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
13. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||||
|
|
||||||
|
|||||||
@@ -435,6 +435,13 @@ defmodule Access do
|
|||||||
...> prev -> {prev, String.upcase(prev)}
|
...> prev -> {prev, String.upcase(prev)}
|
||||||
...> end)
|
...> end)
|
||||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||||
|
|
||||||
|
`at/1` can also be used to pop elements out of a list or
|
||||||
|
a key inside of a list:
|
||||||
|
|
||||||
|
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||||
|
iex> pop_in(list, [Access.at(0)])
|
||||||
|
{%{name: "john"}, [%{name: "mary"}]}
|
||||||
iex> pop_in(list, [Access.at(0), :name])
|
iex> pop_in(list, [Access.at(0), :name])
|
||||||
{"john", [%{}, %{name: "mary"}]}
|
{"john", [%{}, %{name: "mary"}]}
|
||||||
|
|
||||||
@@ -477,7 +484,7 @@ defmodule Access do
|
|||||||
defp get_and_update_at([head | rest], 0, next, updates) do
|
defp get_and_update_at([head | rest], 0, next, updates) do
|
||||||
case next.(head) do
|
case next.(head) do
|
||||||
{get, update} -> {get, :lists.reverse([update | updates], rest)}
|
{get, update} -> {get, :lists.reverse([update | updates], rest)}
|
||||||
:pop -> {head, :lists.reverse([head | updates], rest)}
|
:pop -> {head, :lists.reverse(updates, rest)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+48
-11
@@ -1004,19 +1004,32 @@ defmodule DateTime do
|
|||||||
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 22, microsecond: {211914, 3}, minute: 53,
|
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 22, microsecond: {211914, 3}, minute: 53,
|
||||||
month: 1, second: 43, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
month: 1, second: 43, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
||||||
year: 46302, zone_abbr: "UTC"}}
|
year: 46302, zone_abbr: "UTC"}}
|
||||||
|
|
||||||
|
Negative Unix times are supported, up to -#{@unix_epoch} seconds,
|
||||||
|
which is equivalent to "0000-01-01T00:00:00Z" or 0 gregorian seconds.
|
||||||
|
|
||||||
|
iex> DateTime.from_unix(-12345678910)
|
||||||
|
{:ok, %DateTime{calendar: Calendar.ISO, day: 13, hour: 4, microsecond: {0, 0}, minute: 44,
|
||||||
|
month: 10, second: 50, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
||||||
|
year: 1578, zone_abbr: "UTC"}}
|
||||||
|
|
||||||
|
When a Unix time before that moment is passed to `from_unix/2`, `:error` will be returned.
|
||||||
"""
|
"""
|
||||||
@spec from_unix(non_neg_integer, :native | System.time_unit) :: {:ok, DateTime.t}
|
@spec from_unix(integer, :native | System.time_unit) :: {:ok, DateTime.t}
|
||||||
def from_unix(integer, unit \\ :seconds) when is_integer(integer) and integer >= 0 do
|
def from_unix(integer, unit \\ :seconds) when is_integer(integer) do
|
||||||
total = System.convert_time_unit(integer, unit, :microseconds)
|
total = System.convert_time_unit(integer, unit, :microseconds)
|
||||||
microsecond = rem(total, 1_000_000)
|
if total < -@unix_epoch * 1_000_000 do
|
||||||
precision = precision_for_unit(unit)
|
:error
|
||||||
{{year, month, day}, {hour, minute, second}} =
|
else
|
||||||
:calendar.gregorian_seconds_to_datetime(@unix_epoch + div(total, 1_000_000))
|
microsecond = rem(total, 1_000_000)
|
||||||
|
precision = precision_for_unit(unit)
|
||||||
|
{{year, month, day}, {hour, minute, second}} =
|
||||||
|
:calendar.gregorian_seconds_to_datetime(@unix_epoch + div(total, 1_000_000))
|
||||||
|
|
||||||
{:ok, %DateTime{year: year, month: month, day: day,
|
{:ok, %DateTime{year: year, month: month, day: day,
|
||||||
hour: hour, minute: minute, second: second, microsecond: {microsecond, precision},
|
hour: hour, minute: minute, second: second, microsecond: {microsecond, precision},
|
||||||
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}}
|
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def precision_for_unit(unit) do
|
def precision_for_unit(unit) do
|
||||||
@@ -1034,6 +1047,13 @@ defmodule DateTime do
|
|||||||
@doc """
|
@doc """
|
||||||
Converts the given Unix time to DateTime.
|
Converts the given Unix time to DateTime.
|
||||||
|
|
||||||
|
The integer can be given in different unit
|
||||||
|
according to `System.convert_time_unit/3` and it will
|
||||||
|
be converted to microseconds internally.
|
||||||
|
|
||||||
|
Unix times are always in UTC and therefore the DateTime
|
||||||
|
will be returned in UTC.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> DateTime.from_unix!(1464096368)
|
iex> DateTime.from_unix!(1464096368)
|
||||||
@@ -1046,6 +1066,15 @@ defmodule DateTime do
|
|||||||
month: 5, second: 8, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
month: 5, second: 8, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
||||||
year: 2015, zone_abbr: "UTC"}
|
year: 2015, zone_abbr: "UTC"}
|
||||||
|
|
||||||
|
Negative Unix times are supported, up to -#{@unix_epoch} seconds,
|
||||||
|
which is equivalent to "0000-01-01T00:00:00Z" or 0 gregorian seconds.
|
||||||
|
|
||||||
|
iex> DateTime.from_unix(-12345678910)
|
||||||
|
{:ok, %DateTime{calendar: Calendar.ISO, day: 13, hour: 4, microsecond: {0, 0}, minute: 44,
|
||||||
|
month: 10, second: 50, std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0,
|
||||||
|
year: 1578, zone_abbr: "UTC"}}
|
||||||
|
|
||||||
|
When a Unix time before that moment is passed to `from_unix!/2`, an ArgumentError will be raised.
|
||||||
"""
|
"""
|
||||||
@spec from_unix!(non_neg_integer, :native | System.time_unit) :: DateTime.t
|
@spec from_unix!(non_neg_integer, :native | System.time_unit) :: DateTime.t
|
||||||
def from_unix!(integer, unit \\ :seconds) when is_atom(unit) do
|
def from_unix!(integer, unit \\ :seconds) when is_atom(unit) do
|
||||||
@@ -1057,7 +1086,7 @@ defmodule DateTime do
|
|||||||
Converts the given DateTime to Unix time.
|
Converts the given DateTime to Unix time.
|
||||||
|
|
||||||
The DateTime is expected to be using the ISO calendar
|
The DateTime is expected to be using the ISO calendar
|
||||||
with a year greater than or equal to 1970.
|
with a year greater than or equal to 0.
|
||||||
|
|
||||||
It will return the integer with the given unit,
|
It will return the integer with the given unit,
|
||||||
according to `System.convert_time_unit/3`.
|
according to `System.convert_time_unit/3`.
|
||||||
@@ -1073,11 +1102,19 @@ defmodule DateTime do
|
|||||||
iex> DateTime.to_unix(dt)
|
iex> DateTime.to_unix(dt)
|
||||||
1416517099
|
1416517099
|
||||||
|
|
||||||
|
iex> flamel = %DateTime{calendar: Calendar.ISO, day: 22, hour: 8, microsecond: {527771, 6},
|
||||||
|
...> minute: 2, month: 3, second: 25, std_offset: 0, time_zone: "Etc/UTC",
|
||||||
|
...> utc_offset: 0, year: 1418, zone_abbr: "UTC"}
|
||||||
|
iex> DateTime.to_unix(flamel)
|
||||||
|
-17412508655
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec to_unix(DateTime.t, System.time_unit) :: non_neg_integer
|
@spec to_unix(DateTime.t, System.time_unit) :: non_neg_integer
|
||||||
|
def to_unix(datetime, unit \\ :seconds)
|
||||||
|
|
||||||
def to_unix(%DateTime{calendar: Calendar.ISO, std_offset: std_offset, utc_offset: utc_offset,
|
def to_unix(%DateTime{calendar: Calendar.ISO, std_offset: std_offset, utc_offset: utc_offset,
|
||||||
hour: hour, minute: minute, second: second, microsecond: {microsecond, _},
|
hour: hour, minute: minute, second: second, microsecond: {microsecond, _},
|
||||||
year: year, month: month, day: day}, unit \\ :seconds) when year >= 1970 do
|
year: year, month: month, day: day}, unit) when year >= 0 do
|
||||||
seconds =
|
seconds =
|
||||||
:calendar.datetime_to_gregorian_seconds({{year, month, day}, {hour, minute, second}})
|
:calendar.datetime_to_gregorian_seconds({{year, month, day}, {hour, minute, second}})
|
||||||
|> Kernel.-(utc_offset)
|
|> Kernel.-(utc_offset)
|
||||||
|
|||||||
@@ -861,13 +861,13 @@ defmodule Enum do
|
|||||||
|
|
||||||
def find_index(enumerable, fun) do
|
def find_index(enumerable, fun) do
|
||||||
res =
|
res =
|
||||||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn(entry, {_status, index}) ->
|
||||||
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
|
if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case res do
|
case res do
|
||||||
{:halted, entry} -> entry
|
{_, {:found, index}} -> index
|
||||||
{:done, _} -> nil
|
{_, {:not_found, _}} -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -997,7 +997,8 @@ defmodule Enum do
|
|||||||
|
|
||||||
# TODO: Remove on 2.0
|
# TODO: Remove on 2.0
|
||||||
def group_by(enumerable, dict, fun) when is_function(fun, 1) do
|
def group_by(enumerable, dict, fun) when is_function(fun, 1) do
|
||||||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated, please use a map instead"
|
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
|
||||||
|
"A map is used by default and it is no longer required to pass one to this function"
|
||||||
reduce(reverse(enumerable), dict, fn(entry, categories) ->
|
reduce(reverse(enumerable), dict, fn(entry, categories) ->
|
||||||
Dict.update(categories, fun.(entry), [entry], &[entry | &1])
|
Dict.update(categories, fun.(entry), [entry], &[entry | &1])
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ defmodule Exception do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@typedoc "The exception type"
|
@typedoc "The exception type"
|
||||||
@type t :: %{__struct__: module, __exception__: true}
|
@type t :: %{
|
||||||
|
required(:__struct__) => module,
|
||||||
|
required(:__exception__) => true,
|
||||||
|
atom => any
|
||||||
|
}
|
||||||
|
|
||||||
@typedoc "The kind handled by formatting functions"
|
@typedoc "The kind handled by formatting functions"
|
||||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ defmodule GenServer do
|
|||||||
@spec reply(from, term) :: :ok
|
@spec reply(from, term) :: :ok
|
||||||
def reply(client, reply)
|
def reply(client, reply)
|
||||||
|
|
||||||
def reply({to, tag}, reply) do
|
def reply({to, tag}, reply) when is_pid(to) do
|
||||||
try do
|
try do
|
||||||
send(to, {tag, reply})
|
send(to, {tag, reply})
|
||||||
:ok
|
:ok
|
||||||
|
|||||||
@@ -1862,7 +1862,7 @@ defmodule Kernel do
|
|||||||
In case any entry returns `nil`, its key will be removed
|
In case any entry returns `nil`, its key will be removed
|
||||||
and the deletion will be considered a success.
|
and the deletion will be considered a success.
|
||||||
"""
|
"""
|
||||||
@spec pop_in(Access.t, nonempty_list(term)) :: Access.t
|
@spec pop_in(Access.t, nonempty_list(term)) :: {term, Access.t}
|
||||||
def pop_in(data, keys)
|
def pop_in(data, keys)
|
||||||
def pop_in(nil, [h | _]), do: Access.pop(nil, h)
|
def pop_in(nil, [h | _]), do: Access.pop(nil, h)
|
||||||
def pop_in(data, keys), do: do_pop_in(data, keys)
|
def pop_in(data, keys), do: do_pop_in(data, keys)
|
||||||
@@ -3473,7 +3473,7 @@ defmodule Kernel do
|
|||||||
end
|
end
|
||||||
|
|
||||||
unquote(builder)
|
unquote(builder)
|
||||||
|
Kernel.Utils.announce_struct(__MODULE__)
|
||||||
fields
|
fields
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4314,7 +4314,7 @@ defmodule Kernel do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Deprecate by v1.5
|
# TODO: Deprecate by v1.5
|
||||||
# @doc false
|
@doc false
|
||||||
defmacro to_char_list(arg) do
|
defmacro to_char_list(arg) do
|
||||||
quote do: Kernel.to_charlist(unquote(arg))
|
quote do: Kernel.to_charlist(unquote(arg))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -148,17 +148,15 @@ defmodule Kernel.LexicalTracker do
|
|||||||
|
|
||||||
def handle_cast({:add_import, module, fas, line, warn}, state) when is_atom(module) do
|
def handle_cast({:add_import, module, fas, line, warn}, state) when is_atom(module) do
|
||||||
directives =
|
directives =
|
||||||
for {{:import, {import_module, _, _}}, _} = directive <- state.directives,
|
state.directives
|
||||||
module != import_module,
|
|> Enum.reject(&match?({{:import, {^module, _, _}}, _}, &1))
|
||||||
do: directive,
|
|> :maps.from_list
|
||||||
into: %{}
|
|> add_directive(module, line, warn, :import)
|
||||||
|
|
||||||
directives = add_directive(directives, module, line, warn, :import)
|
|
||||||
|
|
||||||
directives =
|
directives =
|
||||||
Enum.reduce fas, directives, fn {function, arity}, directives ->
|
Enum.reduce(fas, directives, fn {function, arity}, directives ->
|
||||||
add_directive(directives, {module, function, arity}, line, warn, :import)
|
add_directive(directives, {module, function, arity}, line, warn, :import)
|
||||||
end
|
end)
|
||||||
|
|
||||||
{:noreply, %{state | directives: directives}}
|
{:noreply, %{state | directives: directives}}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
:erlang.spawn_monitor fn ->
|
:erlang.spawn_monitor fn ->
|
||||||
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
||||||
:erlang.put(:elixir_compiler_pid, parent)
|
:erlang.put(:elixir_compiler_pid, parent)
|
||||||
|
:erlang.put(:elixir_compiler_file, file)
|
||||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||||
|
|
||||||
exit(try do
|
exit(try do
|
||||||
@@ -206,8 +207,10 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
|
|
||||||
{:waiting, kind, child, ref, on, defining} ->
|
{:waiting, kind, child, ref, on, defining} ->
|
||||||
# Oops, we already got it, do not put it on waiting.
|
# Oops, we already got it, do not put it on waiting.
|
||||||
|
# OR
|
||||||
|
# We're waiting on ourselves, send :found so that we can crash with a better error
|
||||||
waiting =
|
waiting =
|
||||||
if :lists.any(&match?({^kind, ^on}, &1), result) do
|
if :lists.any(&match?({^kind, ^on}, &1), result) or on in defining do
|
||||||
send child, {ref, :found}
|
send child, {ref, :found}
|
||||||
waiting
|
waiting
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -47,21 +47,22 @@ defmodule Kernel.ParallelRequire do
|
|||||||
wait_for_messages(files, waiting, callbacks, schedulers, result)
|
wait_for_messages(files, waiting, callbacks, schedulers, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp spawn_requires([h | t], waiting, callbacks, schedulers, result) do
|
defp spawn_requires([file | files], waiting, callbacks, schedulers, result) do
|
||||||
parent = self()
|
parent = self()
|
||||||
{pid, ref} = :erlang.spawn_monitor fn ->
|
{pid, ref} = :erlang.spawn_monitor fn ->
|
||||||
:erlang.put(:elixir_compiler_pid, parent)
|
:erlang.put(:elixir_compiler_pid, parent)
|
||||||
|
:erlang.put(:elixir_compiler_file, file)
|
||||||
|
|
||||||
exit(try do
|
exit(try do
|
||||||
new = Code.require_file(h) || []
|
new = Code.require_file(file) || []
|
||||||
{:required, Enum.map(new, &elem(&1, 0)), h}
|
{:required, Enum.map(new, &elem(&1, 0)), file}
|
||||||
catch
|
catch
|
||||||
kind, reason ->
|
kind, reason ->
|
||||||
{:failure, kind, reason, System.stacktrace}
|
{:failure, kind, reason, System.stacktrace}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
spawn_requires(t, [{pid, ref} | waiting], callbacks, schedulers, result)
|
spawn_requires(files, [{pid, ref} | waiting], callbacks, schedulers, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp wait_for_messages(files, waiting, callbacks, schedulers, result) do
|
defp wait_for_messages(files, waiting, callbacks, schedulers, result) do
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ defmodule Kernel.Typespec do
|
|||||||
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
||||||
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
||||||
defp translate_spec(:macrocallback, meta, name, args, return, guard, caller),
|
defp translate_spec(:macrocallback, meta, name, args, return, guard, caller),
|
||||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t) | args], return, guard, caller)
|
do: translate_spec(:callback, meta, :"MACRO-#{name}", macro_args(args), return, guard, caller)
|
||||||
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
||||||
ensure_no_defaults!(args)
|
ensure_no_defaults!(args)
|
||||||
|
|
||||||
@@ -470,6 +470,10 @@ defmodule Kernel.Typespec do
|
|||||||
{{kind, {name, arity}, spec}, caller.line}
|
{{kind, {name, arity}, spec}, caller.line}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp macro_args(args) do
|
||||||
|
[quote(do: {line :: Macro.Env.line, env :: Macro.Env.t}) | args]
|
||||||
|
end
|
||||||
|
|
||||||
defp ensure_no_defaults!(args) do
|
defp ensure_no_defaults!(args) do
|
||||||
:lists.foreach fn
|
:lists.foreach fn
|
||||||
{:::, _, [left, right]} ->
|
{:::, _, [left, right]} ->
|
||||||
@@ -773,6 +777,8 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||||
|
# We cannot set a function name to avoid tracking
|
||||||
|
# as a compile time dependency, because for structs it actually is one.
|
||||||
module = Macro.expand(name, caller)
|
module = Macro.expand(name, caller)
|
||||||
|
|
||||||
struct =
|
struct =
|
||||||
@@ -809,6 +815,8 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
||||||
|
# We cannot set a function name to avoid tracking
|
||||||
|
# as a compile time dependency because for records it actually is one.
|
||||||
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||||
keyword when is_list(keyword) ->
|
keyword when is_list(keyword) ->
|
||||||
types =
|
types =
|
||||||
@@ -864,9 +872,24 @@ defmodule Kernel.Typespec do
|
|||||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Handle remote calls in the form of @module_attribute.type.
|
||||||
|
# These are not handled by the general remote type clause as calling
|
||||||
|
# Macro.expand/2 on the remote does not expand module attributes (but expands
|
||||||
|
# things like __MODULE__).
|
||||||
|
defp typespec({{:., meta, [{:@, _, [{attr, _, _}]}, name]}, _, args} = orig, vars, caller) do
|
||||||
|
remote = Module.get_attribute(caller.module, attr)
|
||||||
|
unless is_atom(remote) and remote != nil do
|
||||||
|
message = "invalid remote in typespec: #{Macro.to_string(orig)} (@#{attr} is #{inspect remote})"
|
||||||
|
compile_error(caller, message)
|
||||||
|
end
|
||||||
|
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||||
|
end
|
||||||
|
|
||||||
# Handle remote calls
|
# Handle remote calls
|
||||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||||
remote = Macro.expand remote, caller
|
# We set a function name to avoid tracking
|
||||||
|
# aliases in typespecs as compile time dependencies.
|
||||||
|
remote = Macro.expand remote, %{caller | function: {:typespec, 0}}
|
||||||
unless is_atom(remote) do
|
unless is_atom(remote) do
|
||||||
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
|
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -84,4 +84,11 @@ defmodule Kernel.Utils do
|
|||||||
List.wrap(Module.get_attribute(module, :enforce_keys)),
|
List.wrap(Module.get_attribute(module, :enforce_keys)),
|
||||||
Module.get_attribute(module, :derive)}
|
Module.get_attribute(module, :derive)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def announce_struct(module) do
|
||||||
|
case :erlang.get(:elixir_compiler_pid) do
|
||||||
|
:undefined -> :ok
|
||||||
|
pid -> send(pid, {:struct_available, module})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1098,7 +1098,7 @@ defmodule Macro do
|
|||||||
|
|
||||||
case expand do
|
case expand do
|
||||||
{:ok, receiver, quoted} ->
|
{:ok, receiver, quoted} ->
|
||||||
next = :elixir_counter.next
|
next = :erlang.unique_integer()
|
||||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||||
{:ok, _receiver, _name, _args} ->
|
{:ok, _receiver, _name, _args} ->
|
||||||
{original, false}
|
{original, false}
|
||||||
@@ -1119,7 +1119,7 @@ defmodule Macro do
|
|||||||
|
|
||||||
case expand do
|
case expand do
|
||||||
{:ok, receiver, quoted} ->
|
{:ok, receiver, quoted} ->
|
||||||
next = :elixir_counter.next
|
next = :erlang.unique_integer()
|
||||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||||
:error ->
|
:error ->
|
||||||
{original, false}
|
{original, false}
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ defmodule Module do
|
|||||||
* `@compile :debug_info` - includes `:debug_info` regardless of the
|
* `@compile :debug_info` - includes `:debug_info` regardless of the
|
||||||
setting in `Code.compiler_options`
|
setting in `Code.compiler_options`
|
||||||
|
|
||||||
* `@compile {:debug_info, false} - disables `:debug_info` regardless
|
* `@compile {:debug_info, false}` - disables `:debug_info` regardless
|
||||||
of the setting in `Code.compiler_options`
|
of the setting in `Code.compiler_options`
|
||||||
|
|
||||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||||
@@ -499,7 +499,7 @@ defmodule Module do
|
|||||||
raise ArgumentError, "expected :file to be given as option"
|
raise ArgumentError, "expected :file to be given as option"
|
||||||
end
|
end
|
||||||
|
|
||||||
next = :elixir_counter.next
|
next = :erlang.unique_integer()
|
||||||
line = Keyword.get(opts, :line, 0)
|
line = Keyword.get(opts, :line, 0)
|
||||||
quoted = :elixir_quote.linify_with_context_counter(line, {module, next}, quoted)
|
quoted = :elixir_quote.linify_with_context_counter(line, {module, next}, quoted)
|
||||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||||
|
|||||||
@@ -15,13 +15,24 @@ defmodule OptionParser do
|
|||||||
@doc """
|
@doc """
|
||||||
Parses `argv` into a keywords list.
|
Parses `argv` into a keywords list.
|
||||||
|
|
||||||
It returns a three-element tuple as follows:
|
It returns a three-element tuple with the form `{parsed, args, invalid}`, where:
|
||||||
|
|
||||||
1. parsed switches,
|
* `parsed` is a keyword list of parsed switches with `{switch_name, value}`
|
||||||
2. remaining arguments,
|
tuples in it; `switch_name` is the atom representing the switch name while
|
||||||
3. invalid options.
|
`value` is the value for that switch parsed according to `opts` (see the
|
||||||
|
"Examples" section for more information)
|
||||||
|
* `args` is a list of the remaining arguments in `argv` as strings
|
||||||
|
* `invalid` is a list of invalid options as `{option_name, value}` where
|
||||||
|
`option_name` is the raw option and `value` is `nil` if the option wasn't
|
||||||
|
expected or the string value if the value didn't have the expected type for
|
||||||
|
the corresponding option
|
||||||
|
|
||||||
## Examples
|
Elixir converts switches to underscored atoms, so `--source-path` becomes
|
||||||
|
`:source_path`. This is done to better suit Elixir conventions. However, this
|
||||||
|
means that switches can't contain underscores and switches that do contain
|
||||||
|
underscores are always returned in the list of invalid options.
|
||||||
|
|
||||||
|
Without any options, this function will try to parse all switches in the `argv`.
|
||||||
|
|
||||||
iex> OptionParser.parse(["--debug"])
|
iex> OptionParser.parse(["--debug"])
|
||||||
{[debug: true], [], []}
|
{[debug: true], [], []}
|
||||||
@@ -32,65 +43,85 @@ defmodule OptionParser do
|
|||||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||||
|
|
||||||
By default, Elixir will try to automatically parse all switches.
|
|
||||||
Switches followed by a value will be assigned the value, as a string.
|
Switches followed by a value will be assigned the value, as a string.
|
||||||
Switches without an argument, like `--debug` will automatically
|
Switches without an argument, like `--debug` in the examples above, will
|
||||||
be set to `true`.
|
automatically be set to `true`.
|
||||||
|
|
||||||
Note: Elixir also converts the switches to underscore atoms, so
|
## Options
|
||||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
|
||||||
conventions. This means that option names on the command line cannot
|
|
||||||
contain underscores; such options will be put in the invalid options
|
|
||||||
list.
|
|
||||||
|
|
||||||
## Switch Definitions
|
The following options are supported:
|
||||||
|
|
||||||
|
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||||
|
* `:aliases` - see the "Aliases" section below
|
||||||
|
|
||||||
|
## Switch definitions
|
||||||
|
|
||||||
Often it is better to explicitly list the known
|
Often it is better to explicitly list the known
|
||||||
switches and their formats. The switches can be specified via two
|
switches and their formats. The switches can be specified via one of two
|
||||||
alternative options:
|
options:
|
||||||
|
|
||||||
* `:switches` - defines some switches. An attempt is still made to parse
|
* `:switches` - defines some switches and their types. This function
|
||||||
switches that do not appear in the list.
|
still attempts to parse switches that are not in this list.
|
||||||
|
* `:strict` - defines strict switches. Any switch in `argv` that is not
|
||||||
|
specified in the list is returned in the invalid options list.
|
||||||
|
|
||||||
* `:strict` - the switches are strict. Any switch that is not specified
|
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||||
in the list is returned in the invalid options list.
|
is an atom defining the name of the switch and `type` is an atom that
|
||||||
|
specifies the type for the value of this switch (see the "Types" section below
|
||||||
|
for the possible types and more information about type casting).
|
||||||
|
|
||||||
Note that you should only supply the `:switches` or `:strict` option.
|
Note that you should only supply the `:switches` or `:strict` option.
|
||||||
If you supply both, an error will be raised.
|
If you supply both, an `ArgumentError` exception will be raised.
|
||||||
|
|
||||||
### Types
|
### Types
|
||||||
|
|
||||||
Option parser switches may take 0 or 1 argument.
|
Switches parsed by `OptionParser` may take zero or one arguments.
|
||||||
|
|
||||||
The following switches take no argument:
|
The following switches types take no arguments:
|
||||||
|
|
||||||
* `:boolean` - sets the value to true when given
|
* `:boolean` - sets the value to `true` when given (see also the
|
||||||
* `:count` - counts the number of times the switch is given
|
"Negation switches" section below)
|
||||||
|
* `:count` - counts the number of times the switch is given
|
||||||
|
|
||||||
The following switches take 1 argument:
|
The following switches take one argument:
|
||||||
|
|
||||||
* `:integer` - parses the upcoming value as an integer.
|
* `:integer` - parses the value as an integer
|
||||||
* `:float` - parses the upcoming value as a float.
|
* `:float` - parses the value as a float
|
||||||
* `:string` - parses the upcoming value as a string.
|
* `:string` - parses the value as a string
|
||||||
|
|
||||||
If a switch can't be parsed, it is returned in the invalid
|
If a switch can't be parsed according to the given type, it is returned
|
||||||
options list.
|
in the invalid options list.
|
||||||
|
|
||||||
### Modifiers
|
### Modifiers
|
||||||
|
|
||||||
Switches can be specified with modifiers, which change how
|
Switches can be specified with modifiers, which change how
|
||||||
they behave. The following modifiers are supported:
|
they behave. The following modifiers are supported:
|
||||||
|
|
||||||
* `:keep` - keeps duplicated items instead of overriding them.
|
* `:keep` - keeps duplicated items instead of overriding them; works with
|
||||||
Works with all types except `:count`.
|
all types except `:count`. Specifying `switch_name: :keep` assumes the
|
||||||
|
type of `:switch_name` will be `:string`.
|
||||||
|
|
||||||
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
|
Note that if you want to use `:keep` with a type other than `:string`, use a list
|
||||||
`[foo: [:integer, :keep]]`.
|
as the type for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||||
|
|
||||||
### Examples
|
### Negation switches
|
||||||
|
|
||||||
Here are some examples of option parser working with different types
|
In case a switch `SWITCH` is specified to have type `:boolean`, it may be
|
||||||
and modifiers:
|
passed as `--no-SWITCH` as well which will set the option to `false`:
|
||||||
|
|
||||||
|
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||||
|
{[op: false], ["path/to/file"], []}
|
||||||
|
|
||||||
|
## Aliases
|
||||||
|
|
||||||
|
A set of aliases can be specified in the `:aliases` option:
|
||||||
|
|
||||||
|
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||||
|
{[debug: true], [], []}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Here are some examples of working with different types and modifiers:
|
||||||
|
|
||||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||||
{[unlock: true], ["path/to/file"], []}
|
{[unlock: true], ["path/to/file"], []}
|
||||||
@@ -121,21 +152,6 @@ defmodule OptionParser do
|
|||||||
iex> OptionParser.parse(["--unlock", "path/to/file", "--unlock", "path/to/another/file"], strict: [unlock: :keep])
|
iex> OptionParser.parse(["--unlock", "path/to/file", "--unlock", "path/to/another/file"], strict: [unlock: :keep])
|
||||||
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
|
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
|
||||||
|
|
||||||
### Negation switches
|
|
||||||
|
|
||||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
|
||||||
which will set the option to `false`:
|
|
||||||
|
|
||||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
|
||||||
{[op: false], ["path/to/file"], []}
|
|
||||||
|
|
||||||
## Aliases
|
|
||||||
|
|
||||||
A set of aliases can be given as options too:
|
|
||||||
|
|
||||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
|
||||||
{[debug: true], [], []}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
@@ -146,14 +162,16 @@ defmodule OptionParser do
|
|||||||
The same as `parse/2` but raises an `OptionParser.ParseError`
|
The same as `parse/2` but raises an `OptionParser.ParseError`
|
||||||
exception if any invalid options are given.
|
exception if any invalid options are given.
|
||||||
|
|
||||||
If there weren't any errors, returns a three-element tuple as follows:
|
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||||
|
|
||||||
1. parsed options,
|
* `parsed` is the list of parsed switches (same as in `parse/2`)
|
||||||
2. remaining arguments,
|
* `rest` is the list of arguments (same as in `parse/2`)
|
||||||
3. empty list.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
iex> OptionParser.parse!(["--debug", "path/to/file"], strict: [debug: :boolean])
|
||||||
|
{[debug: true], ["path/to/file"]}
|
||||||
|
|
||||||
iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer])
|
iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer])
|
||||||
** (OptionParser.ParseError) 1 error found!
|
** (OptionParser.ParseError) 1 error found!
|
||||||
--limit : Expected type integer, got "xyz"
|
--limit : Expected type integer, got "xyz"
|
||||||
@@ -172,7 +190,7 @@ defmodule OptionParser do
|
|||||||
@spec parse!(argv, options) :: {parsed, argv} | no_return
|
@spec parse!(argv, options) :: {parsed, argv} | no_return
|
||||||
def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
case parse(argv, opts) do
|
case parse(argv, opts) do
|
||||||
{parsed, argv, []} -> {parsed, argv}
|
{parsed, args, []} -> {parsed, args}
|
||||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -201,14 +219,16 @@ defmodule OptionParser do
|
|||||||
The same as `parse_head/2` but raises an `OptionParser.ParseError`
|
The same as `parse_head/2` but raises an `OptionParser.ParseError`
|
||||||
exception if any invalid options are given.
|
exception if any invalid options are given.
|
||||||
|
|
||||||
If there weren't any errors, returns a three-element tuple as follows:
|
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||||
|
|
||||||
1. parsed options,
|
* `parsed` is the list of parsed switches (same as in `parse_head/2`)
|
||||||
2. remaining arguments,
|
* `rest` is the list of arguments (same as in `parse_head/2`)
|
||||||
3. empty list.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"])
|
||||||
|
{[source: "lib"], ["path/to/file", "--verbose"]}
|
||||||
|
|
||||||
iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer])
|
iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer])
|
||||||
** (OptionParser.ParseError) 1 error found!
|
** (OptionParser.ParseError) 1 error found!
|
||||||
--number : Expected type integer, got "lib"
|
--number : Expected type integer, got "lib"
|
||||||
@@ -219,10 +239,10 @@ defmodule OptionParser do
|
|||||||
--verbose : Missing argument of type integer
|
--verbose : Missing argument of type integer
|
||||||
--source : Expected type integer, got "lib"
|
--source : Expected type integer, got "lib"
|
||||||
"""
|
"""
|
||||||
@spec parse_head!(argv, options) :: {parsed, argv, errors} | no_return
|
@spec parse_head!(argv, options) :: {parsed, argv} | no_return
|
||||||
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
case parse_head(argv, opts) do
|
case parse_head(argv, opts) do
|
||||||
{parsed, argv, []} -> {parsed, argv}
|
{parsed, args, []} -> {parsed, args}
|
||||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -250,7 +270,7 @@ defmodule OptionParser do
|
|||||||
{:error, ["--" | rest]} ->
|
{:error, ["--" | rest]} ->
|
||||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||||
|
|
||||||
{:error, [arg | rest]=remaining_args} ->
|
{:error, [arg | rest] = remaining_args} ->
|
||||||
# there is no option
|
# there is no option
|
||||||
if all? do
|
if all? do
|
||||||
do_parse(rest, config, opts, [arg | args], invalid, all?)
|
do_parse(rest, config, opts, [arg | args], invalid, all?)
|
||||||
@@ -264,20 +284,20 @@ defmodule OptionParser do
|
|||||||
Low-level function that parses one option.
|
Low-level function that parses one option.
|
||||||
|
|
||||||
It accepts the same options as `parse/2` and `parse_head/2`
|
It accepts the same options as `parse/2` and `parse_head/2`
|
||||||
as both functions are built on top of next. This function
|
as both functions are built on top of this function. This function
|
||||||
may return:
|
may return:
|
||||||
|
|
||||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||||
successfully parsed
|
successfully parsed
|
||||||
|
|
||||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||||
(returned when the switch type does not match the one given via the
|
(returned when the value cannot be parsed according to the switch type)
|
||||||
command line)
|
|
||||||
|
|
||||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||||
(returned in strict mode when the switch is unknown)
|
(returned in strict mode when the switch is unknown)
|
||||||
|
|
||||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@spec next(argv, options) ::
|
@spec next(argv, options) ::
|
||||||
@@ -295,15 +315,15 @@ defmodule OptionParser do
|
|||||||
{:error, []}
|
{:error, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp next(["--" | _]=argv, _aliases, _switches, _strict) do
|
defp next(["--" | _] = argv, _aliases, _switches, _strict) do
|
||||||
{:error, argv}
|
{:error, argv}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp next(["-" | _]=argv, _aliases, _switches, _strict) do
|
defp next(["-" | _] = argv, _aliases, _switches, _strict) do
|
||||||
{:error, argv}
|
{:error, argv}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp next(["- " <> _ | _]=argv, _aliases, _switches, _strict) do
|
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict) do
|
||||||
{:error, argv}
|
{:error, argv}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -334,8 +354,9 @@ defmodule OptionParser do
|
|||||||
@doc """
|
@doc """
|
||||||
Receives a key-value enumerable and converts it to argv.
|
Receives a key-value enumerable and converts it to argv.
|
||||||
|
|
||||||
Keys must be atoms. Keys with nil value are discarded,
|
Keys must be atoms. Keys with `nil` value are discarded,
|
||||||
boolean values are converted to `--key` or `--no-key`
|
boolean values are converted to `--key` or `--no-key`
|
||||||
|
(if the value is `true` or `false`, respectively),
|
||||||
and all other values are converted using `to_string/1`.
|
and all other values are converted using `to_string/1`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -364,6 +385,9 @@ defmodule OptionParser do
|
|||||||
@doc ~S"""
|
@doc ~S"""
|
||||||
Splits a string into argv chunks.
|
Splits a string into argv chunks.
|
||||||
|
|
||||||
|
This function splits the given `string` into a list of strings in a similar
|
||||||
|
way to many shells.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> OptionParser.split("foo bar")
|
iex> OptionParser.split("foo bar")
|
||||||
@@ -371,6 +395,7 @@ defmodule OptionParser do
|
|||||||
|
|
||||||
iex> OptionParser.split("foo \"bar baz\"")
|
iex> OptionParser.split("foo \"bar baz\"")
|
||||||
["foo", "bar baz"]
|
["foo", "bar baz"]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec split(String.t) :: argv
|
@spec split(String.t) :: argv
|
||||||
def split(string) do
|
def split(string) do
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ defmodule String do
|
|||||||
defp parts_to_index(:infinity), do: 0
|
defp parts_to_index(:infinity), do: 0
|
||||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||||
|
|
||||||
|
defp split_each("", _pattern, true, 1), do: []
|
||||||
defp split_each(string, _pattern, _trim, 1) when is_binary(string), do: [string]
|
defp split_each(string, _pattern, _trim, 1) when is_binary(string), do: [string]
|
||||||
defp split_each(string, pattern, trim, count) do
|
defp split_each(string, pattern, trim, count) do
|
||||||
case do_splitter(string, pattern, trim) do
|
case do_splitter(string, pattern, trim) do
|
||||||
@@ -402,6 +403,7 @@ defmodule String do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_compile_pattern(""), do: ""
|
defp maybe_compile_pattern(""), do: ""
|
||||||
|
defp maybe_compile_pattern(pattern) when is_tuple(pattern), do: pattern
|
||||||
defp maybe_compile_pattern(pattern), do: :binary.compile_pattern(pattern)
|
defp maybe_compile_pattern(pattern), do: :binary.compile_pattern(pattern)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ defmodule System do
|
|||||||
next = System.monotonic_time()
|
next = System.monotonic_time()
|
||||||
diff = next - prev
|
diff = next - prev
|
||||||
|
|
||||||
|
|
||||||
Generally speaking, the VM provides three time measurements:
|
Generally speaking, the VM provides three time measurements:
|
||||||
|
|
||||||
* `os_time/0` - the time reported by the OS. This time may be
|
* `os_time/0` - the time reported by the OS. This time may be
|
||||||
@@ -113,7 +112,15 @@ defmodule System do
|
|||||||
# Tries to run "git rev-parse --short HEAD". In the case of success returns
|
# Tries to run "git rev-parse --short HEAD". In the case of success returns
|
||||||
# the short revision hash. If that fails, returns an empty string.
|
# the short revision hash. If that fails, returns an empty string.
|
||||||
defmacrop get_revision do
|
defmacrop get_revision do
|
||||||
:os.cmd('git rev-parse --short HEAD 2> /dev/null')
|
null =
|
||||||
|
case :os.type do
|
||||||
|
{:win32, _} -> 'NUL'
|
||||||
|
_ -> '/dev/null'
|
||||||
|
end
|
||||||
|
|
||||||
|
'git rev-parse --short HEAD 2> '
|
||||||
|
|> Kernel.++(null)
|
||||||
|
|> :os.cmd()
|
||||||
|> strip
|
|> strip
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -157,8 +164,7 @@ defmodule System do
|
|||||||
%{build: build,
|
%{build: build,
|
||||||
date: get_date,
|
date: get_date,
|
||||||
revision: revision,
|
revision: revision,
|
||||||
version: version,
|
version: version}
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a string of the build info
|
# Returns a string of the build info
|
||||||
@@ -730,6 +736,30 @@ defmodule System do
|
|||||||
:os.system_time(normalize_time_unit(unit))
|
:os.system_time(normalize_time_unit(unit))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the OTP release number.
|
||||||
|
"""
|
||||||
|
@spec otp_release :: String.t
|
||||||
|
def otp_release do
|
||||||
|
:erlang.list_to_binary :erlang.system_info(:otp_release)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the number of schedulers in the VM.
|
||||||
|
"""
|
||||||
|
@spec schedulers :: pos_integer
|
||||||
|
def schedulers do
|
||||||
|
:erlang.system_info(:schedulers)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the number of schedulers online in the VM.
|
||||||
|
"""
|
||||||
|
@spec schedulers_online :: pos_integer
|
||||||
|
def schedulers_online do
|
||||||
|
:erlang.system_info(:schedulers_online)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Generates and returns an integer that is unique in the current runtime
|
Generates and returns an integer that is unique in the current runtime
|
||||||
instance.
|
instance.
|
||||||
|
|||||||
+21
-20
@@ -1,23 +1,24 @@
|
|||||||
{erl_opts, [
|
{erl_opts, [
|
||||||
warn_unused_vars,
|
warn_unused_vars,
|
||||||
warn_export_all,
|
warn_export_all,
|
||||||
warn_shadow_vars,
|
warn_shadow_vars,
|
||||||
warn_unused_import,
|
warn_unused_import,
|
||||||
warn_unused_function,
|
warn_unused_function,
|
||||||
warn_bif_clash,
|
warn_bif_clash,
|
||||||
warn_unused_record,
|
warn_unused_record,
|
||||||
warn_deprecated_function,
|
warn_deprecated_function,
|
||||||
warn_obsolete_guard,
|
warn_obsolete_guard,
|
||||||
strict_validation,
|
strict_validation,
|
||||||
warn_exported_vars,
|
warn_exported_vars,
|
||||||
%% warn_export_vars,
|
%% warn_export_vars,
|
||||||
%% warn_missing_spec,
|
%% warn_missing_spec,
|
||||||
%% warn_untyped_record,
|
%% warn_untyped_record,
|
||||||
%% warnings_as_errors,
|
%% warnings_as_errors,
|
||||||
debug_info
|
debug_info,
|
||||||
]}.
|
{platform_define, "^18.*", old_map_specs}
|
||||||
|
]}.
|
||||||
|
|
||||||
{yrl_opts, [
|
{yrl_opts, [
|
||||||
{report, true},
|
{report, true},
|
||||||
{verbose, false}
|
{verbose, false}
|
||||||
]}.
|
]}.
|
||||||
|
|||||||
@@ -14,11 +14,16 @@
|
|||||||
-export_type([charlist/0, char_list/0, struct/0, as_boolean/1, keyword/0, keyword/1]).
|
-export_type([charlist/0, char_list/0, struct/0, as_boolean/1, keyword/0, keyword/1]).
|
||||||
-type charlist() :: string().
|
-type charlist() :: string().
|
||||||
-type char_list() :: string().
|
-type char_list() :: string().
|
||||||
-type struct() :: #{'__struct__' => atom()}.
|
|
||||||
-type as_boolean(T) :: T.
|
-type as_boolean(T) :: T.
|
||||||
-type keyword() :: [{atom(), any()}].
|
-type keyword() :: [{atom(), any()}].
|
||||||
-type keyword(T) :: [{atom(), T}].
|
-type keyword(T) :: [{atom(), T}].
|
||||||
|
|
||||||
|
-ifdef(old_map_specs).
|
||||||
|
-type struct() :: #{'__struct__' => atom(), atom() => any()}.
|
||||||
|
-else.
|
||||||
|
-type struct() :: #{'__struct__' := atom(), atom() => any()}.
|
||||||
|
-endif.
|
||||||
|
|
||||||
%% OTP Application API
|
%% OTP Application API
|
||||||
|
|
||||||
-export([start/2, stop/1, config_change/3]).
|
-export([start/2, stop/1, config_change/3]).
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
-module(elixir_counter).
|
|
||||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
|
||||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
-define(timeout, 30000). %% 30 seconds
|
|
||||||
-define(limit, 4294967295). %% 2^32 - 1
|
|
||||||
|
|
||||||
next() ->
|
|
||||||
gen_server:call(?MODULE, next, ?timeout).
|
|
||||||
|
|
||||||
start_link() ->
|
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
|
||||||
|
|
||||||
init(Counter) ->
|
|
||||||
{ok, Counter}.
|
|
||||||
|
|
||||||
handle_call(next, _From, Counter) ->
|
|
||||||
{reply, Counter, bump(Counter)};
|
|
||||||
handle_call(Request, _From, Counter) ->
|
|
||||||
{stop, {badcall, Request}, Counter}.
|
|
||||||
|
|
||||||
handle_cast(Request, Counter) ->
|
|
||||||
{stop, {badcast, Request}, Counter}.
|
|
||||||
|
|
||||||
handle_info(_Request, Counter) ->
|
|
||||||
{noreply, Counter}.
|
|
||||||
|
|
||||||
terminate(_Reason, _Counter) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
code_change(_Old, Counter, _Extra) ->
|
|
||||||
{ok, Counter}.
|
|
||||||
|
|
||||||
bump(Counter) when Counter < ?limit ->
|
|
||||||
Counter + 1;
|
|
||||||
bump(_Counter) ->
|
|
||||||
0.
|
|
||||||
@@ -105,8 +105,6 @@ store_definition(Meta, Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepL
|
|||||||
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
||||||
[store_each(false, Kind, File, Location, Module, 0,
|
[store_each(false, Kind, File, Location, Module, 0,
|
||||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||||
|
|
||||||
make_struct_available(Kind, Module, Name, Args),
|
|
||||||
{Name, Arity}.
|
{Name, Arity}.
|
||||||
|
|
||||||
%% @on_definition
|
%% @on_definition
|
||||||
@@ -117,16 +115,6 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
|||||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
make_struct_available(def, Module, '__struct__', []) ->
|
|
||||||
case erlang:get(elixir_compiler_pid) of
|
|
||||||
undefined -> ok;
|
|
||||||
Pid ->
|
|
||||||
Pid ! {struct_available, Module},
|
|
||||||
ok
|
|
||||||
end;
|
|
||||||
make_struct_available(_, _, _, _) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%% Retrieve location from meta file (if Key == keep)
|
%% Retrieve location from meta file (if Key == keep)
|
||||||
%% or @file, otherwise nil
|
%% or @file, otherwise nil
|
||||||
|
|
||||||
@@ -218,7 +206,7 @@ is_macro(_) -> false.
|
|||||||
unwrap_definitions(File, Module) ->
|
unwrap_definitions(File, Module) ->
|
||||||
Table = elixir_module:defs_table(Module),
|
Table = elixir_module:defs_table(Module),
|
||||||
Entries = ets:match(Table, {{def, '$1'}, '_', '_', '_', '_', '_', '_'}),
|
Entries = ets:match(Table, {{def, '$1'}, '_', '_', '_', '_', '_', '_'}),
|
||||||
{All, Private} = unwrap_definition(Entries, File, Module, Table, [], []),
|
{All, Private} = unwrap_definition(lists:sort(Entries), File, Module, Table, [], []),
|
||||||
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
||||||
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
|
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ import_function(Meta, Name, Arity, E) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
Required = is_element(Receiver, ?m(E, requires)),
|
||||||
|
case is_element({Name, Arity}, get_macros(Receiver, Required)) of
|
||||||
true -> false;
|
true -> false;
|
||||||
false ->
|
false ->
|
||||||
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
||||||
@@ -146,7 +147,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
|||||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||||
{import, Receiver} ->
|
{import, Receiver} ->
|
||||||
case expand_require([{require, false} | Meta], Receiver, Tuple, Args, E) of
|
case expand_require([{required, true} | Meta], Receiver, Tuple, Args, E) of
|
||||||
{ok, _, _} = Response -> Response;
|
{ok, _, _} = Response -> Response;
|
||||||
error -> {ok, Receiver, Name, Args}
|
error -> {ok, Receiver, Name, Args}
|
||||||
end;
|
end;
|
||||||
@@ -161,19 +162,15 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
|||||||
|
|
||||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||||
Module = ?m(E, module),
|
Required = (Receiver == ?m(E, module)) orelse is_element(Receiver, ?m(E, requires)) orelse required(Meta),
|
||||||
|
|
||||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
case is_element(Tuple, get_macros(Receiver, Required)) of
|
||||||
|
true when Required ->
|
||||||
|
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||||
|
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||||
true ->
|
true ->
|
||||||
Requires = ?m(E, requires),
|
Info = {unrequired_module, {Receiver, Name, length(Args), ?m(E, requires)}},
|
||||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info);
|
||||||
true ->
|
|
||||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
|
||||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
|
||||||
false ->
|
|
||||||
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
|
|
||||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
|
|
||||||
end;
|
|
||||||
false ->
|
false ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
@@ -202,7 +199,7 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
|
|||||||
|
|
||||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||||
Line = ?line(Meta),
|
Line = ?line(Meta),
|
||||||
Next = elixir_counter:next(),
|
Next = erlang:unique_integer(),
|
||||||
|
|
||||||
try
|
try
|
||||||
elixir_exp:expand(
|
elixir_exp:expand(
|
||||||
@@ -220,8 +217,8 @@ caller(Line, E) ->
|
|||||||
|
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
|
||||||
skip_require(Meta) ->
|
required(Meta) ->
|
||||||
lists:keyfind(require, 1, Meta) == {require, false}.
|
lists:keyfind(required, 1, Meta) == {required, true}.
|
||||||
|
|
||||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||||
case is_import(Meta) of
|
case is_import(Meta) of
|
||||||
@@ -289,9 +286,20 @@ format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
|
|||||||
%% INTROSPECTION
|
%% INTROSPECTION
|
||||||
|
|
||||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||||
get_optional_macros(erlang) -> [];
|
get_macros(erlang, _) -> [];
|
||||||
|
|
||||||
get_optional_macros(Receiver) ->
|
get_macros(Receiver, false) ->
|
||||||
|
case code:is_loaded(Receiver) of
|
||||||
|
{file, _} ->
|
||||||
|
try
|
||||||
|
Receiver:'__info__'(macros)
|
||||||
|
catch
|
||||||
|
error:undef -> []
|
||||||
|
end;
|
||||||
|
false -> []
|
||||||
|
end;
|
||||||
|
|
||||||
|
get_macros(Receiver, true) ->
|
||||||
case code:ensure_loaded(Receiver) of
|
case code:ensure_loaded(Receiver) of
|
||||||
{module, Receiver} ->
|
{module, Receiver} ->
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ translate(Meta, Clauses, S) ->
|
|||||||
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||||
{TClause, TS } = elixir_clauses:clause(CMeta, fun translate_fn_match/2,
|
{TClause, TS } = elixir_clauses:clause(CMeta, fun translate_fn_match/2,
|
||||||
Args, Expr, Guards, Acc),
|
Args, Expr, Guards, Acc),
|
||||||
{TClause, elixir_scope:mergef(S, TS)}
|
{TClause, elixir_scope:mergec(S, TS)}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
{TClauses, NS} = lists:mapfoldl(Transformer, S, Clauses),
|
{TClauses, NS} = lists:mapfoldl(Transformer, S, Clauses),
|
||||||
@@ -111,7 +111,7 @@ handle_capture(false, Meta, Expr, E, Sequential) ->
|
|||||||
do_capture(Meta, Expr, E, Sequential).
|
do_capture(Meta, Expr, E, Sequential).
|
||||||
|
|
||||||
do_capture(Meta, Expr, E, Sequential) ->
|
do_capture(Meta, Expr, E, Sequential) ->
|
||||||
case do_escape(Expr, elixir_counter:next(), E, []) of
|
case do_escape(Expr, erlang:unique_integer(), E, []) of
|
||||||
{_, []} when not Sequential ->
|
{_, []} when not Sequential ->
|
||||||
invalid_capture(Meta, Expr, E);
|
invalid_capture(Meta, Expr, E);
|
||||||
{EExpr, EDict} ->
|
{EExpr, EDict} ->
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ local_for(Module, Name, Arity, Given) ->
|
|||||||
{Kind, Ann, [_ | _] = Clauses} when Given == nil; Kind == Given ->
|
{Kind, Ann, [_ | _] = Clauses} when Given == nil; Kind == Given ->
|
||||||
get_function(Ann, Module, Clauses);
|
get_function(Ann, Module, Clauses);
|
||||||
_ ->
|
_ ->
|
||||||
[_ | T] = erlang:get_stacktrace(),
|
{current_stacktrace, [_ | T]} = erlang:process_info(self(), current_stacktrace),
|
||||||
erlang:raise(error, undef, [{Module, Name, Arity, []} | T])
|
erlang:raise(error, undef, [{Module, Name, Arity, []} | T])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
|
|||||||
true when is_atom(ELeft) ->
|
true when is_atom(ELeft) ->
|
||||||
%% We always record structs when they are expanded
|
%% We always record structs when they are expanded
|
||||||
%% as they expect the reference at compile time.
|
%% as they expect the reference at compile time.
|
||||||
elixir_lexical:record_remote(ELeft, nil, ?m(E, lexical_tracker));
|
elixir_lexical:record_remote(ELeft, '__struct__', 1, nil, ?line(Meta), ?m(E, lexical_tracker));
|
||||||
true ->
|
true ->
|
||||||
ok;
|
ok;
|
||||||
false when Context == match ->
|
false when Context == match ->
|
||||||
@@ -31,7 +31,9 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
EMeta =
|
EMeta =
|
||||||
case lists:member(ELeft, ?m(E, context_modules)) of
|
%% We also include the current module because it won't be present
|
||||||
|
%% in context module in case the module name is defined dynamically.
|
||||||
|
case lists:member(ELeft, [?m(E, module) | ?m(E, context_modules)]) of
|
||||||
true -> [{struct, context} | Meta];
|
true -> [{struct, context} | Meta];
|
||||||
false -> Meta
|
false -> Meta
|
||||||
end,
|
end,
|
||||||
@@ -90,9 +92,9 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
|
|||||||
|
|
||||||
{TMap, TS} = translate_map(MapMeta, Assocs, Var, VS),
|
{TMap, TS} = translate_map(MapMeta, Assocs, Var, VS),
|
||||||
|
|
||||||
{{'case', Ann, TUpdate, [
|
{{'case', ?generated, TUpdate, [
|
||||||
{clause, Ann, [Match], [], [TMap]},
|
{clause, Ann, [Match], [], [TMap]},
|
||||||
{clause, Ann, [Var], [], [elixir_utils:erl_call(Ann, erlang, error, [Error])]}
|
{clause, ?generated, [Var], [], [elixir_utils:erl_call(Ann, erlang, error, [Error])]}
|
||||||
]}, TS};
|
]}, TS};
|
||||||
match ->
|
match ->
|
||||||
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
|
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ load_form(Line, Data, Forms, Opts, E) ->
|
|||||||
ok;
|
ok;
|
||||||
PID ->
|
PID ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
PID ! {module_available, self(), Ref, ?m(E, file), Module, Binary},
|
PID ! {module_available, self(), Ref, get(elixir_compiler_file), Module, Binary},
|
||||||
receive {Ref, ack} -> ok end
|
receive {Ref, ack} -> ok end
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
@@ -525,7 +525,7 @@ add_beam_chunk(Bin, Id, ChunkData)
|
|||||||
%% the callback can't be expanded, invokes the given
|
%% the callback can't be expanded, invokes the given
|
||||||
%% fun passing a possibly expanded AM:AF(Args).
|
%% fun passing a possibly expanded AM:AF(Args).
|
||||||
expand_callback(Line, M, F, Args, E, Fun) ->
|
expand_callback(Line, M, F, Args, E, Fun) ->
|
||||||
Meta = [{line, Line}, {require, false}],
|
Meta = [{line, Line}, {required, true}],
|
||||||
|
|
||||||
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
||||||
Fun(AM, AF, AA),
|
Fun(AM, AF, AA),
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
-module(elixir_scope).
|
-module(elixir_scope).
|
||||||
-export([translate_var/4, build_var/2, context_info/1,
|
-export([translate_var/4, build_var/2, context_info/1,
|
||||||
load_binding/2, dump_binding/2,
|
load_binding/2, dump_binding/2,
|
||||||
mergev/2, mergec/2, mergef/2,
|
mergev/2, mergec/2, merge_vars/2, merge_opt_vars/2,
|
||||||
merge_vars/2, merge_opt_vars/2,
|
|
||||||
warn_unsafe_var/4, warn_underscored_var_access/3, format_error/1
|
warn_unsafe_var/4, warn_underscored_var_access/3, format_error/1
|
||||||
]).
|
]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
@@ -128,14 +127,6 @@ mergec(S1, S2) ->
|
|||||||
caller=S2#elixir_scope.caller
|
caller=S2#elixir_scope.caller
|
||||||
}.
|
}.
|
||||||
|
|
||||||
%% Similar to mergec but does not merge the user vars counter.
|
|
||||||
|
|
||||||
mergef(S1, S2) ->
|
|
||||||
S1#elixir_scope{
|
|
||||||
super=S2#elixir_scope.super,
|
|
||||||
caller=S2#elixir_scope.caller
|
|
||||||
}.
|
|
||||||
|
|
||||||
%% Mergers.
|
%% Mergers.
|
||||||
|
|
||||||
merge_vars(V, V) -> V;
|
merge_vars(V, V) -> V;
|
||||||
@@ -212,8 +203,8 @@ format_error({unused_match, Name, Kind}) ->
|
|||||||
|
|
||||||
format_error({unsafe_var, Name}) ->
|
format_error({unsafe_var, Name}) ->
|
||||||
io_lib:format("the variable \"~ts\" is unsafe as it has been set inside "
|
io_lib:format("the variable \"~ts\" is unsafe as it has been set inside "
|
||||||
"a case/cond/receive/if/&&/||. Please explicitly return the "
|
"a case/cond/receive/if/&&/||. Please explicitly return "
|
||||||
"variable value instead. For example:\n\n"
|
"the variable value instead. For example:\n\n"
|
||||||
" case int do\n"
|
" case int do\n"
|
||||||
" 1 -> atom = :one\n"
|
" 1 -> atom = :one\n"
|
||||||
" 2 -> atom = :two\n"
|
" 2 -> atom = :two\n"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ init(ok) ->
|
|||||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||||
worker, % Type = worker | supervisor
|
worker, % Type = worker | supervisor
|
||||||
[elixir_config] % Modules = [Module] | dynamic
|
[elixir_config] % Modules = [Module] | dynamic
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
elixir_code_server,
|
elixir_code_server,
|
||||||
@@ -25,17 +25,7 @@ init(ok) ->
|
|||||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||||
worker, % Type = worker | supervisor
|
worker, % Type = worker | supervisor
|
||||||
[elixir_code_server] % Modules = [Module] | dynamic
|
[elixir_code_server] % Modules = [Module] | dynamic
|
||||||
},
|
}
|
||||||
|
|
||||||
{
|
|
||||||
elixir_counter,
|
|
||||||
{elixir_counter, start_link, []},
|
|
||||||
|
|
||||||
permanent, % Restart = permanent | transient | temporary
|
|
||||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
|
||||||
worker, % Type = worker | supervisor
|
|
||||||
[elixir_counter] % Modules = [Module] | dynamic
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
|
||||||
{ok, {{one_for_one, 3, 10}, Workers}}.
|
{ok, {{one_for_one, 3, 10}, Workers}}.
|
||||||
|
|||||||
@@ -135,15 +135,15 @@ tokenize([], EndLine, _Column, #elixir_tokenizer{terminators=[{Start, {StartLine
|
|||||||
% Base integers
|
% Base integers
|
||||||
|
|
||||||
tokenize([$0, $x, H | T], Line, Column, Scope, Tokens) when ?is_hex(H) ->
|
tokenize([$0, $x, H | T], Line, Column, Scope, Tokens) when ?is_hex(H) ->
|
||||||
{Rest, Number, Length} = tokenize_hex([H | T], []),
|
{Rest, Number, Length} = tokenize_hex(T, [H], 1),
|
||||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||||
|
|
||||||
tokenize([$0, $b, H | T], Line, Column, Scope, Tokens) when ?is_bin(H) ->
|
tokenize([$0, $b, H | T], Line, Column, Scope, Tokens) when ?is_bin(H) ->
|
||||||
{Rest, Number, Length} = tokenize_bin([H | T], []),
|
{Rest, Number, Length} = tokenize_bin(T, [H], 1),
|
||||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||||
|
|
||||||
tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
|
tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
|
||||||
{Rest, Number, Length} = tokenize_octal([H | T], []),
|
{Rest, Number, Length} = tokenize_octal(T, [H], 1),
|
||||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||||
|
|
||||||
% Comments
|
% Comments
|
||||||
@@ -413,8 +413,8 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
|
|||||||
|
|
||||||
% Integers and floats
|
% Integers and floats
|
||||||
|
|
||||||
tokenize([H | _] = String, Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
tokenize([H | T], Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||||
{Rest, Number, Length} = tokenize_number(String, [], false),
|
{Rest, Number, Length} = tokenize_number(T, [H], 1, false),
|
||||||
tokenize(Rest, Line, Column + Length, Scope, [{number, {Line, Column, Column + Length}, Number} | Tokens]);
|
tokenize(Rest, Line, Column + Length, Scope, [{number, {Line, Column, Column + Length}, Number} | Tokens]);
|
||||||
|
|
||||||
% Identifiers (including aliases)
|
% Identifiers (including aliases)
|
||||||
@@ -733,46 +733,55 @@ extract_heredoc_line(Marker, Rest, Buffer, _Counter) ->
|
|||||||
%% At this point, we are at least sure the first digit is a number.
|
%% At this point, we are at least sure the first digit is a number.
|
||||||
|
|
||||||
%% Check if we have a point followed by a number;
|
%% Check if we have a point followed by a number;
|
||||||
tokenize_number([$., H | T], Acc, false) when ?is_digit(H) ->
|
tokenize_number([$., H | T], Acc, Length, false) when ?is_digit(H) ->
|
||||||
tokenize_number(T, [H, $. | Acc], true);
|
tokenize_number(T, [H, $. | Acc], Length + 2, true);
|
||||||
|
|
||||||
%% Check if we have an underscore followed by a number;
|
%% Check if we have an underscore followed by a number;
|
||||||
tokenize_number([$_, H | T], Acc, Bool) when ?is_digit(H) ->
|
tokenize_number([$_, H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||||
tokenize_number(T, [H | Acc], Bool);
|
tokenize_number(T, [H | Acc], Length + 2, Bool);
|
||||||
|
|
||||||
%% Check if we have e- followed by numbers (valid only for floats);
|
%% Check if we have e- followed by numbers (valid only for floats);
|
||||||
tokenize_number([E, S, H | T], Acc, true)
|
tokenize_number([E, S, H | T], Acc, Length, true)
|
||||||
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
||||||
tokenize_number(T, [H, S, $e | Acc], true);
|
tokenize_number(T, [H, S, $e | Acc], Length + 3, true);
|
||||||
|
|
||||||
%% Check if we have e followed by numbers (valid only for floats);
|
%% Check if we have e followed by numbers (valid only for floats);
|
||||||
tokenize_number([E, H | T], Acc, true)
|
tokenize_number([E, H | T], Acc, Length, true)
|
||||||
when (E == $E) or (E == $e), ?is_digit(H) ->
|
when (E == $E) or (E == $e), ?is_digit(H) ->
|
||||||
tokenize_number(T, [H, $e | Acc], true);
|
tokenize_number(T, [H, $e | Acc], Length + 2, true);
|
||||||
|
|
||||||
%% Finally just numbers.
|
%% Finally just numbers.
|
||||||
tokenize_number([H | T], Acc, Bool) when ?is_digit(H) ->
|
tokenize_number([H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||||
tokenize_number(T, [H | Acc], Bool);
|
tokenize_number(T, [H | Acc], Length + 1, Bool);
|
||||||
|
|
||||||
%% Cast to float...
|
%% Cast to float...
|
||||||
tokenize_number(Rest, Acc, true) ->
|
tokenize_number(Rest, Acc, Length, true) ->
|
||||||
{Rest, list_to_float(lists:reverse(Acc)), length(Acc)};
|
{Rest, list_to_float(lists:reverse(Acc)), Length};
|
||||||
|
|
||||||
%% Or integer.
|
%% Or integer.
|
||||||
tokenize_number(Rest, Acc, false) ->
|
tokenize_number(Rest, Acc, Length, false) ->
|
||||||
{Rest, list_to_integer(lists:reverse(Acc)), length(Acc)}.
|
{Rest, list_to_integer(lists:reverse(Acc)), Length}.
|
||||||
|
|
||||||
tokenize_hex([H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
tokenize_hex([H | T], Acc, Length) when ?is_hex(H) ->
|
||||||
tokenize_hex([$_, H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
tokenize_hex(T, [H | Acc], Length + 1);
|
||||||
tokenize_hex(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 16), length(Acc)}.
|
tokenize_hex([$_, H | T], Acc, Length) when ?is_hex(H) ->
|
||||||
|
tokenize_hex(T, [H | Acc], Length + 2);
|
||||||
|
tokenize_hex(Rest, Acc, Length) ->
|
||||||
|
{Rest, list_to_integer(lists:reverse(Acc), 16), Length}.
|
||||||
|
|
||||||
tokenize_octal([H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
tokenize_octal([H | T], Acc, Length) when ?is_octal(H) ->
|
||||||
tokenize_octal([$_, H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
tokenize_octal(T, [H | Acc], Length + 1);
|
||||||
tokenize_octal(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 8), length(Acc)}.
|
tokenize_octal([$_, H | T], Acc, Length) when ?is_octal(H) ->
|
||||||
|
tokenize_octal(T, [H | Acc], Length + 2);
|
||||||
|
tokenize_octal(Rest, Acc, Length) ->
|
||||||
|
{Rest, list_to_integer(lists:reverse(Acc), 8), Length}.
|
||||||
|
|
||||||
tokenize_bin([H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
tokenize_bin([H | T], Acc, Length) when ?is_bin(H) ->
|
||||||
tokenize_bin([$_, H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
tokenize_bin(T, [H | Acc], Length + 1);
|
||||||
tokenize_bin(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 2), length(Acc)}.
|
tokenize_bin([$_, H | T], Acc, Length) when ?is_bin(H) ->
|
||||||
|
tokenize_bin(T, [H | Acc], Length + 2);
|
||||||
|
tokenize_bin(Rest, Acc, Length) ->
|
||||||
|
{Rest, list_to_integer(lists:reverse(Acc), 2), Length}.
|
||||||
|
|
||||||
%% Comments
|
%% Comments
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ expand(Meta, Args, E) ->
|
|||||||
|
|
||||||
{ECases, EC} = lists:mapfoldl(fun expand/2, E, Cases),
|
{ECases, EC} = lists:mapfoldl(fun expand/2, E, Cases),
|
||||||
{EDoExpr, _} = elixir_exp:expand(DoExpr, EC),
|
{EDoExpr, _} = elixir_exp:expand(DoExpr, EC),
|
||||||
{EElseExpr, _} = expand_else(ElseExpr, E),
|
{EElseExpr, _} = expand_else(Meta, ElseExpr, E),
|
||||||
{{with, Meta, ECases ++ [[{do, EDoExpr} | EElseExpr]]}, E}.
|
{{with, Meta, ECases ++ [[{do, EDoExpr} | EElseExpr]]}, E}.
|
||||||
|
|
||||||
expand({'<-', Meta, [Left, Right]}, E) ->
|
expand({'<-', Meta, [Left, Right]}, E) ->
|
||||||
@@ -55,25 +55,36 @@ expand({'<-', Meta, [Left, Right]}, E) ->
|
|||||||
expand(X, E) ->
|
expand(X, E) ->
|
||||||
elixir_exp:expand(X, E).
|
elixir_exp:expand(X, E).
|
||||||
|
|
||||||
expand_else(KV, E) when is_list(KV) ->
|
expand_else(_Meta, KV, E) when is_list(KV) ->
|
||||||
{[{do, EClauses}], EC} = elixir_exp_clauses:'case'([], [{do, KV}], E),
|
{[{do, EClauses}], EC} = elixir_exp_clauses:'case'([], [{do, KV}], E),
|
||||||
{[{else, EClauses}], EC};
|
{[{else, EClauses}], EC};
|
||||||
expand_else(nil, E) ->
|
expand_else(_Meta, nil, E) ->
|
||||||
{[], E}.
|
{[], E};
|
||||||
|
expand_else(Meta, _KV, E) ->
|
||||||
|
Message = "expected -> clauses for else in with",
|
||||||
|
elixir_errors:compile_error(Meta, ?m(E, file), Message, []).
|
||||||
|
|
||||||
%% Translation
|
%% Translation
|
||||||
|
|
||||||
translate(Meta, Args, S) ->
|
translate(Meta, Args, S) ->
|
||||||
{Parts, [{do, Expr} | ExprList]} = elixir_utils:split_last(Args),
|
{Parts, [{do, Expr} | ExprList]} = elixir_utils:split_last(Args),
|
||||||
CaseExpr =
|
case ExprList of
|
||||||
case ExprList of
|
[{else, ElseExpr}] ->
|
||||||
[{else, ElseExpr}] ->
|
{TCases, TS} = translate_case(Parts, {ok, Expr}, fun(X) -> {error, X} end, S),
|
||||||
build_else(Meta, build_case(Parts, {ok, Expr}, fun(X) -> {error, X} end), ElseExpr);
|
translate_else(Meta, TCases, ElseExpr, TS);
|
||||||
[] ->
|
[] ->
|
||||||
build_case(Parts, Expr, fun(X) -> X end)
|
translate_case(Parts, Expr, fun(X) -> X end, S)
|
||||||
end,
|
end.
|
||||||
{TC, TS} = elixir_translator:translate(CaseExpr, S#elixir_scope{extra=nil}),
|
|
||||||
{TC, elixir_scope:mergec(S, TS)}.
|
translate_case(Parts, DoExpr, Wrapper, S) ->
|
||||||
|
Cases = build_case(Parts, DoExpr, Wrapper),
|
||||||
|
{TCases, TS} = elixir_translator:translate(Cases, S#elixir_scope{extra=nil}),
|
||||||
|
{TCases, elixir_scope:mergec(S, TS)}.
|
||||||
|
|
||||||
|
translate_else(Meta, WithCases, ElseExpr, S) ->
|
||||||
|
ElseClauses = build_else(Meta, ElseExpr),
|
||||||
|
{TClauses, TS} = elixir_clauses:clauses(Meta, ElseClauses, S#elixir_scope{extra=nil}),
|
||||||
|
{{'case', ?ann(Meta), WithCases, TClauses}, elixir_scope:mergec(S, TS)}.
|
||||||
|
|
||||||
build_case([{'<-', Meta, [{Name, _, Ctx}, _] = Args} | Rest], DoExpr, Wrapper)
|
build_case([{'<-', Meta, [{Name, _, Ctx}, _] = Args} | Rest], DoExpr, Wrapper)
|
||||||
when is_atom(Name) andalso is_atom(Ctx) ->
|
when is_atom(Name) andalso is_atom(Ctx) ->
|
||||||
@@ -90,18 +101,20 @@ build_case([Expr | Rest], DoExpr, Wrapper) ->
|
|||||||
build_case([], DoExpr, _Wrapper) ->
|
build_case([], DoExpr, _Wrapper) ->
|
||||||
DoExpr.
|
DoExpr.
|
||||||
|
|
||||||
build_else(Meta, WithCases, ElseClauses) ->
|
build_else(Meta, ElseClauses) ->
|
||||||
Result = {result, Meta, ?MODULE},
|
Result = {result, Meta, ?MODULE},
|
||||||
Clauses = [
|
[{match, Meta, [{ok, Result}], Result} |
|
||||||
{'->', Meta, [[{ok, Result}], Result]}
|
each_clause_to_error_match(ElseClauses)] ++ [build_raise(Meta)].
|
||||||
| else_to_error_clause(ElseClauses)
|
|
||||||
] ++ [build_raise(Meta)],
|
|
||||||
{'case', Meta, [WithCases, [{do, Clauses}]]}.
|
|
||||||
|
|
||||||
else_to_error_clause(Clauses) ->
|
each_clause_to_error_match(Clauses) ->
|
||||||
[{'->', Meta, [[{error, Match}], Expr]} ||
|
[{match, Meta, [error_match_for_match(Match)], Expr} ||
|
||||||
{'->', Meta, [[Match], Expr]} <- Clauses].
|
{'->', Meta, [[Match], Expr]} <- Clauses].
|
||||||
|
|
||||||
|
error_match_for_match({'when', Meta, [Left, Right]}) ->
|
||||||
|
{'when', Meta, [{error, Left}, Right]};
|
||||||
|
error_match_for_match(Match) ->
|
||||||
|
{error, Match}.
|
||||||
|
|
||||||
build_raise(Meta) ->
|
build_raise(Meta) ->
|
||||||
Other = {raise, Meta, ?MODULE},
|
Other = {other, Meta, ?MODULE},
|
||||||
{'->', ?generated, [[{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}]}.
|
{match, ?generated, [{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}}.
|
||||||
|
|||||||
@@ -3,30 +3,33 @@ Code.require_file "test_helper.exs", __DIR__
|
|||||||
defmodule BehaviourTest do
|
defmodule BehaviourTest do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
defmodule Sample do
|
{_, _, sample_binary, _} =
|
||||||
use Behaviour
|
defmodule Sample do
|
||||||
|
use Behaviour
|
||||||
|
|
||||||
defcallback first(integer) :: integer
|
defcallback first(integer) :: integer
|
||||||
|
|
||||||
defcallback foo(atom(), binary) :: binary
|
defcallback foo(atom(), binary) :: binary
|
||||||
|
|
||||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||||
|
|
||||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||||
|
|
||||||
defcallback orr(atom | integer) :: atom
|
defcallback orr(atom | integer) :: atom
|
||||||
|
|
||||||
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
||||||
|
|
||||||
defmacrocallback last(integer) :: Macro.t
|
defmacrocallback last(integer) :: Macro.t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@sample_binary sample_binary
|
||||||
|
|
||||||
test "callbacks" do
|
test "callbacks" do
|
||||||
assert Sample.__behaviour__(:callbacks) == [first: 1, guarded: 1, "MACRO-last": 2, literal: 5, orr: 1, foo: 2, bar: 2]
|
assert Sample.__behaviour__(:callbacks) == [first: 1, guarded: 1, "MACRO-last": 2, literal: 5, orr: 1, foo: 2, bar: 2]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "specs" do
|
test "specs" do
|
||||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
|
assert length(Kernel.Typespec.beam_callbacks(@sample_binary)) == 7
|
||||||
end
|
end
|
||||||
|
|
||||||
test "default is not supported" do
|
test "default is not supported" do
|
||||||
|
|||||||
@@ -66,4 +66,25 @@ defmodule DateTimeTest do
|
|||||||
utc_offset: -12600, std_offset: 3600, time_zone: "Brazil/Manaus"}
|
utc_offset: -12600, std_offset: 3600, time_zone: "Brazil/Manaus"}
|
||||||
assert to_string(dt) == "2000-02-29 23:00:07-02:30 BRM Brazil/Manaus"
|
assert to_string(dt) == "2000-02-29 23:00:07-02:30 BRM Brazil/Manaus"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "from_unix/2 works with Unix times back to 0 Gregorian Seconds" do
|
||||||
|
assert DateTime.from_unix(-62167219200) == {:ok,
|
||||||
|
%DateTime{calendar: Calendar.ISO, day: 1, hour: 0, microsecond: {0, 0},
|
||||||
|
minute: 0, month: 1, second: 0, std_offset: 0, time_zone: "Etc/UTC",
|
||||||
|
utc_offset: 0, year: 0, zone_abbr: "UTC"}}
|
||||||
|
|
||||||
|
assert DateTime.from_unix(-62167219201) == :error
|
||||||
|
end
|
||||||
|
|
||||||
|
test "to_unix/2 works with Unix times back to 0 Gregorian Seconds" do
|
||||||
|
gregorian_0 = %DateTime{calendar: Calendar.ISO, day: 1, hour: 0, microsecond: {0, 0},
|
||||||
|
minute: 0, month: 1, second: 0, std_offset: 0, time_zone: "Etc/UTC",
|
||||||
|
utc_offset: 0, year: 0, zone_abbr: "UTC"}
|
||||||
|
before_gregorian_0 = %DateTime{gregorian_0 | year: -1}
|
||||||
|
|
||||||
|
assert DateTime.to_unix(gregorian_0) == -62167219200
|
||||||
|
assert_raise FunctionClauseError, fn ->
|
||||||
|
DateTime.to_unix(before_gregorian_0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ defmodule EnumTest do
|
|||||||
test "find_index/2" do
|
test "find_index/2" do
|
||||||
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||||
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||||
|
assert Stream.take(1..3, 3) |> Enum.find_index(fn _ -> false end) == nil
|
||||||
|
assert Stream.take(1..6, 6) |> Enum.find_index(fn x -> x == 5 end) == 4
|
||||||
end
|
end
|
||||||
|
|
||||||
test "find_value/2" do
|
test "find_value/2" do
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
defmodule Dialyzer.Macrocallback do
|
||||||
|
@macrocallback required(atom) :: Macro.t
|
||||||
|
@macrocallback optional(atom) :: Macro.t
|
||||||
|
@optional_callbacks [optional: 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Dialyzer.Macrocallback.Impl do
|
||||||
|
@behaviour Dialyzer.Macrocallback
|
||||||
|
defmacro required(var), do: Macro.expand(var, __CALLER__)
|
||||||
|
defmacro optional(var), do: Macro.expand(var, __CALLER__)
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
defmodule Dialyzer.StructUpdate do
|
||||||
|
defstruct [:foo]
|
||||||
|
|
||||||
|
def update(%__MODULE__{} = struct) do
|
||||||
|
%__MODULE__{struct | foo: :bar}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
defmodule Bar do
|
defmodule Bar do
|
||||||
defstruct name: ""
|
defstruct name: "", foo: %Foo{}
|
||||||
def foo?(%Foo{}), do: true
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
defmodule Undef do
|
||||||
|
def undef do
|
||||||
|
%__MODULE__{}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -16,8 +16,14 @@ defmodule Kernel.DialyzerTest do
|
|||||||
|> Path.join("base_plt")
|
|> Path.join("base_plt")
|
||||||
|> String.to_charlist()
|
|> String.to_charlist()
|
||||||
|
|
||||||
# Add a few key elixir modules for types
|
# Some OSs (like Windows) do not provide the HOME environment variable.
|
||||||
files = Enum.map([Kernel, String, Keyword, Exception], &:code.which/1)
|
unless System.get_env("HOME") do
|
||||||
|
System.put_env("HOME", System.user_home())
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add a few key elixir modules for types and macro functions
|
||||||
|
mods = [Kernel, String, Keyword, Exception, Macro, Macro.Env, :elixir_env]
|
||||||
|
files = Enum.map(mods, &:code.which/1)
|
||||||
:dialyzer.run([analysis_type: :plt_build, output_plt: plt,
|
:dialyzer.run([analysis_type: :plt_build, output_plt: plt,
|
||||||
apps: [:erts], files: files])
|
apps: [:erts], files: files])
|
||||||
|
|
||||||
@@ -48,6 +54,7 @@ defmodule Kernel.DialyzerTest do
|
|||||||
{:ok, [outdir: dir, dialyzer: dialyzer]}
|
{:ok, [outdir: dir, dialyzer: dialyzer]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag otp19: false
|
||||||
test "no warnings on valid remote calls", context do
|
test "no warnings on valid remote calls", context do
|
||||||
copy_beam! context, Dialyzer.RemoteCall
|
copy_beam! context, Dialyzer.RemoteCall
|
||||||
assert_dialyze_no_warnings! context
|
assert_dialyze_no_warnings! context
|
||||||
@@ -63,6 +70,17 @@ defmodule Kernel.DialyzerTest do
|
|||||||
assert_dialyze_no_warnings! context
|
assert_dialyze_no_warnings! context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "no warnings on macrocallback", context do
|
||||||
|
copy_beam! context, Dialyzer.Macrocallback
|
||||||
|
copy_beam! context, Dialyzer.Macrocallback.Impl
|
||||||
|
assert_dialyze_no_warnings! context
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no warnings on struct update", context do
|
||||||
|
copy_beam! context, Dialyzer.StructUpdate
|
||||||
|
assert_dialyze_no_warnings! context
|
||||||
|
end
|
||||||
|
|
||||||
defp copy_beam!(context, module) do
|
defp copy_beam!(context, module) do
|
||||||
name = "#{module}.beam"
|
name = "#{module}.beam"
|
||||||
File.cp! Path.join(context[:base_dir], name),
|
File.cp! Path.join(context[:base_dir], name),
|
||||||
|
|||||||
@@ -18,6 +18,18 @@ defmodule Kernel.FnTest do
|
|||||||
refute (fn ^x -> true; _ -> false end).(1.0)
|
refute (fn ^x -> true; _ -> false end).(1.0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "case function hoisting does not affect anonymous fns" do
|
||||||
|
assert :undefined =
|
||||||
|
(if is_a?(:atom, 0) do
|
||||||
|
user = :defined
|
||||||
|
else
|
||||||
|
(fn() ->
|
||||||
|
user = :undefined
|
||||||
|
user
|
||||||
|
end).()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
test "capture with access" do
|
test "capture with access" do
|
||||||
assert (&(&1[:hello])).([hello: :world]) == :world
|
assert (&(&1[:hello])).([hello: :world]) == :world
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ defmodule Kernel.LexicalTrackerTest do
|
|||||||
&R.func/0
|
&R.func/0
|
||||||
&Remote.func/0
|
&Remote.func/0
|
||||||
&Integer.is_even/1
|
&Integer.is_even/1
|
||||||
|
%Macro.Env{}
|
||||||
end
|
end
|
||||||
|
|
||||||
&extract/2
|
&extract/2
|
||||||
@@ -171,6 +172,8 @@ defmodule Kernel.LexicalTrackerTest do
|
|||||||
|
|
||||||
&is_record/1; def b(a), do: is_record(a)
|
&is_record/1; def b(a), do: is_record(a)
|
||||||
|
|
||||||
|
%Macro.Env{}
|
||||||
|
|
||||||
Kernel.LexicalTracker.remote_dispatches(__ENV__.module)
|
Kernel.LexicalTracker.remote_dispatches(__ENV__.module)
|
||||||
end |> elem(3)
|
end |> elem(3)
|
||||||
""")
|
""")
|
||||||
@@ -181,14 +184,16 @@ defmodule Kernel.LexicalTrackerTest do
|
|||||||
assert {9, Integer, :is_even, 1} in compile_remote_calls
|
assert {9, Integer, :is_even, 1} in compile_remote_calls
|
||||||
assert {15, Record, :is_record, 1} in compile_remote_calls
|
assert {15, Record, :is_record, 1} in compile_remote_calls
|
||||||
assert {18, Integer, :is_even, 1} in compile_remote_calls
|
assert {18, Integer, :is_even, 1} in compile_remote_calls
|
||||||
assert {21, Record, :extract, 2} in compile_remote_calls
|
assert {19, Macro.Env, :__struct__, 1} in compile_remote_calls
|
||||||
assert {22, Record, :is_record, 1} in compile_remote_calls
|
assert {22, Record, :extract, 2} in compile_remote_calls
|
||||||
assert {23, Remote, :func, 0} in compile_remote_calls
|
assert {23, Record, :is_record, 1} in compile_remote_calls
|
||||||
assert {24, Remote, :func, 0} in compile_remote_calls
|
assert {24, Remote, :func, 0} in compile_remote_calls
|
||||||
assert {25, Integer, :is_even, 1} in compile_remote_calls
|
assert {25, Remote, :func, 0} in compile_remote_calls
|
||||||
assert {27, Kernel, :def, 2} in compile_remote_calls
|
assert {26, Integer, :is_even, 1} in compile_remote_calls
|
||||||
assert {27, Record, :is_record, 1} in compile_remote_calls
|
assert {28, Kernel, :def, 2} in compile_remote_calls
|
||||||
assert {29, Kernel.LexicalTracker, :remote_dispatches, 1} in compile_remote_calls
|
assert {28, Record, :is_record, 1} in compile_remote_calls
|
||||||
|
assert {30, Macro.Env, :__struct__, 1} in compile_remote_calls
|
||||||
|
assert {32, Kernel.LexicalTracker, :remote_dispatches, 1} in compile_remote_calls
|
||||||
|
|
||||||
runtime_remote_calls = unroll_dispatches(runtime_remote_calls)
|
runtime_remote_calls = unroll_dispatches(runtime_remote_calls)
|
||||||
assert {7, Record, :extract, 2} in runtime_remote_calls
|
assert {7, Record, :extract, 2} in runtime_remote_calls
|
||||||
@@ -200,7 +205,7 @@ defmodule Kernel.LexicalTrackerTest do
|
|||||||
assert {16, Remote, :func, 0} in runtime_remote_calls
|
assert {16, Remote, :func, 0} in runtime_remote_calls
|
||||||
assert {17, Remote, :func, 0} in runtime_remote_calls
|
assert {17, Remote, :func, 0} in runtime_remote_calls
|
||||||
assert {18, :erlang, :==, 2} in runtime_remote_calls
|
assert {18, :erlang, :==, 2} in runtime_remote_calls
|
||||||
assert {27, :erlang, :is_tuple, 1} in runtime_remote_calls
|
assert {28, :erlang, :is_tuple, 1} in runtime_remote_calls
|
||||||
end
|
end
|
||||||
|
|
||||||
defp unroll_dispatches(dispatches) do
|
defp unroll_dispatches(dispatches) do
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ defmodule Kernel.MacrosTest do
|
|||||||
assert_raise UndefinedFunctionError, fn -> x.value end
|
assert_raise UndefinedFunctionError, fn -> x.value end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "bang do block" do
|
test "macros with bang and do block have proper precedence" do
|
||||||
import Kernel.MacrosTest.Nested
|
import Kernel.MacrosTest.Nested
|
||||||
assert (do_identity! do 1 end) == 1
|
assert (do_identity! do 1 end) == 1
|
||||||
assert (Kernel.MacrosTest.Nested.do_identity! do 1 end) == 1
|
assert (Kernel.MacrosTest.Nested.do_identity! do 1 end) == 1
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ defmodule Kernel.ParallelCompilerTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "emits struct undefined error when local struct is undefined" do
|
||||||
|
fixtures = [fixture_path("parallel_struct/undef.ex")]
|
||||||
|
assert capture_io(fn ->
|
||||||
|
assert catch_exit(Kernel.ParallelCompiler.files(fixtures)) == {:shutdown, 1}
|
||||||
|
end) =~ "Undef.__struct__/1 is undefined, cannot expand struct Undef"
|
||||||
|
end
|
||||||
|
|
||||||
test "does not hang on missing dependencies" do
|
test "does not hang on missing dependencies" do
|
||||||
fixtures = [fixture_path("parallel_compiler/bat.ex")]
|
fixtures = [fixture_path("parallel_compiler/bat.ex")]
|
||||||
assert capture_io(fn ->
|
assert capture_io(fn ->
|
||||||
|
|||||||
@@ -283,9 +283,10 @@ defmodule Kernel.RaiseTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "badfun error" do
|
test "badfun error" do
|
||||||
x = :example
|
# Avoid "invalid function call" warning in >= OTP 19
|
||||||
|
x = fn -> :example end
|
||||||
result = try do
|
result = try do
|
||||||
x.(2)
|
x.().(2)
|
||||||
rescue
|
rescue
|
||||||
x in [BadFunctionError] -> Exception.message(x)
|
x in [BadFunctionError] -> Exception.message(x)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -455,6 +455,27 @@ defmodule Kernel.TypespecTest do
|
|||||||
types(module)
|
types(module)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "@type with module attributes" do
|
||||||
|
module = test_module do
|
||||||
|
@keyword Keyword
|
||||||
|
@type kw :: @keyword.t
|
||||||
|
@type kw(value) :: @keyword.t(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert [type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, []]}, _},
|
||||||
|
type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]]}, [{:var, _, :value}]}] =
|
||||||
|
types(module)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "invalid remote @type with module attribute that does not evaluate to a module" do
|
||||||
|
assert_raise CompileError, ~r/\(@foo is "bar"\)/, fn ->
|
||||||
|
test_module do
|
||||||
|
@foo "bar"
|
||||||
|
@type t :: @foo.t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "defines_type?" do
|
test "defines_type?" do
|
||||||
test_module do
|
test_module do
|
||||||
@type mytype :: tuple
|
@type mytype :: tuple
|
||||||
|
|||||||
@@ -310,6 +310,20 @@ defmodule Kernel.WarningTest do
|
|||||||
purge Sample
|
purge Sample
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "unused alias when also import" do
|
||||||
|
assert capture_err(fn ->
|
||||||
|
Code.compile_string """
|
||||||
|
defmodule Sample do
|
||||||
|
alias :lists, as: List
|
||||||
|
import MapSet
|
||||||
|
new()
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end) =~ "unused alias List"
|
||||||
|
after
|
||||||
|
purge Sample
|
||||||
|
end
|
||||||
|
|
||||||
test "unused inside dynamic module" do
|
test "unused inside dynamic module" do
|
||||||
import List, only: [flatten: 1], warn: false
|
import List, only: [flatten: 1], warn: false
|
||||||
|
|
||||||
@@ -421,8 +435,8 @@ defmodule Kernel.WarningTest do
|
|||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string ~S"""
|
Code.eval_string ~S"""
|
||||||
defmodule Sample do
|
defmodule Sample do
|
||||||
def hello(arg \\ 0), do: nil
|
def hello(arg \\ 0), do: nil
|
||||||
def hello(arg), do: nil
|
def hello(arg), do: nil
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
end) =~ "definitions with multiple clauses and default values require a function head"
|
end) =~ "definitions with multiple clauses and default values require a function head"
|
||||||
@@ -551,7 +565,7 @@ defmodule Kernel.WarningTest do
|
|||||||
"""
|
"""
|
||||||
end) =~ "undefined behaviour function foo/0 (for behaviour Sample1)"
|
end) =~ "undefined behaviour function foo/0 (for behaviour Sample1)"
|
||||||
after
|
after
|
||||||
purge [Sample1, Sample2, Sample3]
|
purge [Sample1, Sample2]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "undefined macro for behaviour" do
|
test "undefined macro for behaviour" do
|
||||||
@@ -567,7 +581,7 @@ defmodule Kernel.WarningTest do
|
|||||||
"""
|
"""
|
||||||
end) =~ "undefined behaviour macro foo/0 (for behaviour Sample1)"
|
end) =~ "undefined behaviour macro foo/0 (for behaviour Sample1)"
|
||||||
after
|
after
|
||||||
purge [Sample1, Sample2, Sample3]
|
purge [Sample1, Sample2]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "undefined behavior" do
|
test "undefined behavior" do
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ defmodule Kernel.WithTest do
|
|||||||
assert result == :error
|
assert result == :error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not leak variables to else" do
|
||||||
|
state = 1
|
||||||
|
result = with 1 <- state, state = 2, :ok <- error(), do: state, else: (_ -> state)
|
||||||
|
assert result == 1
|
||||||
|
assert state == 1
|
||||||
|
end
|
||||||
|
|
||||||
test "errors in with" do
|
test "errors in with" do
|
||||||
assert_raise RuntimeError, fn ->
|
assert_raise RuntimeError, fn ->
|
||||||
with({:ok, res} <- oops(), do: res)
|
with({:ok, res} <- oops(), do: res)
|
||||||
@@ -59,6 +66,7 @@ defmodule Kernel.WithTest do
|
|||||||
|
|
||||||
test "else conditions" do
|
test "else conditions" do
|
||||||
assert with({:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1)) == 42
|
assert with({:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1)) == 42
|
||||||
|
assert with({:ok, res} <- 41, do: res, else: (res when res == 41 -> res + 1; res -> res)) == 42
|
||||||
assert with({:ok, res} <- 41, do: res, else: (_ -> :error)) == :error
|
assert with({:ok, res} <- 41, do: res, else: (_ -> :error)) == :error
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,6 +76,12 @@ defmodule Kernel.WithTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "invalid else form" do
|
||||||
|
assert_raise CompileError, "nofile:1: expected -> clauses for else in with", fn ->
|
||||||
|
Code.eval_quoted(quote do: with(_ <- true, do: :ok, else: :error))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp four() do
|
defp four() do
|
||||||
4
|
4
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -147,6 +147,16 @@ defmodule MapTest do
|
|||||||
%^struct{name: "john"} = user
|
%^struct{name: "john"} = user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "structs when using dynamic modules" do
|
||||||
|
defmodule Module.concat(MapTest, DynamicUser) do
|
||||||
|
defstruct [:name, :age]
|
||||||
|
|
||||||
|
def sample do
|
||||||
|
%__MODULE__{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "structs when quoted" do
|
test "structs when quoted" do
|
||||||
assert (quote do
|
assert (quote do
|
||||||
%User{foo: 1}
|
%User{foo: 1}
|
||||||
|
|||||||
@@ -245,6 +245,17 @@ defmodule ModuleTest do
|
|||||||
assert ModuleHygiene.test == [1, 2, 3]
|
assert ModuleHygiene.test == [1, 2, 3]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ensure function clauses are ordered" do
|
||||||
|
{_, _, binary, _} =
|
||||||
|
defmodule Ordered do
|
||||||
|
def foo(:foo), do: :bar
|
||||||
|
def baz(:baz), do: :bat
|
||||||
|
end
|
||||||
|
atoms = :beam_lib.chunks(binary, [:atoms])
|
||||||
|
assert :erlang.phash2(atoms) == 53987778
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Remove this check once we depend only on 19
|
||||||
if :erlang.system_info(:otp_release) >= '19' do
|
if :erlang.system_info(:otp_release) >= '19' do
|
||||||
test "create with generated true does not emit warnings" do
|
test "create with generated true does not emit warnings" do
|
||||||
contents =
|
contents =
|
||||||
|
|||||||
@@ -5,18 +5,24 @@ defmodule ProtocolTest do
|
|||||||
|
|
||||||
doctest Protocol
|
doctest Protocol
|
||||||
|
|
||||||
defprotocol Sample do
|
{_, _, sample_binary, _} =
|
||||||
@type t :: any
|
defprotocol Sample do
|
||||||
@doc "Ok"
|
@type t :: any
|
||||||
@spec ok(t) :: boolean
|
@doc "Ok"
|
||||||
def ok(term)
|
@spec ok(t) :: boolean
|
||||||
end
|
def ok(term)
|
||||||
|
end
|
||||||
|
|
||||||
defprotocol WithAny do
|
@sample_binary sample_binary
|
||||||
@fallback_to_any true
|
|
||||||
@doc "Ok"
|
{_, _, with_any_binary, _} =
|
||||||
def ok(term)
|
defprotocol WithAny do
|
||||||
end
|
@fallback_to_any true
|
||||||
|
@doc "Ok"
|
||||||
|
def ok(term)
|
||||||
|
end
|
||||||
|
|
||||||
|
@with_any_binary with_any_binary
|
||||||
|
|
||||||
defprotocol Derivable do
|
defprotocol Derivable do
|
||||||
def ok(a)
|
def ok(a)
|
||||||
@@ -123,11 +129,11 @@ defmodule ProtocolTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "protocol defines callbacks" do
|
test "protocol defines callbacks" do
|
||||||
assert get_callbacks(Sample, :ok, 1) ==
|
assert get_callbacks(@sample_binary, :ok, 1) ==
|
||||||
[{:type, [11], :fun, [{:type, [11], :product, [{:user_type, [11], :t, []}]}, {:type, [11], :boolean, []}]}]
|
[{:type, 12, :fun, [{:type, 12, :product, [{:user_type, 12, :t, []}]}, {:type, 12, :boolean, []}]}]
|
||||||
|
|
||||||
assert get_callbacks(WithAny, :ok, 1) ==
|
assert get_callbacks(@with_any_binary, :ok, 1) ==
|
||||||
[{:type, [18], :fun, [{:type, [18], :product, [{:user_type, [18], :t, []}]}, {:type, [18], :term, []}]}]
|
[{:type, 22, :fun, [{:type, 22, :product, [{:user_type, 22, :t, []}]}, {:type, 22, :term, []}]}]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "protocol defines functions and attributes" do
|
test "protocol defines functions and attributes" do
|
||||||
@@ -182,8 +188,8 @@ defmodule ProtocolTest do
|
|||||||
assert Multi.test(:a) == :a
|
assert Multi.test(:a) == :a
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_callbacks(module, name, arity) do
|
defp get_callbacks(beam, name, arity) do
|
||||||
callbacks = for {:callback, info} <- module.__info__(:attributes), do: hd(info)
|
callbacks = Kernel.Typespec.beam_callbacks(beam)
|
||||||
List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
|
List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -308,6 +314,8 @@ defmodule Protocol.ConsolidationTest do
|
|||||||
{:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
|
{:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
|
||||||
:code.load_binary(Sample, 'protocol_test.exs', binary)
|
:code.load_binary(Sample, 'protocol_test.exs', binary)
|
||||||
|
|
||||||
|
@sample_binary binary
|
||||||
|
|
||||||
# Any should be moved to the end
|
# Any should be moved to the end
|
||||||
:code.purge(WithAny)
|
:code.purge(WithAny)
|
||||||
:code.delete(WithAny)
|
:code.delete(WithAny)
|
||||||
@@ -367,7 +375,7 @@ defmodule Protocol.ConsolidationTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "consolidated keeps callbacks" do
|
test "consolidated keeps callbacks" do
|
||||||
callbacks = for {:callback, info} <- Sample.__info__(:attributes), do: hd(info)
|
callbacks = Kernel.Typespec.beam_callbacks(@sample_binary)
|
||||||
assert callbacks != []
|
assert callbacks != []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ defmodule StringTest do
|
|||||||
assert String.split("abé", "", trim: true, parts: 2) == ["a", "bé"]
|
assert String.split("abé", "", trim: true, parts: 2) == ["a", "bé"]
|
||||||
|
|
||||||
assert String.split("noël", "") == ["n", "o", "ë", "l", ""]
|
assert String.split("noël", "") == ["n", "o", "ë", "l", ""]
|
||||||
|
|
||||||
|
assert String.split("x-", "-", parts: 2, trim: true) == ["x"]
|
||||||
|
assert String.split("x-x-", "-", parts: 3, trim: true) == ["x", "x"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "split with regex" do
|
test "split with regex" do
|
||||||
@@ -79,6 +82,14 @@ defmodule StringTest do
|
|||||||
assert String.split("a,b", ~r{\.}) == ["a,b"]
|
assert String.split("a,b", ~r{\.}) == ["a,b"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "split with compiled pattern" do
|
||||||
|
pattern = :binary.compile_pattern("-")
|
||||||
|
|
||||||
|
assert String.split("x-", pattern) == ["x", ""]
|
||||||
|
assert String.split("x-", pattern, parts: 2, trim: true) == ["x"]
|
||||||
|
assert String.split("x-x-", pattern, parts: 3, trim: true) == ["x", "x"]
|
||||||
|
end
|
||||||
|
|
||||||
test "splitter" do
|
test "splitter" do
|
||||||
assert String.splitter("a,b,c", ",") |> Enum.to_list == ["a", "b", "c"]
|
assert String.splitter("a,b,c", ",") |> Enum.to_list == ["a", "b", "c"]
|
||||||
assert String.splitter("a,b", ".") |> Enum.to_list == ["a,b"]
|
assert String.splitter("a,b", ".") |> Enum.to_list == ["a,b"]
|
||||||
|
|||||||
@@ -187,4 +187,16 @@ defmodule SystemTest do
|
|||||||
time = System.monotonic_time(:nanoseconds)
|
time = System.monotonic_time(:nanoseconds)
|
||||||
assert abs(System.convert_time_unit(time, :nanoseconds, :microseconds)) < abs(time)
|
assert abs(System.convert_time_unit(time, :nanoseconds, :microseconds)) < abs(time)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "schedulers/0" do
|
||||||
|
assert System.schedulers >= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "schedulers_online/0" do
|
||||||
|
assert System.schedulers_online >= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "otp_release/0" do
|
||||||
|
assert is_binary System.otp_release
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
ExUnit.start [trace: "--trace" in System.argv]
|
exclude =
|
||||||
|
case :erlang.system_info(:otp_release) do
|
||||||
|
'19' -> [otp19: false]
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
ExUnit.start [exclude: exclude, trace: "--trace" in System.argv]
|
||||||
|
|
||||||
# Beam files compiled on demand
|
# Beam files compiled on demand
|
||||||
path = Path.expand("../../tmp/beams", __DIR__)
|
path = Path.expand("../../tmp/beams", __DIR__)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ extract_interpolations(String) ->
|
|||||||
Z
|
Z
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
% Interpolations
|
% Interpolations
|
||||||
|
|
||||||
extract_interpolations_without_interpolation_test() ->
|
extract_interpolations_without_interpolation_test() ->
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ op_kw_test() ->
|
|||||||
[{atom, {1, 1, 5}, foo}, {dual_op, {1, 5, 6}, '+'}, {atom, {1, 6, 10}, bar}] = tokenize(":foo+:bar").
|
[{atom, {1, 1, 5}, foo}, {dual_op, {1, 5, 6}, '+'}, {atom, {1, 6, 10}, bar}] = tokenize(":foo+:bar").
|
||||||
|
|
||||||
scientific_test() ->
|
scientific_test() ->
|
||||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1").
|
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1"),
|
||||||
|
[{number, {1, 1, 16}, 1.2345678e-7}] = tokenize("1_234.567_8e-10").
|
||||||
|
|
||||||
hex_bin_octal_test() ->
|
hex_bin_octal_test() ->
|
||||||
[{number, {1, 1, 5}, 255}] = tokenize("0xFF"),
|
[{number, {1, 1, 5}, 255}] = tokenize("0xFF"),
|
||||||
[{number, {1, 1, 5}, 255}] = tokenize("0xF_F"),
|
[{number, {1, 1, 6}, 255}] = tokenize("0xF_F"),
|
||||||
[{number, {1, 1, 5}, 63}] = tokenize("0o77"),
|
[{number, {1, 1, 5}, 63}] = tokenize("0o77"),
|
||||||
[{number, {1, 1, 5}, 63}] = tokenize("0o7_7"),
|
[{number, {1, 1, 6}, 63}] = tokenize("0o7_7"),
|
||||||
[{number, {1, 1, 5}, 3}] = tokenize("0b11"),
|
[{number, {1, 1, 5}, 3}] = tokenize("0b11"),
|
||||||
[{number, {1, 1, 5}, 3}] = tokenize("0b1_1").
|
[{number, {1, 1, 6}, 3}] = tokenize("0b1_1").
|
||||||
|
|
||||||
unquoted_atom_test() ->
|
unquoted_atom_test() ->
|
||||||
[{atom, {1, 1, 3}, '+'}] = tokenize(":+"),
|
[{atom, {1, 1, 3}, '+'}] = tokenize(":+"),
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ defmodule ExUnit do
|
|||||||
and print them on test failure. Can be overridden for individual tests via
|
and print them on test failure. Can be overridden for individual tests via
|
||||||
`@tag capture_log: false`. Defaults to `false`.
|
`@tag capture_log: false`. Defaults to `false`.
|
||||||
|
|
||||||
|
* `:case_load_timeout` - the timeout to be used when loading a test case.
|
||||||
|
Defaults to `60_000` milliseconds.
|
||||||
|
|
||||||
* `:colors` - a keyword list of colors to be used by some formatters.
|
* `:colors` - a keyword list of colors to be used by some formatters.
|
||||||
The only option so far is `[enabled: boolean]` which defaults to `IO.ANSI.enabled?/0`
|
The only option so far is `[enabled: boolean]` which defaults to `IO.ANSI.enabled?/0`
|
||||||
|
|
||||||
|
|||||||
@@ -379,7 +379,6 @@ defmodule ExUnit.Assertions do
|
|||||||
<> ExUnit.Assertions.__mailbox__(self()))
|
<> ExUnit.Assertions.__mailbox__(self()))
|
||||||
end
|
end
|
||||||
|
|
||||||
_ = unquote(vars) # Silence warnings
|
|
||||||
received
|
received
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -428,6 +427,14 @@ defmodule ExUnit.Assertions do
|
|||||||
Enum.uniq_by(pins, &elem(&1, 0))
|
Enum.uniq_by(pins, &elem(&1, 0))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp collect_vars_from_pattern({:when, _, [left, right]}) do
|
||||||
|
pattern = collect_vars_from_pattern(left)
|
||||||
|
for {name, _, context} = var <- collect_vars_from_pattern(right),
|
||||||
|
Enum.any?(pattern, &match?({^name, _, ^context}, &1)),
|
||||||
|
into: pattern,
|
||||||
|
do: var
|
||||||
|
end
|
||||||
|
|
||||||
defp collect_vars_from_pattern(expr) do
|
defp collect_vars_from_pattern(expr) do
|
||||||
Macro.prewalk(expr, [], fn
|
Macro.prewalk(expr, [], fn
|
||||||
{:::, _, [left, _]}, acc ->
|
{:::, _, [left, _]}, acc ->
|
||||||
|
|||||||
@@ -367,18 +367,29 @@ defmodule ExUnit.Diff do
|
|||||||
map_pair = format_key_value(key, val, keyword?)
|
map_pair = format_key_value(key, val, keyword?)
|
||||||
[[ins: ", ", ins: map_pair] | acc]
|
[[ins: ", ", ins: map_pair] | acc]
|
||||||
end)
|
end)
|
||||||
|
result =
|
||||||
|
if same == [] and altered == [] and missing != [] and surplus != [] do
|
||||||
|
[[_ | elem_diff] | rest] = result
|
||||||
|
[elem_diff | rest]
|
||||||
|
else
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
result = Enum.reduce(surplus, result, fn({key, val}, acc) ->
|
result = Enum.reduce(surplus, result, fn({key, val}, acc) ->
|
||||||
map_pair = format_key_value(key, val, keyword?)
|
map_pair = format_key_value(key, val, keyword?)
|
||||||
[[del: ", ", del: map_pair] | acc]
|
[[del: ", ", del: map_pair] | acc]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
result = Enum.reduce(altered, result, fn({key, {val1, val2}}, acc) ->
|
result = Enum.reduce(altered, result, fn({key, {val1, val2}}, acc) ->
|
||||||
value_diff = script_inner(val1, val2)
|
value_diff = script_inner(val1, val2)
|
||||||
[[{:eq, ", "}, {:eq, format_key(key, keyword?)}, value_diff] | acc]
|
[[{:eq, ", "}, {:eq, format_key(key, keyword?)}, value_diff] | acc]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
result = Enum.reduce(same, result, fn({key, val}, acc) ->
|
result = Enum.reduce(same, result, fn({key, val}, acc) ->
|
||||||
map_pair = format_key_value(key, val, keyword?)
|
map_pair = format_key_value(key, val, keyword?)
|
||||||
[[eq: ", ", eq: map_pair] | acc]
|
[[eq: ", ", eq: map_pair] | acc]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
[[_ | elem_diff] | rest] = result
|
[[_ | elem_diff] | rest] = result
|
||||||
[{:eq, "%" <> name <> "{"}, [elem_diff | rest], {:eq, "}"}]
|
[{:eq, "%" <> name <> "{"}, [elem_diff | rest], {:eq, "}"}]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
defmodule ExUnit.Server do
|
defmodule ExUnit.Server do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
@timeout 60_000
|
|
||||||
|
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
@@ -21,11 +20,13 @@ defmodule ExUnit.Server do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def take_async_cases(count) do
|
def take_async_cases(count) do
|
||||||
GenServer.call(__MODULE__, {:take_async_cases, count}, @timeout)
|
timeout = Application.fetch_env!(:ex_unit, :case_load_timeout)
|
||||||
|
GenServer.call(__MODULE__, {:take_async_cases, count}, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
def take_sync_cases() do
|
def take_sync_cases() do
|
||||||
GenServer.call(__MODULE__, :take_sync_cases, @timeout)
|
timeout = Application.fetch_env!(:ex_unit, :case_load_timeout)
|
||||||
|
GenServer.call(__MODULE__, :take_sync_cases, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
## Callbacks
|
## Callbacks
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ defmodule ExUnit.Mixfile do
|
|||||||
assert_receive_timeout: 100,
|
assert_receive_timeout: 100,
|
||||||
autorun: true,
|
autorun: true,
|
||||||
capture_log: false,
|
capture_log: false,
|
||||||
|
case_load_timeout: 60_000,
|
||||||
colors: [],
|
colors: [],
|
||||||
exclude: [],
|
exclude: [],
|
||||||
include: [],
|
include: [],
|
||||||
|
|||||||
@@ -266,6 +266,13 @@ defmodule ExUnit.AssertionsTest do
|
|||||||
:world = world
|
:world = world
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "assert received does not leak external variables used in guards" do
|
||||||
|
send self(), {:hello, :world}
|
||||||
|
guard_world = :world
|
||||||
|
assert_received {:hello, world} when world == guard_world
|
||||||
|
:world = world
|
||||||
|
end
|
||||||
|
|
||||||
test "refute received does not wait" do
|
test "refute received does not wait" do
|
||||||
false = refute_received :hello
|
false = refute_received :hello
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -242,6 +242,8 @@ defmodule ExUnit.DiffTest do
|
|||||||
assert script(map2, map1) == expected
|
assert script(map2, map1) == expected
|
||||||
assert script(map1, %{}) == [{:eq, "%{"}, [[del: "baz: 12"]], {:eq, "}"}]
|
assert script(map1, %{}) == [{:eq, "%{"}, [[del: "baz: 12"]], {:eq, "}"}]
|
||||||
assert script(%{}, map1) == [{:eq, "%{"}, [[ins: "baz: 12"]], {:eq, "}"}]
|
assert script(%{}, map1) == [{:eq, "%{"}, [[ins: "baz: 12"]], {:eq, "}"}]
|
||||||
|
expected = [{:eq, "%{"}, [[del: "baz: 12"], [ins: "foo: 12"]], {:eq, "}"}]
|
||||||
|
assert script(map1, %{foo: 12}) == expected
|
||||||
|
|
||||||
assert script(%{}, %{}) == [eq: "%{}"]
|
assert script(%{}, %{}) == [eq: "%{}"]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,9 +59,12 @@ defmodule IEx.Autocomplete do
|
|||||||
defp reduce(expr) do
|
defp reduce(expr) do
|
||||||
Enum.reduce(' ([{', expr, fn token, acc ->
|
Enum.reduce(' ([{', expr, fn token, acc ->
|
||||||
hd(:string.tokens(acc, [token]))
|
hd(:string.tokens(acc, [token]))
|
||||||
end) |> Enum.reverse |> strip_ampersand
|
end) |> Enum.reverse |> strip_ampersand |> strip_percent
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp strip_percent([?% | t]), do: t
|
||||||
|
defp strip_percent(expr), do: expr
|
||||||
|
|
||||||
defp strip_ampersand([?& | t]), do: t
|
defp strip_ampersand([?& | t]), do: t
|
||||||
defp strip_ampersand(expr), do: expr
|
defp strip_ampersand(expr), do: expr
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ defmodule IEx.Helpers do
|
|||||||
There are many other helpers available:
|
There are many other helpers available:
|
||||||
|
|
||||||
* `b/1` - prints callbacks info and docs for a given module
|
* `b/1` - prints callbacks info and docs for a given module
|
||||||
* `c/1` - compiles a file at the current directory
|
* `c/1` - compiles a file into the current directory
|
||||||
* `c/2` - compiles a file at the given path
|
* `c/2` - compiles a file to the given path
|
||||||
* `cd/1` - changes the current directory
|
* `cd/1` - changes the current directory
|
||||||
* `clear/0` - clears the screen
|
* `clear/0` - clears the screen
|
||||||
* `flush/0` - flushes all messages sent to the shell
|
* `flush/0` - flushes all messages sent to the shell
|
||||||
@@ -115,6 +115,7 @@ defmodule IEx.Helpers do
|
|||||||
|
|
||||||
c "baz.ex"
|
c "baz.ex"
|
||||||
#=> [Baz]
|
#=> [Baz]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def c(files, path \\ ".") when is_binary(path) do
|
def c(files, path \\ ".") when is_binary(path) do
|
||||||
files = List.wrap(files)
|
files = List.wrap(files)
|
||||||
@@ -123,10 +124,7 @@ defmodule IEx.Helpers do
|
|||||||
raise ArgumentError, "expected a binary or a list of binaries as argument"
|
raise ArgumentError, "expected a binary or a list of binaries as argument"
|
||||||
end
|
end
|
||||||
|
|
||||||
{found, not_found} =
|
{found, not_found} = Enum.partition(files, &File.exists?/1)
|
||||||
files
|
|
||||||
|> Enum.map(&Path.expand(&1, path))
|
|
||||||
|> Enum.partition(&File.exists?/1)
|
|
||||||
|
|
||||||
unless Enum.empty?(not_found) do
|
unless Enum.empty?(not_found) do
|
||||||
raise ArgumentError, "could not find files #{Enum.join(not_found, ", ")}"
|
raise ArgumentError, "could not find files #{Enum.join(not_found, ", ")}"
|
||||||
@@ -535,6 +533,15 @@ defmodule IEx.Helpers do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Similar to `import_file` but only imports it if it is available.
|
||||||
|
"""
|
||||||
|
defmacro import_file_if_available(path) do
|
||||||
|
quote do
|
||||||
|
import_file(unquote(path), optional: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Evaluates the contents of the file at `path` as if it were directly typed into
|
Evaluates the contents of the file at `path` as if it were directly typed into
|
||||||
the shell.
|
the shell.
|
||||||
@@ -587,6 +594,25 @@ defmodule IEx.Helpers do
|
|||||||
raise ArgumentError, "import_file/1 expects a literal binary as its argument"
|
raise ArgumentError, "import_file/1 expects a literal binary as its argument"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Calls `import/2` with the given arguments, but only if the module is available.
|
||||||
|
|
||||||
|
This lets you put imports in `.iex.exs` files (including `~/.iex.exs`) without
|
||||||
|
getting compile errors if you open a console where the module is not available.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
# In ~/.iex.exs
|
||||||
|
import_if_available Ecto.Query
|
||||||
|
"""
|
||||||
|
defmacro import_if_available(quoted_module, opts \\ []) do
|
||||||
|
module = Macro.expand(quoted_module, __CALLER__)
|
||||||
|
|
||||||
|
if Code.ensure_loaded?(module) do
|
||||||
|
quote do: import unquote(quoted_module), unquote(opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Compiles and loads an Erlang source file, returns {module, binary}
|
# Compiles and loads an Erlang source file, returns {module, binary}
|
||||||
defp compile_erlang(source) do
|
defp compile_erlang(source) do
|
||||||
source = Path.relative_to_cwd(source) |> String.to_charlist
|
source = Path.relative_to_cwd(source) |> String.to_charlist
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ defmodule IEx.Introspection do
|
|||||||
if docs = Code.get_docs(mod, :docs) do
|
if docs = Code.get_docs(mod, :docs) do
|
||||||
if doc = find_doc(docs, fun, arity) do
|
if doc = find_doc(docs, fun, arity) do
|
||||||
if callback_module = is_nil(elem(doc, 4)) and callback_module(mod, fun, arity) do
|
if callback_module = is_nil(elem(doc, 4)) and callback_module(mod, fun, arity) do
|
||||||
filter = &match?({^fun, _}, elem(&1, 0))
|
filter = &match?({^fun, ^arity}, elem(&1, 0))
|
||||||
print_callback_docs(callback_module, filter, &print_doc/2)
|
print_callback_docs(callback_module, filter, &print_doc/2)
|
||||||
else
|
else
|
||||||
print_doc(doc)
|
print_doc(doc)
|
||||||
@@ -139,6 +139,8 @@ defmodule IEx.Introspection do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp has_content?(nil),
|
||||||
|
do: false
|
||||||
defp has_content?({_, _, _, _, false}),
|
defp has_content?({_, _, _, _, false}),
|
||||||
do: false
|
do: false
|
||||||
defp has_content?({{name, _}, _, _, _, nil}),
|
defp has_content?({{name, _}, _, _, _, nil}),
|
||||||
@@ -147,14 +149,11 @@ defmodule IEx.Introspection do
|
|||||||
do: true
|
do: true
|
||||||
|
|
||||||
defp callback_module(mod, fun, arity) do
|
defp callback_module(mod, fun, arity) do
|
||||||
|
filter = &match?({{^fun, ^arity}, _}, &1)
|
||||||
mod.module_info(:attributes)
|
mod.module_info(:attributes)
|
||||||
|> Keyword.get_values(:behaviour)
|
|> Keyword.get_values(:behaviour)
|
||||||
|> Stream.concat()
|
|> Stream.concat()
|
||||||
|> Enum.find(fn module ->
|
|> Enum.find(&Enum.any?(Typespec.beam_callbacks(&1), filter))
|
||||||
module.module_info(:attributes)
|
|
||||||
|> Enum.filter(&match?({:callback, _}, &1))
|
|
||||||
|> Enum.any?(&match?({_, [{{^fun, ^arity}, _} | _]}, &1))
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp print_doc({{fun, _}, _line, kind, args, doc}) do
|
defp print_doc({{fun, _}, _line, kind, args, doc}) do
|
||||||
|
|||||||
@@ -179,4 +179,13 @@ defmodule IEx.AutocompleteTest do
|
|||||||
:code.purge(Sample)
|
:code.purge(Sample)
|
||||||
:code.delete(Sample)
|
:code.delete(Sample)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
defmodule MyStruct do
|
||||||
|
defstruct my_val: "val"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "completion for structs" do
|
||||||
|
assert expand('%IEx.AutocompleteTest.MyStr') == {:yes, 'uct.', []}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ defmodule IEx.HelpersTest do
|
|||||||
test "h helper function" do
|
test "h helper function" do
|
||||||
pwd_h = "* def pwd()\n\nPrints the current working directory.\n\n"
|
pwd_h = "* def pwd()\n\nPrints the current working directory.\n\n"
|
||||||
c_h = "* def c(files, path \\\\ \".\")\n\nCompiles the given files."
|
c_h = "* def c(files, path \\\\ \".\")\n\nCompiles the given files."
|
||||||
|
eq_h = "* def ==(left, right)\n\nReturns `true` if the two items are equal.\n\n"
|
||||||
|
|
||||||
assert capture_io(fn -> h IEx.Helpers.pwd/0 end) =~ pwd_h
|
assert capture_io(fn -> h IEx.Helpers.pwd/0 end) =~ pwd_h
|
||||||
assert capture_io(fn -> h IEx.Helpers.c/2 end) =~ c_h
|
assert capture_io(fn -> h IEx.Helpers.c/2 end) =~ c_h
|
||||||
|
assert capture_io(fn -> h ==/2 end) =~ eq_h
|
||||||
|
|
||||||
assert capture_io(fn -> h IEx.Helpers.c/1 end) =~ c_h
|
assert capture_io(fn -> h IEx.Helpers.c/1 end) =~ c_h
|
||||||
assert capture_io(fn -> h pwd end) =~ pwd_h
|
assert capture_io(fn -> h pwd end) =~ pwd_h
|
||||||
@@ -77,14 +79,17 @@ defmodule IEx.HelpersTest do
|
|||||||
@doc "Docs for MyBehaviour.first"
|
@doc "Docs for MyBehaviour.first"
|
||||||
@callback first(integer) :: integer
|
@callback first(integer) :: integer
|
||||||
@callback second(integer) :: integer
|
@callback second(integer) :: integer
|
||||||
|
@callback second(integer, integer) :: integer
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
impl = """
|
impl = """
|
||||||
defmodule Impl do
|
defmodule Impl do
|
||||||
@behaviour MyBehaviour
|
@behaviour MyBehaviour
|
||||||
def first(0), do: 0
|
def first(0), do: 0
|
||||||
@doc "Docs for Impl.second"
|
@doc "Docs for Impl.second/1"
|
||||||
def second(0), do: 0
|
def second(0), do: 0
|
||||||
|
@doc "Docs for Impl.second/2"
|
||||||
|
def second(0, 0), do: 0
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
files = ["my_behaviour.ex", "impl.ex"]
|
files = ["my_behaviour.ex", "impl.ex"]
|
||||||
@@ -92,10 +97,11 @@ defmodule IEx.HelpersTest do
|
|||||||
assert c(files) |> Enum.sort == [Impl, MyBehaviour]
|
assert c(files) |> Enum.sort == [Impl, MyBehaviour]
|
||||||
|
|
||||||
assert capture_io(fn -> h Impl.first/1 end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
assert capture_io(fn -> h Impl.first/1 end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||||
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second\n"
|
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second/1\n"
|
||||||
|
assert capture_io(fn -> h Impl.second/2 end) == "* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||||
|
|
||||||
assert capture_io(fn -> h Impl.first end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
assert capture_io(fn -> h Impl.first end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||||
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second\n"
|
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second/1\n* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
cleanup_modules([Impl, MyBehaviour])
|
cleanup_modules([Impl, MyBehaviour])
|
||||||
@@ -255,11 +261,17 @@ defmodule IEx.HelpersTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "import_file when the file is missing" do
|
test "import_file when the file is missing" do
|
||||||
assert "nil" == capture_iex("import_file \"nonexistent\", optional: true")
|
|
||||||
|
|
||||||
failing = capture_iex("import_file \"nonexistent\"")
|
failing = capture_iex("import_file \"nonexistent\"")
|
||||||
assert "** (File.Error) could not read file" <> _ = failing
|
assert "** (File.Error) could not read file" <> _ = failing
|
||||||
assert failing =~ "no such file or directory"
|
assert failing =~ "no such file or directory"
|
||||||
|
|
||||||
|
assert "nil" == capture_iex("import_file_if_available \"nonexistent\"")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "import_if_available helper" do
|
||||||
|
assert "nil" == capture_iex("import_if_available NoSuchModule")
|
||||||
|
assert "[1, 2, 3]" == capture_iex("import_if_available Integer; digits 123")
|
||||||
|
assert "[1, 2, 3]" == capture_iex("import_if_available Integer, only: [digits: 1]; digits 123")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "c helper" do
|
test "c helper" do
|
||||||
|
|||||||
@@ -70,4 +70,3 @@ defmodule IEx.Case do
|
|||||||
|> String.trim
|
|> String.trim
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ defmodule Logger.Backends.Console do
|
|||||||
colors = configure_colors(config)
|
colors = configure_colors(config)
|
||||||
max_buffer = Keyword.get(config, :max_buffer, 32)
|
max_buffer = Keyword.get(config, :max_buffer, 32)
|
||||||
%{state | format: format, metadata: Enum.reverse(metadata),
|
%{state | format: format, metadata: Enum.reverse(metadata),
|
||||||
level: level, colors: colors, device: device, max_buffer: max_buffer}
|
level: level, colors: colors, device: device, max_buffer: max_buffer}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp configure_merge(env, options) do
|
defp configure_merge(env, options) do
|
||||||
@@ -149,7 +149,7 @@ defmodule Logger.Backends.Console do
|
|||||||
%{format: format, metadata: keys, colors: colors} = state
|
%{format: format, metadata: keys, colors: colors} = state
|
||||||
format
|
format
|
||||||
|> Logger.Formatter.format(level, msg, ts, take_metadata(md, keys))
|
|> Logger.Formatter.format(level, msg, ts, take_metadata(md, keys))
|
||||||
|> color_event(level, colors)
|
|> color_event(level, colors, md)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp take_metadata(metadata, keys) do
|
defp take_metadata(metadata, keys) do
|
||||||
@@ -161,10 +161,11 @@ defmodule Logger.Backends.Console do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp color_event(data, _level, %{enabled: false}), do: data
|
defp color_event(data, _level, %{enabled: false}, _md), do: data
|
||||||
|
|
||||||
defp color_event(data, level, %{enabled: true} = colors) do
|
defp color_event(data, level, %{enabled: true} = colors, md) do
|
||||||
[IO.ANSI.format_fragment(Map.fetch!(colors, level), true), data | IO.ANSI.reset]
|
color = md[:ansi_color] || Map.fetch!(colors, level)
|
||||||
|
[IO.ANSI.format_fragment(color, true), data | IO.ANSI.reset]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp log_buffer(%{buffer_size: 0, buffer: []} = state), do: state
|
defp log_buffer(%{buffer_size: 0, buffer: []} = state), do: state
|
||||||
|
|||||||
@@ -109,4 +109,12 @@ defmodule Logger.Backends.ConsoleTest do
|
|||||||
Logger.error("hello")
|
Logger.error("hello")
|
||||||
end) == IO.ANSI.cyan() <> "hello" <> IO.ANSI.reset()
|
end) == IO.ANSI.cyan() <> "hello" <> IO.ANSI.reset()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can use colors from metadata" do
|
||||||
|
Logger.configure_backend(:console, [format: "$message", colors: [enabled: true]])
|
||||||
|
|
||||||
|
assert capture_log(fn ->
|
||||||
|
Logger.log(:error, "hello", ansi_color: :yellow)
|
||||||
|
end) == IO.ANSI.yellow() <> "hello" <> IO.ANSI.reset()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -177,8 +177,6 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp each_module(pid, cwd, source, module, binary) do
|
defp each_module(pid, cwd, source, module, binary) do
|
||||||
beam = Atom.to_string(module) <> ".beam"
|
|
||||||
|
|
||||||
{compile_references, runtime_references} = Kernel.LexicalTracker.remote_references(module)
|
{compile_references, runtime_references} = Kernel.LexicalTracker.remote_references(module)
|
||||||
|
|
||||||
compile_references =
|
compile_references =
|
||||||
@@ -214,7 +212,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
module: module,
|
module: module,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
source: source,
|
source: source,
|
||||||
beam: beam,
|
beam: nil, # They are calculated when writing the manifest
|
||||||
binary: binary
|
binary: binary
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -248,10 +246,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
|
|
||||||
defp get_external_resources(module, cwd) do
|
defp get_external_resources(module, cwd) do
|
||||||
for file <- Module.get_attribute(module, :external_resource),
|
for file <- Module.get_attribute(module, :external_resource),
|
||||||
File.regular?(file),
|
do: Path.relative_to(file, cwd)
|
||||||
relative = Path.relative_to(file, cwd),
|
|
||||||
Path.type(relative) == :relative,
|
|
||||||
do: relative
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp each_file(source) do
|
defp each_file(source) do
|
||||||
@@ -265,8 +260,10 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
## Resolution
|
## Resolution
|
||||||
|
|
||||||
defp update_stale_sources(sources, removed, changed) do
|
defp update_stale_sources(sources, removed, changed) do
|
||||||
|
# Remove delete sources
|
||||||
sources =
|
sources =
|
||||||
Enum.reject(sources, fn source(source: source) -> source in removed end)
|
Enum.reduce(removed, sources, &List.keydelete(&2, &1, source(:source)))
|
||||||
|
# Store empty sources for the changed ones as the compiler appends data
|
||||||
sources =
|
sources =
|
||||||
Enum.reduce(changed, sources, &List.keystore(&2, &1, source(:source), source(source: &1)))
|
Enum.reduce(changed, sources, &List.keystore(&2, &1, source(:source), source(source: &1)))
|
||||||
sources
|
sources
|
||||||
@@ -394,13 +391,14 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
File.mkdir_p!(Path.dirname(manifest))
|
File.mkdir_p!(Path.dirname(manifest))
|
||||||
|
|
||||||
modules =
|
modules =
|
||||||
for module(beam: beam, binary: binary) = module <- modules do
|
for module(binary: binary, module: module) = entry <- modules do
|
||||||
|
beam = Atom.to_string(module) <> ".beam"
|
||||||
if binary do
|
if binary do
|
||||||
beam_path = Path.join(compile_path, beam)
|
beam_path = Path.join(compile_path, beam)
|
||||||
File.write!(beam_path, binary)
|
File.write!(beam_path, binary)
|
||||||
File.touch!(beam_path, timestamp)
|
File.touch!(beam_path, timestamp)
|
||||||
end
|
end
|
||||||
module(module, binary: nil)
|
module(entry, binary: nil, beam: beam)
|
||||||
end
|
end
|
||||||
|
|
||||||
manifest_data =
|
manifest_data =
|
||||||
|
|||||||
@@ -264,9 +264,6 @@ defmodule Mix.Compilers.Test do
|
|||||||
|
|
||||||
defp get_external_resources(module, cwd) do
|
defp get_external_resources(module, cwd) do
|
||||||
for file <- Module.get_attribute(module, :external_resource),
|
for file <- Module.get_attribute(module, :external_resource),
|
||||||
File.regular?(file),
|
do: Path.relative_to(file, cwd)
|
||||||
relative = Path.relative_to(file, cwd),
|
|
||||||
Path.type(relative) == :relative,
|
|
||||||
do: relative
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+16
-13
@@ -64,19 +64,22 @@ defmodule Mix.Dep do
|
|||||||
@doc """
|
@doc """
|
||||||
Returns loaded dependencies from the cache for the current environment.
|
Returns loaded dependencies from the cache for the current environment.
|
||||||
|
|
||||||
Because the dependencies are cached during deps.check, their
|
Because the dependencies are cached during deps.loadpaths, their
|
||||||
status may be outdated (for example, `:compile` did not
|
status may be outdated (for example, `:compile` did not
|
||||||
yet become `:ok`). Therefore it is recommended to not rely
|
yet become `:ok`). Therefore it is recommended to not rely
|
||||||
on their status, also given they haven't been checked
|
on their status, also given they haven't been checked
|
||||||
against the lock.
|
against the lock.
|
||||||
"""
|
"""
|
||||||
def cached do
|
def cached do
|
||||||
if project = Mix.Project.get do
|
cond do
|
||||||
key = {:cached_deps, project}
|
System.get_env("MIX_NO_DEPS") in ~w(1 true) ->
|
||||||
Mix.ProjectStack.read_cache(key) ||
|
[]
|
||||||
Mix.ProjectStack.write_cache(key, loaded(env: Mix.env))
|
project = Mix.Project.get ->
|
||||||
else
|
key = {:cached_deps, project}
|
||||||
loaded(env: Mix.env)
|
Mix.ProjectStack.read_cache(key) ||
|
||||||
|
Mix.ProjectStack.write_cache(key, loaded(env: Mix.env))
|
||||||
|
true ->
|
||||||
|
loaded(env: Mix.env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -225,16 +228,16 @@ defmodule Mix.Dep do
|
|||||||
def format_status(%Mix.Dep{app: app, status: {:divergedonly, other}} = dep) do
|
def format_status(%Mix.Dep{app: app, status: {:divergedonly, other}} = dep) do
|
||||||
recommendation =
|
recommendation =
|
||||||
if Keyword.has_key?(other.opts, :only) do
|
if Keyword.has_key?(other.opts, :only) do
|
||||||
"Ensure the parent dependency specifies a superset of the child one in"
|
"Ensure you specify at least the same environments in :only in your dep"
|
||||||
else
|
else
|
||||||
"Remove the :only restriction from"
|
"Remove the :only restriction from your dep"
|
||||||
end
|
end
|
||||||
|
|
||||||
"the dependency #{app}\n" <>
|
"the :only option for dependency #{app}\n" <>
|
||||||
"#{dep_status(dep)}" <>
|
"#{dep_status(dep)}" <>
|
||||||
"\n does not match the environments calculated for\n" <>
|
"\n does not match the :only option calculated for\n" <>
|
||||||
"#{dep_status(other)}" <>
|
"#{dep_status(other)}" <>
|
||||||
"\n #{recommendation} your dep"
|
"\n #{recommendation}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_status(%Mix.Dep{app: app, status: {:diverged, other}} = dep) do
|
def format_status(%Mix.Dep{app: app, status: {:diverged, other}} = dep) do
|
||||||
@@ -264,7 +267,7 @@ defmodule Mix.Dep do
|
|||||||
do: "the dependency was built with another SCM, run \"#{mix_env_var}mix deps.compile\""
|
do: "the dependency was built with another SCM, run \"#{mix_env_var}mix deps.compile\""
|
||||||
|
|
||||||
defp dep_status(%Mix.Dep{app: app, requirement: req, manager: manager, opts: opts, from: from}) do
|
defp dep_status(%Mix.Dep{app: app, requirement: req, manager: manager, opts: opts, from: from}) do
|
||||||
opts = Keyword.drop(opts, [:dest, :build, :lock, :manager])
|
opts = Keyword.drop(opts, [:dest, :build, :lock, :manager, :checkout])
|
||||||
opts = opts ++ (if manager, do: [manager: manager], else: [])
|
opts = opts ++ (if manager, do: [manager: manager], else: [])
|
||||||
info = if req, do: {app, req, opts}, else: {app, opts}
|
info = if req, do: {app, req, opts}, else: {app, opts}
|
||||||
"\n > In #{Path.relative_to_cwd(from)}:\n #{inspect info}\n"
|
"\n > In #{Path.relative_to_cwd(from)}:\n #{inspect info}\n"
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ defmodule Mix.Dep.Converger do
|
|||||||
if not diverged? && remote do
|
if not diverged? && remote do
|
||||||
# If there is a lock, it means we are doing a get/update
|
# If there is a lock, it means we are doing a get/update
|
||||||
# and we need to hit the remote converger which do external
|
# and we need to hit the remote converger which do external
|
||||||
# requests and what not. In case of deps.check, deps and so
|
# requests and what not. In case of deps.loadpaths, deps and so
|
||||||
# on, there is no lock, so we won't hit this branch.
|
# on, there is no lock, so we won't hit this branch.
|
||||||
lock = if lock_given?, do: remote.converge(deps, lock), else: lock
|
lock = if lock_given?, do: remote.converge(deps, lock), else: lock
|
||||||
|
|
||||||
|
|||||||
+81
-34
@@ -25,6 +25,11 @@ defmodule Mix.SCM.Git do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def accepts_options(_app, opts) do
|
def accepts_options(_app, opts) do
|
||||||
|
opts =
|
||||||
|
opts
|
||||||
|
|> Keyword.put(:checkout, opts[:dest])
|
||||||
|
|> sparse_opts()
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
gh = opts[:github] ->
|
gh = opts[:github] ->
|
||||||
opts
|
opts
|
||||||
@@ -41,7 +46,9 @@ defmodule Mix.SCM.Git do
|
|||||||
|
|
||||||
def checked_out?(opts) do
|
def checked_out?(opts) do
|
||||||
# Are we inside a Git repository?
|
# Are we inside a Git repository?
|
||||||
File.regular?(Path.join(opts[:dest], ".git/HEAD"))
|
opts[:checkout]
|
||||||
|
|> Path.join(".git/HEAD")
|
||||||
|
|> File.regular?
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock_status(opts) do
|
def lock_status(opts) do
|
||||||
@@ -50,7 +57,7 @@ defmodule Mix.SCM.Git do
|
|||||||
|
|
||||||
cond do
|
cond do
|
||||||
lock_rev = get_lock_rev(lock, opts) ->
|
lock_rev = get_lock_rev(lock, opts) ->
|
||||||
File.cd!(opts[:dest], fn ->
|
File.cd!(opts[:checkout], fn ->
|
||||||
%{origin: origin, rev: rev} = get_rev_info()
|
%{origin: origin, rev: rev} = get_rev_info()
|
||||||
if get_lock_repo(lock) == origin and lock_rev == rev do
|
if get_lock_repo(lock) == origin and lock_rev == rev do
|
||||||
:ok
|
:ok
|
||||||
@@ -76,29 +83,75 @@ defmodule Mix.SCM.Git do
|
|||||||
|
|
||||||
def checkout(opts) do
|
def checkout(opts) do
|
||||||
assert_git!()
|
assert_git!()
|
||||||
|
path = opts[:checkout]
|
||||||
path = opts[:dest]
|
File.rm_rf!(path)
|
||||||
location = opts[:git]
|
File.mkdir_p!(path)
|
||||||
|
File.cd!(path, fn ->
|
||||||
_ = File.rm_rf!(path)
|
git!("init --quiet")
|
||||||
git!(~s(clone --no-checkout --progress "#{location}" "#{path}"))
|
git!("--git-dir=.git remote add origin \"#{opts[:git]}\"")
|
||||||
|
checkout(path, opts)
|
||||||
File.cd! path, fn -> do_checkout(opts) end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(opts) do
|
def update(opts) do
|
||||||
assert_git!()
|
assert_git!()
|
||||||
|
path = opts[:checkout]
|
||||||
|
File.cd! path, fn -> checkout(path, opts) end
|
||||||
|
end
|
||||||
|
|
||||||
File.cd! opts[:dest], fn ->
|
defp checkout(_path, opts) do
|
||||||
location = opts[:git]
|
# Set configuration
|
||||||
update_origin(location)
|
sparse_toggle(opts)
|
||||||
|
update_origin(opts[:git])
|
||||||
|
|
||||||
command = IO.iodata_to_binary(["--git-dir=.git fetch --force",
|
# Fetch external data
|
||||||
progress_switch(git_version()),
|
command = IO.iodata_to_binary(["--git-dir=.git fetch --force --quiet",
|
||||||
tags_switch(opts[:tag])])
|
progress_switch(git_version()),
|
||||||
|
tags_switch(opts[:tag])])
|
||||||
|
git!(command)
|
||||||
|
|
||||||
git!(command)
|
# Migrate the git repo
|
||||||
do_checkout(opts)
|
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
|
||||||
|
git!("--git-dir=.git checkout --quiet #{rev}")
|
||||||
|
|
||||||
|
if opts[:submodules] do
|
||||||
|
git!("--git-dir=.git submodule update --init --recursive")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the new repo lock
|
||||||
|
get_lock(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sparse_opts(opts) do
|
||||||
|
if opts[:sparse] do
|
||||||
|
dest = Path.join(opts[:dest], opts[:sparse])
|
||||||
|
Keyword.put(opts, :dest, dest)
|
||||||
|
else
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sparse_toggle(opts) do
|
||||||
|
cond do
|
||||||
|
sparse = opts[:sparse] ->
|
||||||
|
sparse_check(git_version())
|
||||||
|
git!("--git-dir=.git config core.sparsecheckout true")
|
||||||
|
File.write!(".git/info/sparse-checkout", sparse)
|
||||||
|
File.exists?(".git/info/sparse-checkout") ->
|
||||||
|
File.write!(".git/info/sparse-checkout", "*")
|
||||||
|
git!("--git-dir=.git read-tree -mu HEAD")
|
||||||
|
git!("--git-dir=.git config core.sparsecheckout false")
|
||||||
|
File.rm(".git/info/sparse-checkout")
|
||||||
|
true ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sparse_check(version) do
|
||||||
|
unless {1, 7, 0} <= version do
|
||||||
|
version = version |> Tuple.to_list |> Enum.join(".")
|
||||||
|
Mix.raise "Git >= 1.7.0 is required to use sparse checkout. " <>
|
||||||
|
"You are running version #{version}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -111,24 +164,17 @@ defmodule Mix.SCM.Git do
|
|||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
defp validate_git_options(opts) do
|
defp validate_git_options(opts) do
|
||||||
case Keyword.take(opts, [:branch, :ref, :tag]) do
|
err = "You should specify only one of branch, ref or tag, and only once. " <>
|
||||||
[] -> opts
|
"Error on Git dependency: #{opts[:git]}"
|
||||||
[_] -> opts
|
validate_single_uniq(opts, [:branch, :ref, :tag], err)
|
||||||
_ ->
|
|
||||||
Mix.raise "You should specify only one of branch, ref or tag, and only once. " <>
|
|
||||||
"Error on Git dependency: #{opts[:git]}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_checkout(opts) do
|
defp validate_single_uniq(opts, take, error) do
|
||||||
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
|
case Keyword.take(opts, take) do
|
||||||
git!("--git-dir=.git checkout --quiet #{rev}")
|
[] -> opts
|
||||||
|
[_] -> opts
|
||||||
if opts[:submodules] do
|
_ -> Mix.raise error
|
||||||
git!("--git-dir=.git submodule update --init --recursive")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
get_lock(opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_lock(opts) do
|
defp get_lock(opts) do
|
||||||
@@ -146,7 +192,8 @@ defmodule Mix.SCM.Git do
|
|||||||
defp get_lock_rev(_, _), do: nil
|
defp get_lock_rev(_, _), do: nil
|
||||||
|
|
||||||
defp get_lock_opts(opts) do
|
defp get_lock_opts(opts) do
|
||||||
lock_opts = Keyword.take(opts, [:branch, :ref, :tag])
|
lock_opts = Keyword.take(opts, [:branch, :ref, :tag, :sparse])
|
||||||
|
|
||||||
if opts[:submodules] do
|
if opts[:submodules] do
|
||||||
lock_opts ++ [submodules: true]
|
lock_opts ++ [submodules: true]
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ defmodule Mix.Task do
|
|||||||
# 2. Otherwise we look for it in dependencies.
|
# 2. Otherwise we look for it in dependencies.
|
||||||
# 3. Finally, we compile the current project in hope it is available.
|
# 3. Finally, we compile the current project in hope it is available.
|
||||||
module =
|
module =
|
||||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.check") end) ||
|
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
|
||||||
get_task_or_run(proj, task, fn -> Mix.Project.compile([]) end) ||
|
get_task_or_run(proj, task, fn -> Mix.Project.compile([]) end) ||
|
||||||
get!(task)
|
get!(task)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ defmodule Mix.Tasks.App.Start do
|
|||||||
applications are started in permanent mode
|
applications are started in permanent mode
|
||||||
|
|
||||||
* `:consolidate_protocols` - when `true`, loads consolidated
|
* `:consolidate_protocols` - when `true`, loads consolidated
|
||||||
protocols before start
|
protocols before start. The default value is `true`.
|
||||||
|
|
||||||
* `:elixir` - matches the current elixir version against the
|
* `:elixir` - matches the current elixir version against the
|
||||||
given requirement
|
given requirement
|
||||||
|
|||||||
@@ -56,11 +56,18 @@ defmodule Mix.Tasks.App.Tree do
|
|||||||
|
|
||||||
if opts[:format] == "dot" do
|
if opts[:format] == "dot" do
|
||||||
Mix.Utils.write_dot_graph!("app_tree.dot", "application tree",
|
Mix.Utils.write_dot_graph!("app_tree.dot", "application tree",
|
||||||
{:normal, app}, callback, opts)
|
[{:normal, app}], callback, opts)
|
||||||
Mix.shell.info "Generated \"app_tree.dot\" in current directory.\n" <>
|
"""
|
||||||
"You can use http://www.graphviz.org/ to open it."
|
Generated "app_tree.dot" in the current directory. To generate a PNG:
|
||||||
|
|
||||||
|
dot -Tpng app_tree.dot -o app_tree.png
|
||||||
|
|
||||||
|
For more options see http://www.graphviz.org/.
|
||||||
|
"""
|
||||||
|
|> String.trim_trailing
|
||||||
|
|> Mix.shell.info
|
||||||
else
|
else
|
||||||
Mix.Utils.print_tree({:normal, app}, callback, opts)
|
Mix.Utils.print_tree([{:normal, app}], callback, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ defmodule Mix.Tasks.Clean do
|
|||||||
|
|
||||||
# Loadpaths without checks because compilers may be defined in deps.
|
# Loadpaths without checks because compilers may be defined in deps.
|
||||||
defp loadpaths! do
|
defp loadpaths! do
|
||||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||||
Mix.Task.reenable "loadpaths"
|
Mix.Task.reenable "loadpaths"
|
||||||
|
Mix.Task.reenable "deps.loadpaths"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -90,14 +90,14 @@ defmodule Mix.Tasks.Compile.App do
|
|||||||
best_guess
|
best_guess
|
||||||
end
|
end
|
||||||
|
|
||||||
|
properties = ensure_correct_properties(app, config, properties)
|
||||||
|
|
||||||
# Ensure we always prepend the standard application dependencies
|
# Ensure we always prepend the standard application dependencies
|
||||||
core_apps = [:kernel, :stdlib] ++ language_app(config)
|
|
||||||
properties = Keyword.update!(properties, :applications, fn apps ->
|
properties = Keyword.update!(properties, :applications, fn apps ->
|
||||||
core_apps ++ apps
|
[:kernel, :stdlib] ++ language_app(config) ++ apps
|
||||||
end)
|
end)
|
||||||
|
|
||||||
properties = ensure_correct_properties(app, config, properties)
|
contents = {:application, app, properties}
|
||||||
contents = {:application, app, properties}
|
|
||||||
|
|
||||||
Mix.Project.ensure_structure()
|
Mix.Project.ensure_structure()
|
||||||
File.write!(target, :io_lib.format("~p.", [contents]), [:utf8])
|
File.write!(target, :io_lib.format("~p.", [contents]), [:utf8])
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ defmodule Mix.Tasks.Compile do
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
* `:compilers` - compilers to run, defaults to:
|
* `:compilers` - compilers to run, defaults to:
|
||||||
`[:yeec, :leex, :erlang, :elixir, :xref, :app]`
|
`[:yecc, :leex, :erlang, :elixir, :xref, :app]`
|
||||||
|
|
||||||
* `:consolidate_protocols` - when `true`, runs protocol
|
* `:consolidate_protocols` - when `true`, runs protocol
|
||||||
consolidation via the `compile.protocols` task
|
consolidation via the `compile.protocols` task. The default
|
||||||
|
value is `true`.
|
||||||
|
|
||||||
* `:build_embedded` - when `true`, activates protocol
|
* `:build_embedded` - when `true`, activates protocol
|
||||||
consolidation and does not generate symlinks in builds
|
consolidation and does not generate symlinks in builds
|
||||||
@@ -93,8 +94,9 @@ defmodule Mix.Tasks.Compile do
|
|||||||
|
|
||||||
# Loadpaths without checks because compilers may be defined in deps.
|
# Loadpaths without checks because compilers may be defined in deps.
|
||||||
defp loadpaths! do
|
defp loadpaths! do
|
||||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||||
Mix.Task.reenable "loadpaths"
|
Mix.Task.reenable "loadpaths"
|
||||||
|
Mix.Task.reenable "deps.loadpaths"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp consolidate_protocols? do
|
defp consolidate_protocols? do
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ defmodule Mix.Tasks.Compile.Protocols do
|
|||||||
|
|
||||||
This task is automatically invoked whenever the project
|
This task is automatically invoked whenever the project
|
||||||
enables `:consolidate_protocols` or `:build_embedded` in
|
enables `:consolidate_protocols` or `:build_embedded` in
|
||||||
its configuration.
|
its configuration. As `:consolidate_protocols` is enabled
|
||||||
|
in the default configuration, this is the default case.
|
||||||
|
|
||||||
## Consolidation
|
## Consolidation
|
||||||
|
|
||||||
@@ -44,10 +45,7 @@ defmodule Mix.Tasks.Compile.Protocols do
|
|||||||
output = Mix.Project.consolidation_path(config)
|
output = Mix.Project.consolidation_path(config)
|
||||||
manifest = Path.join(output, @manifest)
|
manifest = Path.join(output, @manifest)
|
||||||
|
|
||||||
protocols_and_impls =
|
protocols_and_impls = protocols_and_impls(config)
|
||||||
unless Mix.Project.umbrella?(config) do
|
|
||||||
protocols_and_impls(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
opts[:force] || Mix.Utils.stale?(Mix.Project.config_files(), [manifest]) ->
|
opts[:force] || Mix.Utils.stale?(Mix.Project.config_files(), [manifest]) ->
|
||||||
@@ -79,10 +77,15 @@ defmodule Mix.Tasks.Compile.Protocols do
|
|||||||
not scm.fetchable?,
|
not scm.fetchable?,
|
||||||
do: opts[:build])
|
do: opts[:build])
|
||||||
|
|
||||||
app = Mix.Project.app_path(config)
|
app =
|
||||||
|
if Mix.Project.umbrella?(config) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[Mix.Project.app_path(config)]
|
||||||
|
end
|
||||||
|
|
||||||
protocols_and_impls =
|
protocols_and_impls =
|
||||||
for path <- [app | deps] do
|
for path <- app ++ deps do
|
||||||
manifest_path = Path.join(path, ".compile.elixir")
|
manifest_path = Path.join(path, ".compile.elixir")
|
||||||
compile_path = Path.join(path, "ebin")
|
compile_path = Path.join(path, "ebin")
|
||||||
Mix.Compilers.Elixir.protocols_and_impls(manifest_path, compile_path)
|
Mix.Compilers.Elixir.protocols_and_impls(manifest_path, compile_path)
|
||||||
@@ -120,10 +123,24 @@ defmodule Mix.Tasks.Compile.Protocols do
|
|||||||
defp consolidate(protocol, paths, output, opts) do
|
defp consolidate(protocol, paths, output, opts) do
|
||||||
impls = Protocol.extract_impls(protocol, paths)
|
impls = Protocol.extract_impls(protocol, paths)
|
||||||
reload(protocol)
|
reload(protocol)
|
||||||
{:ok, binary} = Protocol.consolidate(protocol, impls)
|
case Protocol.consolidate(protocol, impls) do
|
||||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
{:ok, binary} ->
|
||||||
if opts[:verbose] do
|
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
if opts[:verbose] do
|
||||||
|
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we remove a dependency and we have implemented one of its
|
||||||
|
# protocols locally, we will mark the protocol as needing to be
|
||||||
|
# reconsolidated when the implementation is removed even though
|
||||||
|
# the protocol no longer exists. Although most times removing a
|
||||||
|
# dependency will trigger a full recompilation, such won't happen
|
||||||
|
# in umbrella apps with shared build.
|
||||||
|
{:error, :no_beam_info} ->
|
||||||
|
remove_consolidated(protocol, output)
|
||||||
|
if opts[:verbose] do
|
||||||
|
Mix.shell.info "Unavailable #{inspect protocol}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ defmodule Mix.Tasks.Compile.Xref do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp run_xref do
|
defp run_xref do
|
||||||
Mix.Task.run("xref", ["--warnings"])
|
Mix.Task.run("xref", ["warnings"])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp needs_xref?(opts) do
|
defp needs_xref?(opts) do
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
|
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
|
||||||
make?: 1, mix?: 1]
|
make?: 1, mix?: 1]
|
||||||
|
|
||||||
@switches [include_children: :boolean]
|
@switches [include_children: :boolean, force: :boolean]
|
||||||
|
|
||||||
@spec run(OptionParser.argv) :: :ok
|
@spec run(OptionParser.argv) :: :ok
|
||||||
def run(args) do
|
def run(args) do
|
||||||
@@ -44,18 +44,18 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
Mix.Project.get!
|
Mix.Project.get!
|
||||||
|
|
||||||
case OptionParser.parse(args, switches: @switches) do
|
case OptionParser.parse(args, switches: @switches) do
|
||||||
{_, [], _} ->
|
{opts, [], _} ->
|
||||||
# Because this command may be invoked explicitly with
|
# Because this command may be invoked explicitly with
|
||||||
# deps.compile, we simply try to compile any available
|
# deps.compile, we simply try to compile any available
|
||||||
# dependency.
|
# dependency.
|
||||||
compile(Enum.filter(loaded(env: Mix.env), &available?/1))
|
compile(Enum.filter(loaded(env: Mix.env), &available?/1), opts)
|
||||||
{opts, tail, _} ->
|
{opts, tail, _} ->
|
||||||
compile(loaded_by_name(tail, [env: Mix.env] ++ opts))
|
compile(loaded_by_name(tail, [env: Mix.env] ++ opts), opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def compile(deps) do
|
def compile(deps, options \\ []) do
|
||||||
shell = Mix.shell
|
shell = Mix.shell
|
||||||
config = Mix.Project.deps_config
|
config = Mix.Project.deps_config
|
||||||
|
|
||||||
@@ -65,6 +65,8 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
Enum.map(deps, fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
|
Enum.map(deps, fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
|
||||||
check_unavailable!(app, status)
|
check_unavailable!(app, status)
|
||||||
|
|
||||||
|
clean(app, options)
|
||||||
|
|
||||||
compiled? = cond do
|
compiled? = cond do
|
||||||
not is_nil(opts[:compile]) ->
|
not is_nil(opts[:compile]) ->
|
||||||
do_compile dep, config
|
do_compile dep, config
|
||||||
@@ -93,6 +95,12 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
if true in compiled, do: Mix.Dep.Lock.touch_manifest, else: :ok
|
if true in compiled, do: Mix.Dep.Lock.touch_manifest, else: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp clean(app, opts) do
|
||||||
|
if Keyword.get(opts, :force, false) do
|
||||||
|
File.rm_rf! Path.join [Mix.Project.build_path, "lib", Atom.to_string(app)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp touch_fetchable(scm, path) do
|
defp touch_fetchable(scm, path) do
|
||||||
if scm.fetchable? do
|
if scm.fetchable? do
|
||||||
File.mkdir_p!(path)
|
File.mkdir_p!(path)
|
||||||
@@ -125,7 +133,8 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
end
|
end
|
||||||
|
|
||||||
try do
|
try do
|
||||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
|
res = Mix.Task.run("compile", ["--no-deps", "--no-archives-check",
|
||||||
|
"--no-elixir-version-check", "--no-warnings-as-errors"])
|
||||||
:ok in List.wrap(res)
|
:ok in List.wrap(res)
|
||||||
catch
|
catch
|
||||||
kind, reason ->
|
kind, reason ->
|
||||||
@@ -176,13 +185,14 @@ defmodule Mix.Tasks.Deps.Compile do
|
|||||||
Mix.raise "\"#{manager}\" installation failed"
|
Mix.raise "\"#{manager}\" installation failed"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_make(dep, config) do
|
defp do_make(%{opts: opts} = dep, config) do
|
||||||
command = if match?({:win32, _}, :os.type) and File.regular?("Makefile.win") do
|
command =
|
||||||
"nmake /F Makefile.win"
|
if match?({:win32, _}, :os.type) and File.regular?(Path.join(opts[:dest], "Makefile.win")) do
|
||||||
else
|
"nmake /F Makefile.win"
|
||||||
"make"
|
else
|
||||||
end
|
"make"
|
||||||
do_command(dep, config, command, true)
|
end
|
||||||
|
do_command(dep, config, command, true, [{"IS_DEP", "1"}])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
||||||
|
|||||||
@@ -1,32 +1,26 @@
|
|||||||
defmodule Mix.Tasks.Deps.Check do
|
defmodule Mix.Tasks.Deps.Loadpaths do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
import Mix.Dep, only: [loaded_by_name: 2, format_dep: 1, ok?: 1,
|
import Mix.Dep, only: [loaded_by_name: 2, format_dep: 1, ok?: 1,
|
||||||
format_status: 1, check_lock: 1]
|
format_status: 1, check_lock: 1]
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Checks if all dependencies are valid, loading them along
|
Checks and loads all dependencies along the way.
|
||||||
the way.
|
|
||||||
|
|
||||||
If there is an invalid dependency, its status is printed
|
If there is an invalid dependency, its status is printed
|
||||||
before aborting.
|
before aborting.
|
||||||
|
|
||||||
This task is not shown in `mix help` but it is part
|
Although this task does not show up in `mix help`, it is
|
||||||
of the `mix` public API and can be depended on.
|
part of Mix public API and can be depended on.
|
||||||
|
|
||||||
## Command line options
|
## Command line options
|
||||||
|
|
||||||
* `--no-archives-check` - do not check archives
|
* `--no-deps-check` - do not check or compile deps, only load available ones
|
||||||
* `--no-deps-check` - do not check deps
|
|
||||||
* `--no-compile` - do not compile dependencies
|
* `--no-compile` - do not compile dependencies
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec run(OptionParser.argv) :: :ok
|
@spec run(OptionParser.argv) :: :ok
|
||||||
def run(args) do
|
def run(args) do
|
||||||
unless "--no-archives-check" in args do
|
|
||||||
Mix.Task.run "archive.check", args
|
|
||||||
end
|
|
||||||
|
|
||||||
all = Enum.map(Mix.Dep.cached(), &check_lock/1)
|
all = Enum.map(Mix.Dep.cached(), &check_lock/1)
|
||||||
|
|
||||||
unless "--no-deps-check" in args do
|
unless "--no-deps-check" in args do
|
||||||
@@ -39,7 +33,9 @@ defmodule Mix.Tasks.Deps.Check do
|
|||||||
path
|
path
|
||||||
end
|
end
|
||||||
|
|
||||||
prune_deps(load_paths, "--no-deps-check" in args)
|
unless System.get_env("MIX_NO_DEPS") in ~w(1 true) do
|
||||||
|
prune_deps(load_paths, "--no-deps-check" in args)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the build is per environment, we should be able to look
|
# If the build is per environment, we should be able to look
|
||||||
@@ -95,11 +91,16 @@ defmodule Mix.Tasks.Deps.Check do
|
|||||||
|
|
||||||
defp partition([dep | deps], not_ok, compile) do
|
defp partition([dep | deps], not_ok, compile) do
|
||||||
cond do
|
cond do
|
||||||
from_umbrella?(dep) -> partition(deps, not_ok, compile)
|
compilable?(dep) ->
|
||||||
compilable?(dep) -> partition(deps, not_ok, [dep | compile])
|
if from_umbrella?(dep) do
|
||||||
ok?(dep) and local?(dep) -> partition(deps, not_ok, [dep | compile])
|
partition(deps, not_ok, compile)
|
||||||
ok?(dep) -> partition(deps, not_ok, compile)
|
else
|
||||||
true -> partition(deps, [dep | not_ok], compile)
|
partition(deps, not_ok, [dep | compile])
|
||||||
|
end
|
||||||
|
ok?(dep) ->
|
||||||
|
partition(deps, not_ok, compile)
|
||||||
|
true ->
|
||||||
|
partition(deps, [dep | not_ok], compile)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ defmodule Mix.Tasks.Deps.Check do
|
|||||||
defp compilable?(%Mix.Dep{status: {:noappfile, _}}), do: true
|
defp compilable?(%Mix.Dep{status: {:noappfile, _}}), do: true
|
||||||
defp compilable?(%Mix.Dep{status: {:scmlock, _}}), do: true
|
defp compilable?(%Mix.Dep{status: {:scmlock, _}}), do: true
|
||||||
defp compilable?(%Mix.Dep{status: :compile}), do: true
|
defp compilable?(%Mix.Dep{status: :compile}), do: true
|
||||||
defp compilable?(%Mix.Dep{}), do: false
|
defp compilable?(%Mix.Dep{} = dep), do: ok?(dep) and local?(dep)
|
||||||
|
|
||||||
defp show_not_ok!([]) do
|
defp show_not_ok!([]) do
|
||||||
:ok
|
:ok
|
||||||
@@ -51,12 +51,19 @@ defmodule Mix.Tasks.Deps.Tree do
|
|||||||
|
|
||||||
if opts[:format] == "dot" do
|
if opts[:format] == "dot" do
|
||||||
callback = callback(&format_dot/1, deps, opts)
|
callback = callback(&format_dot/1, deps, opts)
|
||||||
Mix.Utils.write_dot_graph!("deps_tree.dot", "dependency tree", root, callback, opts)
|
Mix.Utils.write_dot_graph!("deps_tree.dot", "dependency tree", [root], callback, opts)
|
||||||
Mix.shell.info "Generated \"deps_tree.dot\" in current directory.\n" <>
|
"""
|
||||||
"You can use http://www.graphviz.org/ to open it."
|
Generated "deps_tree.dot" in the current directory. To generate a PNG:
|
||||||
|
|
||||||
|
dot -Tpng deps_tree.dot -o deps_tree.png
|
||||||
|
|
||||||
|
For more options see http://www.graphviz.org/.
|
||||||
|
"""
|
||||||
|
|> String.trim_trailing
|
||||||
|
|> Mix.shell.info
|
||||||
else
|
else
|
||||||
callback = callback(&format_tree/1, deps, opts)
|
callback = callback(&format_tree/1, deps, opts)
|
||||||
Mix.Utils.print_tree(root, callback, opts)
|
Mix.Utils.print_tree([root], callback, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Deps.Unlock do
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@switches [all: :boolean, unused: :boolean]
|
@switches [all: :boolean, unused: :boolean, filter: :string]
|
||||||
|
|
||||||
@spec run(OptionParser.argv) :: :ok
|
@spec run(OptionParser.argv) :: :ok
|
||||||
def run(args) do
|
def run(args) do
|
||||||
@@ -29,6 +29,27 @@ defmodule Mix.Tasks.Deps.Unlock do
|
|||||||
opts[:unused] ->
|
opts[:unused] ->
|
||||||
apps = Mix.Dep.loaded([]) |> Enum.map(& &1.app)
|
apps = Mix.Dep.loaded([]) |> Enum.map(& &1.app)
|
||||||
Mix.Dep.Lock.read() |> Map.take(apps) |> Mix.Dep.Lock.write()
|
Mix.Dep.Lock.read() |> Map.take(apps) |> Mix.Dep.Lock.write()
|
||||||
|
filter = opts[:filter] ->
|
||||||
|
lock = Mix.Dep.Lock.read
|
||||||
|
apps = Map.keys(lock)
|
||||||
|
|
||||||
|
unlock =
|
||||||
|
apps
|
||||||
|
|> Enum.filter(&String.contains?("#{&1}", filter))
|
||||||
|
|
||||||
|
if unlock == [] do
|
||||||
|
Mix.shell.error "warning: no dependencies were matched"
|
||||||
|
else
|
||||||
|
lock =
|
||||||
|
Enum.reject(lock, fn({app, _}) ->
|
||||||
|
app in unlock
|
||||||
|
end)
|
||||||
|
Mix.Dep.Lock.write(lock)
|
||||||
|
Mix.shell.info """
|
||||||
|
Unlocked deps:
|
||||||
|
* #{Enum.join(unlock, "\n* ")}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
apps != [] ->
|
apps != [] ->
|
||||||
lock =
|
lock =
|
||||||
|
|||||||
+13
-17
@@ -6,6 +6,8 @@ defmodule Mix.Tasks.Do do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
Executes the tasks separated by comma.
|
Executes the tasks separated by comma.
|
||||||
|
|
||||||
|
The comma should be followed by a space.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
The example below prints the available compilers and
|
The example below prints the available compilers and
|
||||||
@@ -24,27 +26,21 @@ defmodule Mix.Tasks.Do do
|
|||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def gather_commands(args) do
|
def gather_commands(args) do
|
||||||
gather_commands(args, [], [])
|
gather_commands args, [], []
|
||||||
end
|
end
|
||||||
|
|
||||||
defp gather_commands([], current, commands) do
|
def gather_commands([head | rest], current, acc)
|
||||||
[current | commands]
|
when binary_part(head, byte_size(head), -1) == "," do
|
||||||
|> Enum.reject(&(&1 == []))
|
part = binary_part(head, 0, byte_size(head) - 1)
|
||||||
|> Enum.map(&Enum.reverse(&1))
|
current = Enum.reverse([part | current])
|
||||||
|> Enum.reverse
|
gather_commands rest, [], [current | acc]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp gather_commands([arg | rest], current, commands) do
|
def gather_commands([head | rest], current, acc) do
|
||||||
case String.split(arg, ",", parts: 2) do
|
gather_commands rest, [head | current], acc
|
||||||
[arg] ->
|
|
||||||
gather_commands(rest, [arg | current], commands)
|
|
||||||
[left, right] ->
|
|
||||||
rest = append_unless_empty(right, rest)
|
|
||||||
current = append_unless_empty(left, current)
|
|
||||||
gather_commands(rest, [], [current | commands])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp append_unless_empty("", list), do: list
|
def gather_commands([], current, acc) do
|
||||||
defp append_unless_empty(h, list), do: [h | list]
|
Enum.reverse [Enum.reverse(current) | acc]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
defmodule Mix.Tasks.Escript do
|
defmodule Mix.Tasks.Escript do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
@shortdoc "List installed escripts."
|
@shortdoc "Lists installed escripts"
|
||||||
|
|
||||||
@moduledoc ~S"""
|
@moduledoc ~S"""
|
||||||
Lists all installed escripts.
|
Lists all installed escripts.
|
||||||
|
|||||||
@@ -105,8 +105,9 @@ defmodule Mix.Tasks.Help do
|
|||||||
|
|
||||||
# Loadpaths without checks because tasks may be defined in deps.
|
# Loadpaths without checks because tasks may be defined in deps.
|
||||||
defp loadpaths! do
|
defp loadpaths! do
|
||||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||||
Mix.Task.reenable "loadpaths"
|
Mix.Task.reenable "loadpaths"
|
||||||
|
Mix.Task.reenable "deps.loadpaths"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load_tasks() do
|
defp load_tasks() do
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ defmodule Mix.Tasks.Loadpaths do
|
|||||||
## Command line options
|
## Command line options
|
||||||
|
|
||||||
* `--no-archives-check` - do not check archive
|
* `--no-archives-check` - do not check archive
|
||||||
* `--no-deps-check` - do not check dependencies (also implies --no-archives-check)
|
* `--no-deps-check` - do not check dependencies
|
||||||
* `--no-elixir-version-check` - do not check Elixir version
|
* `--no-elixir-version-check` - do not check Elixir version
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@spec run(OptionParser.argv) :: :ok
|
@spec run(OptionParser.argv) :: :ok
|
||||||
|
|
||||||
def run(args) do
|
def run(args) do
|
||||||
config = Mix.Project.config
|
config = Mix.Project.config
|
||||||
|
|
||||||
@@ -25,10 +26,14 @@ defmodule Mix.Tasks.Loadpaths do
|
|||||||
check_elixir_version(config, args)
|
check_elixir_version(config, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# --no-deps is used only internally. It has not purpose
|
unless "--no-archives-check" in args do
|
||||||
# from Mix.CLI because the CLI itself already loads deps.
|
Mix.Task.run "archive.check", args
|
||||||
|
end
|
||||||
|
|
||||||
|
# --no-deps is used only internally. It has no purpose
|
||||||
|
# from Mix.CLI because running a task may load deps.
|
||||||
unless "--no-deps" in args do
|
unless "--no-deps" in args do
|
||||||
Mix.Task.run "deps.check", args
|
Mix.Task.run "deps.loadpaths", args
|
||||||
end
|
end
|
||||||
|
|
||||||
if config[:app] do
|
if config[:app] do
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ defmodule Mix.Tasks.New do
|
|||||||
elixir: "~> <%= @version %>",
|
elixir: "~> <%= @version %>",
|
||||||
build_embedded: Mix.env == :prod,
|
build_embedded: Mix.env == :prod,
|
||||||
start_permanent: Mix.env == :prod,
|
start_permanent: Mix.env == :prod,
|
||||||
deps: deps]
|
deps: deps()]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Configuration for the OTP application
|
# Configuration for the OTP application
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ defmodule Mix.Tasks.Test do
|
|||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
def start(compile_path, opts) do
|
def start(compile_path, opts) do
|
||||||
Mix.shell.info "Cover compiling modules ... "
|
Mix.shell.info "Cover compiling modules ..."
|
||||||
_ = :cover.start
|
_ = :cover.start
|
||||||
|
|
||||||
case :cover.compile_beam_directory(compile_path |> to_charlist) do
|
case :cover.compile_beam_directory(compile_path |> to_charlist) do
|
||||||
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Test do
|
|||||||
output = opts[:output]
|
output = opts[:output]
|
||||||
|
|
||||||
fn() ->
|
fn() ->
|
||||||
Mix.shell.info "\nGenerating cover results ... "
|
Mix.shell.info "\nGenerating cover results ..."
|
||||||
File.mkdir_p!(output)
|
File.mkdir_p!(output)
|
||||||
Enum.each :cover.modules, fn(mod) ->
|
Enum.each :cover.modules, fn(mod) ->
|
||||||
{:ok, _} = :cover.analyse_to_file(mod, '#{output}/#{mod}.html', [:html])
|
{:ok, _} = :cover.analyse_to_file(mod, '#{output}/#{mod}.html', [:html])
|
||||||
@@ -67,6 +67,9 @@ defmodule Mix.Tasks.Test do
|
|||||||
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
||||||
* `--stale` - run only tests which reference modules that changed since the
|
* `--stale` - run only tests which reference modules that changed since the
|
||||||
last `test --stale`. You can read more about this option in the "Stale" section below.
|
last `test --stale`. You can read more about this option in the "Stale" section below.
|
||||||
|
* `--listen-on-stdin` - run tests, and then listen on stdin. Receiving a newline will
|
||||||
|
result in the tests being run again. Very useful when combined with `--stale` and
|
||||||
|
external commands which produce output on stdout upon file system modification.
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
|
|
||||||
@@ -164,7 +167,7 @@ defmodule Mix.Tasks.Test do
|
|||||||
exclude: :keep, seed: :integer, only: :keep, compile: :boolean,
|
exclude: :keep, seed: :integer, only: :keep, compile: :boolean,
|
||||||
start: :boolean, timeout: :integer, raise: :boolean,
|
start: :boolean, timeout: :integer, raise: :boolean,
|
||||||
deps_check: :boolean, archives_check: :boolean, elixir_version_check: :boolean,
|
deps_check: :boolean, archives_check: :boolean, elixir_version_check: :boolean,
|
||||||
stale: :boolean]
|
stale: :boolean, listen_on_stdin: :boolean]
|
||||||
|
|
||||||
@cover [output: "cover", tool: Cover]
|
@cover [output: "cover", tool: Cover]
|
||||||
|
|
||||||
@@ -172,6 +175,15 @@ defmodule Mix.Tasks.Test do
|
|||||||
def run(args) do
|
def run(args) do
|
||||||
{opts, files} = OptionParser.parse!(args, strict: @switches)
|
{opts, files} = OptionParser.parse!(args, strict: @switches)
|
||||||
|
|
||||||
|
if opts[:listen_on_stdin] do
|
||||||
|
System.at_exit fn _ ->
|
||||||
|
IO.gets(:stdio, "")
|
||||||
|
Mix.shell.info "Restarting..."
|
||||||
|
:init.restart()
|
||||||
|
:timer.sleep(:infinity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
unless System.get_env("MIX_ENV") || Mix.env == :test do
|
unless System.get_env("MIX_ENV") || Mix.env == :test do
|
||||||
Mix.raise "\"mix test\" is running on environment \"#{Mix.env}\". If you are " <>
|
Mix.raise "\"mix test\" is running on environment \"#{Mix.env}\". If you are " <>
|
||||||
"running tests along another task, please set MIX_ENV explicitly"
|
"running tests along another task, please set MIX_ENV explicitly"
|
||||||
|
|||||||
+227
-24
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Xref do
|
|||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
alias Mix.Tasks.Compile.Elixir, as: E
|
alias Mix.Tasks.Compile.Elixir, as: E
|
||||||
import Mix.Compilers.Elixir, only: [read_manifest: 2, source: 1, source: 2]
|
import Mix.Compilers.Elixir, only: [read_manifest: 2, source: 1, source: 2, module: 1]
|
||||||
|
|
||||||
@shortdoc "Performs cross reference checks"
|
@shortdoc "Performs cross reference checks"
|
||||||
@recursive true
|
@recursive true
|
||||||
@@ -12,23 +12,82 @@ defmodule Mix.Tasks.Xref do
|
|||||||
|
|
||||||
## Xref modes
|
## Xref modes
|
||||||
|
|
||||||
The following options control the information xref can emit.
|
The `xref` task expects a mode as first argument:
|
||||||
|
|
||||||
* `--warnings` - prints warnings for violated cross reference checks
|
mix xref MODE
|
||||||
* `--unreachable` - prints all unreachable "file:line: module.function/arity" entries
|
|
||||||
* `--callers` - prints all references of given `Module`, `Module.function`, or
|
|
||||||
`Module.function/arity`
|
|
||||||
|
|
||||||
## Command line options
|
All available modes are discussed below.
|
||||||
|
|
||||||
|
### warnings
|
||||||
|
|
||||||
|
Prints warnings for violated cross reference checks:
|
||||||
|
|
||||||
|
mix xref warnings
|
||||||
|
|
||||||
|
This is the mode used by Mix during compilation.
|
||||||
|
|
||||||
|
### unreachable
|
||||||
|
|
||||||
|
Prints all unreachable "file:line: module.function/arity" entries:
|
||||||
|
|
||||||
|
mix xref unreachable
|
||||||
|
|
||||||
|
The "file:line" represents the file and line a call to an unknown
|
||||||
|
"module.function/arity" is made.
|
||||||
|
|
||||||
|
### callers CALLEE
|
||||||
|
|
||||||
|
Prints all callers of the given `CALLEE`, which can be one of: `Module`,
|
||||||
|
`Module.function`, or `Module.function/arity`. Examples:
|
||||||
|
|
||||||
|
mix xref callers MyMod
|
||||||
|
mix xref callers MyMod.fun
|
||||||
|
mix xref callers MyMod.fun/3
|
||||||
|
|
||||||
|
### graph
|
||||||
|
|
||||||
|
Prints a file dependency graph where an edge from `A` to `B` indicates
|
||||||
|
that `A` depends on `B`.
|
||||||
|
|
||||||
|
mix xref graph --format dot
|
||||||
|
|
||||||
|
The following options are accepted:
|
||||||
|
|
||||||
|
* `--exclude` - paths to exclude
|
||||||
|
|
||||||
|
* `--source` - display all files that the given source file references (directly or indirectly)
|
||||||
|
|
||||||
|
* `--sink` - display all files that reference the given file (directly or indirectly)
|
||||||
|
|
||||||
|
* `--format` - can be set to one of:
|
||||||
|
|
||||||
|
* `pretty` - use Unicode codepoints for formatting the graph.
|
||||||
|
This is the default except on Windows
|
||||||
|
|
||||||
|
* `plain` - do not use Unicode codepoints for formatting the graph.
|
||||||
|
This is the default on Windows
|
||||||
|
|
||||||
|
* `dot` - produces a DOT graph description in `xref_graph.dot` in the
|
||||||
|
current directory. Warning: this will override any previously generated file
|
||||||
|
|
||||||
|
The `--source` and `--sink` options are particularly useful when trying to understand how
|
||||||
|
the modules in a particular file interact with the whole system.
|
||||||
|
|
||||||
|
## Shared options
|
||||||
|
|
||||||
|
Those options are shared across all modes:
|
||||||
|
|
||||||
* `--no-compile` - do not compile even if files require compilation
|
* `--no-compile` - do not compile even if files require compilation
|
||||||
|
|
||||||
* `--no-deps-check` - do not check dependencies
|
* `--no-deps-check` - do not check dependencies
|
||||||
|
|
||||||
* `--no-archives-check` - do not check archives
|
* `--no-archives-check` - do not check archives
|
||||||
|
|
||||||
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All configuration for Xref should be placed under the key `:xref`.
|
All configuration for Xref should be placed under the key `:xref`.
|
||||||
|
|
||||||
* `:exclude` - a list of modules and `{module, function, arity}` tuples to ignore when checking
|
* `:exclude` - a list of modules and `{module, function, arity}` tuples to ignore when checking
|
||||||
cross references. For example: `[MissingModule, {MissingModule2, :missing_func, 2}]`
|
cross references. For example: `[MissingModule, {MissingModule2, :missing_func, 2}]`
|
||||||
@@ -36,31 +95,34 @@ defmodule Mix.Tasks.Xref do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@switches [compile: :boolean, deps_check: :boolean, archives_check: :boolean,
|
@switches [compile: :boolean, deps_check: :boolean, archives_check: :boolean,
|
||||||
warnings: :boolean, unreachable: :boolean, elixir_version_check: :boolean,
|
elixir_version_check: :boolean, exclude: :keep, format: :string,
|
||||||
callers: :string]
|
source: :string, sink: :string]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Runs this task.
|
Runs this task.
|
||||||
"""
|
"""
|
||||||
@spec run(OptionParser.argv) :: :ok | :error
|
@spec run(OptionParser.argv) :: :ok | :error
|
||||||
def run(args) do
|
def run(args) do
|
||||||
{opts, _} =
|
{opts, args} =
|
||||||
OptionParser.parse!(args, strict: @switches)
|
OptionParser.parse!(args, strict: @switches)
|
||||||
|
|
||||||
|
Mix.Task.run("loadpaths")
|
||||||
|
|
||||||
if Keyword.get(opts, :compile, true) do
|
if Keyword.get(opts, :compile, true) do
|
||||||
Mix.Task.run("compile")
|
Mix.Task.run("compile")
|
||||||
end
|
end
|
||||||
|
|
||||||
modes = [:warnings, :unreachable, :callers]
|
case args do
|
||||||
case Keyword.take(opts, modes) do
|
["warnings"] ->
|
||||||
[warnings: true] ->
|
|
||||||
warnings()
|
warnings()
|
||||||
[unreachable: true] ->
|
["unreachable"] ->
|
||||||
unreachable()
|
unreachable()
|
||||||
[callers: callee] ->
|
["callers", callee] ->
|
||||||
callers(callee)
|
callers(callee)
|
||||||
|
["graph"] ->
|
||||||
|
graph(opts)
|
||||||
_ ->
|
_ ->
|
||||||
Mix.raise "xref expects exactly one of the following modes: --warnings, --unreachable, --callers"
|
Mix.raise "xref doesn't support this command, see mix help xref for more information"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -90,11 +152,16 @@ defmodule Mix.Tasks.Xref do
|
|||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp graph(opts) do
|
||||||
|
write_graph(file_references(), excluded(opts), opts)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
## Unreachable
|
## Unreachable
|
||||||
|
|
||||||
defp unreachable(pair_fun) do
|
defp unreachable(pair_fun) do
|
||||||
excludes = excludes()
|
excludes = excludes()
|
||||||
|
|
||||||
each_source_entries(&source_warnings(&1, excludes), pair_fun)
|
each_source_entries(&source_warnings(&1, excludes), pair_fun)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,20 +169,38 @@ defmodule Mix.Tasks.Xref do
|
|||||||
source(runtime_dispatches: runtime_dispatches) = source
|
source(runtime_dispatches: runtime_dispatches) = source
|
||||||
|
|
||||||
for {module, func_arity_lines} <- runtime_dispatches,
|
for {module, func_arity_lines} <- runtime_dispatches,
|
||||||
|
exports = load_exports(module),
|
||||||
{{func, arity}, lines} <- func_arity_lines,
|
{{func, arity}, lines} <- func_arity_lines,
|
||||||
warning = unreachable_mfa(module, func, arity, lines, excludes),
|
warning = unreachable_mfa(exports, module, func, arity, lines, excludes),
|
||||||
do: warning
|
do: warning
|
||||||
end
|
end
|
||||||
|
|
||||||
defp unreachable_mfa(module, func, arity, lines, excludes) do
|
defp load_exports(module) do
|
||||||
|
if :code.is_loaded(module) do
|
||||||
|
# If the module is loaded, we will use the faster function_exported?/3 check
|
||||||
|
module
|
||||||
|
else
|
||||||
|
# Otherwise we get all exports from :beam_lib to avoid loading modules
|
||||||
|
with file when is_list(file) <- :code.which(module),
|
||||||
|
{:ok, {^module, [exports: exports]}} <- :beam_lib.chunks(file, [:exports]) do
|
||||||
|
exports
|
||||||
|
else
|
||||||
|
_ -> :unknown_module
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp unreachable_mfa(exports, module, func, arity, lines, excludes) do
|
||||||
cond do
|
cond do
|
||||||
excluded?(module, func, arity, excludes) ->
|
excluded?(module, func, arity, excludes) ->
|
||||||
nil
|
nil
|
||||||
skip?(module, func, arity) ->
|
skip?(module, func, arity) ->
|
||||||
nil
|
nil
|
||||||
not Code.ensure_loaded?(module) ->
|
exports == :unknown_module ->
|
||||||
{Enum.sort(lines), :unknown_module, module, func, arity}
|
{Enum.sort(lines), :unknown_module, module, func, arity}
|
||||||
not function_exported?(module, func, arity) ->
|
is_atom(exports) and not function_exported?(module, func, arity) ->
|
||||||
|
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||||
|
is_list(exports) and not {func, arity} in exports ->
|
||||||
{Enum.sort(lines), :unknown_function, module, func, arity}
|
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||||
true ->
|
true ->
|
||||||
nil
|
nil
|
||||||
@@ -210,7 +295,7 @@ defmodule Mix.Tasks.Xref do
|
|||||||
defp source_calls_for_filter(source, filter) do
|
defp source_calls_for_filter(source, filter) do
|
||||||
runtime_dispatches = source(source, :runtime_dispatches)
|
runtime_dispatches = source(source, :runtime_dispatches)
|
||||||
compile_dispatches = source(source, :compile_dispatches)
|
compile_dispatches = source(source, :compile_dispatches)
|
||||||
dispatches = Stream.concat(runtime_dispatches, compile_dispatches)
|
dispatches = runtime_dispatches ++ compile_dispatches
|
||||||
|
|
||||||
calls =
|
calls =
|
||||||
for {module, func_arity_lines} <- dispatches,
|
for {module, func_arity_lines} <- dispatches,
|
||||||
@@ -279,12 +364,130 @@ defmodule Mix.Tasks.Xref do
|
|||||||
|
|
||||||
defp raise_invalid_callee(callee) do
|
defp raise_invalid_callee(callee) do
|
||||||
message =
|
message =
|
||||||
"xref --callers expects Module, Module.function, or Module.function/arity, got: " <>
|
"xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: " <>
|
||||||
callee
|
callee
|
||||||
|
|
||||||
Mix.raise message
|
Mix.raise message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
## Graph helpers
|
||||||
|
|
||||||
|
defp excluded(opts) do
|
||||||
|
Keyword.get_values(opts, :exclude)
|
||||||
|
|> Enum.flat_map(&[{&1, nil}, {&1, "(compile)"}, {&1, "(runtime)"}])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp file_references() do
|
||||||
|
module_sources =
|
||||||
|
for manifest <- E.manifests(),
|
||||||
|
manifest_data = read_manifest(manifest, ""),
|
||||||
|
module(module: module, source: source) <- manifest_data,
|
||||||
|
source = Enum.find(manifest_data, &match?(source(source: ^source), &1)),
|
||||||
|
do: {module, source},
|
||||||
|
into: %{}
|
||||||
|
|
||||||
|
all_modules = MapSet.new(module_sources, &elem(&1, 0))
|
||||||
|
|
||||||
|
Map.new module_sources, fn {module, source} ->
|
||||||
|
source(runtime_references: runtime, compile_references: compile, source: file) = source
|
||||||
|
compile_references =
|
||||||
|
compile
|
||||||
|
|> MapSet.new()
|
||||||
|
|> MapSet.delete(module)
|
||||||
|
|> MapSet.intersection(all_modules)
|
||||||
|
|> Enum.filter(&module_sources[&1] != source)
|
||||||
|
|> Enum.map(&{source(module_sources[&1], :source), "(compile)"})
|
||||||
|
|
||||||
|
runtime_references =
|
||||||
|
runtime
|
||||||
|
|> MapSet.new()
|
||||||
|
|> MapSet.delete(module)
|
||||||
|
|> MapSet.intersection(all_modules)
|
||||||
|
|> Enum.filter(&module_sources[&1] != source)
|
||||||
|
|> Enum.map(&{source(module_sources[&1], :source), nil})
|
||||||
|
|
||||||
|
{file, compile_references ++ runtime_references}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp write_graph(file_references, excluded, opts) do
|
||||||
|
{root, file_references} =
|
||||||
|
case {opts[:source], opts[:sink]} do
|
||||||
|
{nil, nil} ->
|
||||||
|
{Enum.map(file_references, &{elem(&1, 0), nil}) -- excluded, file_references}
|
||||||
|
|
||||||
|
{source, nil} ->
|
||||||
|
if file_references[source] do
|
||||||
|
{[{source, nil}], file_references}
|
||||||
|
else
|
||||||
|
Mix.raise "Source could not be found: #{source}"
|
||||||
|
end
|
||||||
|
|
||||||
|
{nil, sink} ->
|
||||||
|
if file_references[sink] do
|
||||||
|
file_references = filter_for_sink(file_references, sink)
|
||||||
|
roots =
|
||||||
|
file_references
|
||||||
|
|> Map.delete(sink)
|
||||||
|
|> Enum.map(&{elem(&1, 0), nil})
|
||||||
|
{roots -- excluded, file_references}
|
||||||
|
else
|
||||||
|
Mix.raise "Sink could not be found: #{sink}"
|
||||||
|
end
|
||||||
|
|
||||||
|
{_, _} ->
|
||||||
|
Mix.raise "mix xref graph expects only one of --source and --sink"
|
||||||
|
end
|
||||||
|
|
||||||
|
callback =
|
||||||
|
fn {file, type} ->
|
||||||
|
children = Map.get(file_references, file, [])
|
||||||
|
{{file, type}, children -- excluded}
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[:format] == "dot" do
|
||||||
|
Mix.Utils.write_dot_graph!("xref_graph.dot", "xref graph",
|
||||||
|
root, callback, opts)
|
||||||
|
"""
|
||||||
|
Generated "xref_graph.dot" in the current directory. To generate a PNG:
|
||||||
|
|
||||||
|
dot -Tpng xref_graph.dot -o xref_graph.png
|
||||||
|
|
||||||
|
For more options see http://www.graphviz.org/.
|
||||||
|
"""
|
||||||
|
|> String.trim_trailing()
|
||||||
|
|> Mix.shell.info()
|
||||||
|
else
|
||||||
|
Mix.Utils.print_tree(root, callback, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter_for_sink(file_references, sink) do
|
||||||
|
file_references
|
||||||
|
|> invert_references()
|
||||||
|
|> do_filter_for_sink([{sink, nil}], %{})
|
||||||
|
|> invert_references()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_filter_for_sink(file_references, new_nodes, acc) do
|
||||||
|
Enum.reduce new_nodes, acc, fn {new_node_name, _type}, acc ->
|
||||||
|
new_nodes = file_references[new_node_name]
|
||||||
|
if acc[new_node_name] || !new_nodes do
|
||||||
|
acc
|
||||||
|
else
|
||||||
|
do_filter_for_sink(file_references, new_nodes, Map.put(acc, new_node_name, new_nodes))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp invert_references(file_references) do
|
||||||
|
Enum.reduce file_references, %{}, fn {file, references}, acc ->
|
||||||
|
Enum.reduce references, acc, fn {reference, type}, acc ->
|
||||||
|
Map.update(acc, reference, [{file, type}], &[{file, type} | &1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
defp each_source_entries(entries_fun, pair_fun) do
|
defp each_source_entries(entries_fun, pair_fun) do
|
||||||
|
|||||||
+48
-34
@@ -142,31 +142,40 @@ defmodule Mix.Utils do
|
|||||||
end) |> Enum.uniq
|
end) |> Enum.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type tree_node :: {name :: String.Chars.t, edge_info :: String.Chars.t}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Prints the given tree according to the callback.
|
Prints the given tree according to the callback.
|
||||||
|
|
||||||
The callback will be invoked for each node and it
|
The callback will be invoked for each node and it
|
||||||
must either return `{printed, children}` tuple or
|
must return a `{printed, children}` tuple.
|
||||||
`false` if the given node must not be printed.
|
|
||||||
"""
|
"""
|
||||||
@spec print_tree(term, (term -> {String.t, [term]}), Keyword.t) :: :ok
|
@spec print_tree([tree_node], (tree_node -> {tree_node, [tree_node]}), Keyword.t) :: :ok
|
||||||
def print_tree(root, callback, opts \\ []) do
|
def print_tree(nodes, callback, opts \\ []) do
|
||||||
pretty =
|
pretty =
|
||||||
case Keyword.get(opts, :format) do
|
case Keyword.get(opts, :format) do
|
||||||
"pretty" -> true
|
"pretty" -> true
|
||||||
"plain" -> false
|
"plain" -> false
|
||||||
_ -> elem(:os.type, 0) != :win32
|
_ -> elem(:os.type, 0) != :win32
|
||||||
end
|
end
|
||||||
print_tree([root], [], pretty, callback)
|
print_tree(nodes, [], nil, MapSet.new(), pretty, callback)
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp print_tree([], _depth, _pretty, _callback), do: :ok
|
defp print_tree([], _depth, _parent, seen, _pretty, _callback), do: seen
|
||||||
defp print_tree([node | nodes], depth, pretty, callback) do
|
defp print_tree([node | nodes], depth, parent, seen, pretty, callback) do
|
||||||
{{name, info}, children} = callback.(node)
|
{{name, info}, children} = callback.(node)
|
||||||
space = if info, do: " ", else: ""
|
key = {parent, name}
|
||||||
Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{name}#{space}#{info}")
|
|
||||||
print_tree(children, [(nodes != []) | depth], pretty, callback)
|
if MapSet.member?(seen, key) do
|
||||||
print_tree(nodes, depth, pretty, callback)
|
seen
|
||||||
|
else
|
||||||
|
space = if info, do: " ", else: ""
|
||||||
|
Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{name}#{space}#{info}")
|
||||||
|
seen = print_tree(children, [(nodes != []) | depth], name, MapSet.put(seen, key), pretty, callback)
|
||||||
|
print_tree(nodes, depth, parent, seen, pretty, callback)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp depth(_pretty, []), do: ""
|
defp depth(_pretty, []), do: ""
|
||||||
@@ -188,37 +197,42 @@ defmodule Mix.Utils do
|
|||||||
Outputs the given tree according to the callback as a DOT graph.
|
Outputs the given tree according to the callback as a DOT graph.
|
||||||
|
|
||||||
The callback will be invoked for each node and it
|
The callback will be invoked for each node and it
|
||||||
must either return `{printed, children}` tuple or
|
must return a `{printed, children}` tuple.
|
||||||
`false` if the given node must not be printed.
|
|
||||||
"""
|
"""
|
||||||
@spec write_dot_graph!(Path.t, String.t, term, (term -> {String.t, [term]}), Keyword.t) :: :ok
|
@spec write_dot_graph!(Path.t, String.t, [tree_node], (tree_node -> {tree_node, [tree_node]}), Keyword.t) :: :ok
|
||||||
def write_dot_graph!(path, title, root, callback, _opts \\ []) do
|
def write_dot_graph!(path, title, nodes, callback, _opts \\ []) do
|
||||||
{{parent, _}, children} = callback.(root)
|
{dot, _} = build_dot_graph(make_ref(), nodes, MapSet.new(), callback)
|
||||||
{dot, _} = build_dot_graph(parent, children, %{}, callback)
|
|
||||||
File.write! path, "digraph \"#{title}\" {\n#{dot}}\n"
|
File.write! path, "digraph \"#{title}\" {\n#{dot}}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_dot_graph(_parent, [], seen, _callback), do: {"", seen}
|
defp build_dot_graph(_parent, [], seen, _callback), do: {"", seen}
|
||||||
defp build_dot_graph(parent, [node | nodes], seen, callback) do
|
defp build_dot_graph(parent, [node | nodes], seen, callback) do
|
||||||
{{name, edge_info}, children} = callback.(node)
|
{{name, edge_info}, children} = callback.(node)
|
||||||
{current, seen} = build_dot_current(parent, name, edge_info, seen)
|
key = {parent, name}
|
||||||
{children, seen} = build_dot_graph(name, children, seen, callback)
|
|
||||||
{siblings, seen} = build_dot_graph(parent, nodes, seen, callback)
|
if MapSet.member?(seen, key) do
|
||||||
{current <> children <> siblings, seen}
|
{"", seen}
|
||||||
|
else
|
||||||
|
seen = MapSet.put(seen, key)
|
||||||
|
current = build_dot_current(parent, name, edge_info)
|
||||||
|
{children, seen} = build_dot_graph(name, children, seen, callback)
|
||||||
|
{siblings, seen} = build_dot_graph(parent, nodes, seen, callback)
|
||||||
|
{current <> children <> siblings, seen}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_dot_current(parent, name, edge_info, seen) do
|
defp build_dot_current(parent, name, edge_info) do
|
||||||
key = {parent, name}
|
edge_info =
|
||||||
case seen do
|
if edge_info do
|
||||||
%{^key => _} ->
|
~s( [label="#{edge_info}"])
|
||||||
{"", seen}
|
end
|
||||||
%{} when is_nil(edge_info) ->
|
|
||||||
{~s( "#{parent}" -> "#{name}"\n),
|
parent =
|
||||||
Map.put(seen, key, true),}
|
unless is_reference(parent) do
|
||||||
%{} ->
|
~s("#{parent}" -> )
|
||||||
{~s( "#{parent}" -> "#{name}" [label=\"#{edge_info}\"]\n),
|
end
|
||||||
Map.put(seen, key, true)}
|
|
||||||
end
|
~s( #{parent}"#{name}"#{edge_info}\n)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
git_repo
|
git_repo
|
||||||
|
git_sparse_repo
|
||||||
deps_on_git_repo
|
deps_on_git_repo
|
||||||
git_rebar
|
git_rebar
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
{sub_dirs, ["apps/*"]}.
|
{sub_dirs, ["apps/*"]}.
|
||||||
|
|
||||||
{deps, [
|
|
||||||
{git_rebar, "0.1..*", {git, "../../test/fixtures/git_rebar", master}}
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{overrides, [{add, rebar_dep, [{src_dirs, ["apps/rebar_dep/src"]}]}]}.
|
{overrides, [{add, rebar_dep, [{src_dirs, ["apps/rebar_dep/src"]}]}]}.
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
CONFIG ++ [{'SCRIPT', SCRIPT}].
|
CONFIG ++ [{'SCRIPT', SCRIPT}] ++ [{deps, [{git_rebar, "0.1..*", {git, filename:absname("../../test/fixtures/git_rebar"), master}}]}].
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user