Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27fdc68ff4 | ||
|
|
2d4722a12f | ||
|
|
e002ac55b2 | ||
|
|
fb89d5548d | ||
|
|
8618d8effa | ||
|
|
bd1c3ab8d7 | ||
|
|
2110342661 | ||
|
|
8533df25a9 | ||
|
|
9e8ac51fb3 | ||
|
|
451b8eb9a3 | ||
|
|
23742f1237 | ||
|
|
3f9ffbeb82 | ||
|
|
0397a46bb7 | ||
|
|
b87e58e7e3 | ||
|
|
6cb09a77e6 | ||
|
|
12edab720c | ||
|
|
74f9a34015 | ||
|
|
49af97a800 | ||
|
|
b806ee8559 | ||
|
|
7d09c2b857 | ||
|
|
f38646781b | ||
|
|
b5777de582 | ||
|
|
67f77ec1cd | ||
|
|
8a91f648a8 | ||
|
|
7f61b0ff05 | ||
|
|
c830fde31e | ||
|
|
019e6b85e6 | ||
|
|
097fe87eb7 | ||
|
|
6ec1e7f7b4 | ||
|
|
f045705ac5 | ||
|
|
2b4f573092 | ||
|
|
7cf4369157 | ||
|
|
4135ab1c0d | ||
|
|
89f7dbc64d | ||
|
|
ec5afe2226 | ||
|
|
345ce9c592 | ||
|
|
9c734ac6ee | ||
|
|
9388198ca0 | ||
|
|
6ce366c7d4 | ||
|
|
10f9b78b4f | ||
|
|
eb8c734b96 | ||
|
|
c55cf5ac90 | ||
|
|
d28cc754b6 | ||
|
|
1819addc75 | ||
|
|
21e649a1f2 | ||
|
|
c599956de1 | ||
|
|
ca7f09a8b1 | ||
|
|
d32ad190ea | ||
|
|
4635631a56 | ||
|
|
2edf718f84 | ||
|
|
16a14c1f6d | ||
|
|
cfa6cf220e | ||
|
|
5e1b7d9470 | ||
|
|
38baa729da | ||
|
|
5fe53f7b7a | ||
|
|
276b0d3c72 | ||
|
|
abb8817a0a | ||
|
|
5309843d2b | ||
|
|
1a2c2dcda6 | ||
|
|
03b6c07215 | ||
|
|
d10b9d5b29 | ||
|
|
e38be075c3 | ||
|
|
28824e8171 | ||
|
|
3edc407713 |
+81
-7
@@ -1,6 +1,6 @@
|
||||
# Changelog for Elixir v1.4
|
||||
|
||||
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aids developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
|
||||
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aid developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
|
||||
|
||||
## Registry
|
||||
|
||||
@@ -55,9 +55,9 @@ While the snippet above works fine in many occasions, for large collections it w
|
||||
|
||||
The `Task.async_stream` functions are also lazy, allowing developers to partially consume the stream until a condition is reached. Furthermore, `Task.Supervisor.async_stream/4` and `Task.Supervisor.async_stream/6` can be used to ensure the concurrent tasks are spawned under a given supervisor.
|
||||
|
||||
## Application inflection
|
||||
## Application inference
|
||||
|
||||
Mix v1.4 now automatically inflects the list of applications that are required on runtime from your dependencies list.
|
||||
Mix v1.4 now automatically infers the list of applications that are required on runtime from your dependencies list.
|
||||
|
||||
In previous Mix versions, most of your dependencies had to be added both to your dependencies list and applications list. Here is how a `mix.exs` would look like:
|
||||
|
||||
@@ -72,7 +72,7 @@ In previous Mix versions, most of your dependencies had to be added both to your
|
||||
|
||||
This was error prone as many developers would not list their dependencies in their applications list.
|
||||
|
||||
Mix v1.4 now automatically inflects your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
|
||||
Mix v1.4 now automatically infers your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
|
||||
|
||||
def application do
|
||||
[extra_applications: [:logger]]
|
||||
@@ -91,7 +91,70 @@ Finally, if there is a dependency you don't want to include in the application r
|
||||
|
||||
We hope this feature provides a more streamlined workflow for developers who are building releases for their Elixir projects.
|
||||
|
||||
## v1.4.0-rc.0 (2016-11-28)
|
||||
## Mix install from SCM
|
||||
|
||||
Mix v1.4 can now install escripts and archives from both Git and Hex, providing you with even more options for distributing Elixir code.
|
||||
|
||||
This makes it possible to distribute CLI applications written in Elixir by publishing a package which builds an escript to Hex. [`ex_doc`](https://hex.pm/packages/ex_doc) has been updated to serve as an example of how to use this new functionality.
|
||||
|
||||
Simply running:
|
||||
|
||||
mix escript.install hex ex_doc
|
||||
|
||||
will fetch `ex_doc` and its dependencies, build them, and then install `ex_doc` to `~/.mix/escripts` (by default). After adding `~/.mix/escripts` to your `PATH`, running `ex_doc` is as simple as:
|
||||
|
||||
ex_doc
|
||||
|
||||
You can now also install archives from Hex in this way. Since they are fetched and built on the user's machine, they do not have the same limitations as pre-built archives. However, keep in mind archives run alongside every Mix project, which may lead to conflicts. For this reason, escripts is the preferred format.
|
||||
|
||||
It is also possible to install escripts and archives by providing a Git/GitHub repo. See `mix help escript.install` and `mix help archive.install` for more details.
|
||||
|
||||
## v1.4.2 (2017-02-16)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Support middle expressions on trim mode
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Correct typo on Calendar types
|
||||
* [Kernel] Ensure redefined functions point to the proper source
|
||||
* [OptionParser] Add `:allow_inexistent_atoms` to support unsafe behaviour prior to v1.4
|
||||
* [Stream] Allow consuming multiple items from suspended enumerable in `Stream.transform/3`
|
||||
* [String] Incorporate new grapheme rules in Unicode 9
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Do not crash on aliases which are not known at compile time
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix.Umbrella] Ensure umbrella projects can depend on other umbrella projects
|
||||
* [Mix.Archive] Ensure previous archives with `.ez` extension are deleted
|
||||
|
||||
## v1.4.1 (2017-01-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Remove warning when making private functions overridable
|
||||
* [Path] Ensure `Path.join/1` returns strings for lists of one element
|
||||
* [Regex] Ensure `Regex.escape/1` also escapes `-`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Disable ANSI detection for powershell to avoid false positives
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix.Make] Run `make clean` for `erlang.mk`
|
||||
* [Mix.Rebar] Support all of rebar3 dependency package declaration
|
||||
* [Mix.Rebar] Only pass overrides from parent to child in Rebar dep
|
||||
|
||||
## v1.4.0 (2017-01-05)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -101,14 +164,16 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
* [Calendar] Support `NaiveDateTime.add/3` and `NaiveDateTime.diff/3` for adding seconds (up to microseconds) as well as the difference between two NaiveDateTimes in seconds (up to microseconds)
|
||||
* [Calendar] Add `Date.leap_year?/1` and `Date.day_of_week/1`
|
||||
* [Calendar] Ensure `Date`, `Time` and `NaiveDateTime` APIs work with any struct that provides the same set of fields as their respective struct. For example, a `NaiveDateTime` can be given to `Date` since it contains a superset of the fields in the `Date` struct
|
||||
* [Calendar] Add `Time.utc_now/0` and `NaiveDateTime.utc_now/0`
|
||||
* [Enum] Add `Enum.map_every/2` that invokes the given function with every nth item
|
||||
* [Enum] Add `min/2`, `max/2`, `min_max/2`, `min_by/3`, `max_by/3`, and `min_max_by/3` that allow a function specifying the default value when the enumerable is empty
|
||||
* [Enum] Introduce `Enum.zip/1` to zip multiple entries at once
|
||||
* [Float] Introduce `Float.ratio/1` that returns a tuple with the numerator and denominator as integers to retrieve the given float
|
||||
* [GenServer] Log error on default `handle_info/2` implementation
|
||||
* [GenServer] Log warn on default `handle_info/2` implementation
|
||||
* [Inspect] Support syntax coloring via the `:syntax_color` option
|
||||
* [Integer] `Integer.digits/2` now accepts negative integers
|
||||
* [Integer] Add `Integer.mod/2` and `Integer.floor_div/2`
|
||||
* [IO] Add `:label` option to `IO.inspect/2` to help distinguish multiple `IO.inspect/2` calls.
|
||||
* [Kernel] Recognize merge conflict markers in source and provide a readable error message
|
||||
* [Kernel] Warn on unused module attributes
|
||||
* [Kernel] Improve compiler message on unexpected end of line
|
||||
@@ -135,6 +200,7 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
|
||||
* [IEx.Autocomplete] Stop appending a trailing dot when autocompleting modules in IEx
|
||||
* [IEx.Autocomplete] Support autocompletion for structs
|
||||
* [IEx.Autocomplete] Improve IEx autocomplete to support navigating map atom keys
|
||||
* [IEx.Helpers] `c/1` now compiles in memory by default to avoid common issue where `.beam` files remain at projects root directory
|
||||
* [IEx.Helpers] Add info about protocols in `i/1`
|
||||
* [IEx.Server] Support interrupting IEx evaluation through the Ctrl+G prompt
|
||||
@@ -142,12 +208,14 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
#### Mix
|
||||
|
||||
* [mix archive] Compress archive files built by `mix archive` as they are now unzipped during installation
|
||||
* [mix compile] Automatically inflect the list of applications for Mix projects
|
||||
* [mix archive] Install from SCM
|
||||
* [mix compile] Automatically infer the list of applications for Mix projects
|
||||
* [mix cmd] Add the ability to specify one or more apps in `mix cmd`
|
||||
* [mix deps] Warn if there are non-applications in the `apps` directory for umbrella projects
|
||||
* [mix deps] Add warning for invalid paths on `mix deps.clean`
|
||||
* [mix deps] Add `Mix.Project.apps_paths` that returns the paths to children applications in umbrella projects
|
||||
* [mix deps] Add `MIX_REBAR` environment variable for overriding local rebar
|
||||
* [mix escript] Install from SCM
|
||||
* [mix new] Check directory existence in `mix new` and ask how to proceed if one exists
|
||||
* [mix new] Applications built with the `--sup` flag now have an individual module to work as application callback
|
||||
* [mix test] Add `--formatter` option to `mix test`
|
||||
@@ -157,9 +225,12 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Access] Do not accept nils in `Access.key/1` and `Access.key/2` in favor of explicit default values (or Access.key!/1 if you expect the key to always be available)
|
||||
* [Float] Avoid multiple roundings in `Float.ceil/2`, `Float.floor/2` and `Float.round/2`
|
||||
* [Kernel] Don't crash in `macro_exported?/3` when dealing with Erlang modules
|
||||
* [Kernel] Ensure locals calls are rewritten when calling a local function or macro from inside a module
|
||||
* [Kernel] Annotate the context for variables as zero-arity funs in quotes
|
||||
* [Kernel.SpecialForms] Ensure comprehensions with guards and filters keep proper ordering,
|
||||
* [Kernel.SpecialForms] Produce meaningful warning when with's else clauses have no effect
|
||||
* [Macro] Wrap fn calls in parens in `Macro.to_string/2`
|
||||
* [Macro] Do not print aliases as keys inside keyword lists in `Macro.to_string/2`
|
||||
@@ -167,6 +238,7 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
* [Stream] Ensure `Stream.take/2` does not consume next element on `:suspend`
|
||||
* [String] Fix infinite recursion in `String.replace_leading/3` and `String.replace_trailing/3` when given an empty string
|
||||
* [Task] Fix `Task.shutdown/1,2` infinite block when task has no monitor
|
||||
* [Task] Ensure task cannot link after parents unlinks
|
||||
|
||||
#### ExUnit
|
||||
|
||||
@@ -197,12 +269,14 @@ We hope this feature provides a more streamlined workflow for developers who are
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Access] `Access.key/1` is deprecated due to erratic behaviour for missing keys, please use Access.key/2 instead with proper default values
|
||||
* [Behaviour] The `Behaviour` module is deprecated. Callbacks may now be defined directly via the `@callback` attribute
|
||||
* [Enum] Deprecate `Enum.uniq/2` in favor of `Enum.uniq_by/2`
|
||||
* [Float] `Float.to_char_list/2` and `Float.to_string/2` are deprecated (use the :erlang functions if such conversions are desired)
|
||||
* [Kernel] Deprecate support for making private functions overridable. Overridable functions must always be public as they must be contracts
|
||||
* [Kernel] Warn if variable is used as a function call
|
||||
* [OptionParser] Deprecate aliases with multiple letters, such as `-abc`
|
||||
* [Set] Deprecate the `Set` module
|
||||
* [Stream] Deprecate `Stream.uniq/2` in favor of `Stream.uniq_by/2`
|
||||
|
||||
#### IEx
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
REBAR ?= "$(CURDIR)/rebar"
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
CANONICAL := master/
|
||||
CANONICAL :=
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ This document simply outlines the release process:
|
||||
|
||||
9. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases
|
||||
|
||||
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
10. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. After a new `vMAJOR.MINOR`, move back to master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vMAJOR.MINOR+1"
|
||||
|
||||
|
||||
@@ -100,27 +100,6 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
setlocal disabledelayedexpansion
|
||||
|
||||
rem ******* detect ANSI terminal support ********************
|
||||
timeout 0 2>nul >nul || goto run
|
||||
where /Q powershell || goto run
|
||||
|
||||
set ASSERT_ANSI= ^
|
||||
$err = 1; ^
|
||||
$Kernel32 = Add-Type -Name 'Kernel32' -PassThru -MemberDefinition ' ^
|
||||
[DllImport(\"Kernel32.dll\", SetLastError = true)] ^
|
||||
public static extern IntPtr GetStdHandle(int nStdHandle); ^
|
||||
[DllImport(\"Kernel32.dll\", SetLastError = true)] ^
|
||||
public static extern bool GetConsoleMode(IntPtr hWnd, ref UInt32 lpMode); ^
|
||||
'; ^
|
||||
$StdoutHandle = $Kernel32::GetStdHandle(-11); ^
|
||||
$ConsoleMode = New-Object -TypeName UInt32; ^
|
||||
$null = $Kernel32::GetConsoleMode($StdoutHandle, [ref]$ConsoleMode); ^
|
||||
if ($ConsoleMode -band 0x4) { $err = 0 } ^
|
||||
exit $err
|
||||
|
||||
powershell -NoProfile -NonInteractive -Command %ASSERT_ANSI% || goto run
|
||||
set parsErlang=%parsErlang% -elixir ansi_enabled true
|
||||
|
||||
:run
|
||||
if not %runMode% == "iex" (
|
||||
set beforeExtra=-noshell -s elixir start_cli %beforeExtra%
|
||||
|
||||
@@ -90,7 +90,9 @@ defmodule EEx.Compiler do
|
||||
{contents, start, list}
|
||||
end
|
||||
end
|
||||
|
||||
defp look_ahead_text([{:middle_expr, line, _, chars} | t], _start, contents) do
|
||||
{contents ++ chars, line, t}
|
||||
end
|
||||
defp look_ahead_text(t, start, contents) do
|
||||
{contents, start, t}
|
||||
end
|
||||
|
||||
@@ -181,7 +181,6 @@ defmodule EEx.Tokenizer do
|
||||
# If trim mode is enabled and the token is on a line with
|
||||
# only itself and whitespace, trim the whitespace around it,
|
||||
# including the line break following it if there is one.
|
||||
|
||||
defp trim_if_needed(rest, line, opts, buffer, acc) do
|
||||
original = {rest, line, buffer}
|
||||
if opts[:trim] do
|
||||
|
||||
@@ -331,6 +331,19 @@ foo
|
||||
assert_eval expected, string, [], trim: true
|
||||
end
|
||||
|
||||
test "trim mode with middle expression" do
|
||||
string = """
|
||||
<%= cond do %>
|
||||
<% false -> %>
|
||||
this
|
||||
<% true -> %>
|
||||
that
|
||||
<% end %>
|
||||
"""
|
||||
expected = " that\n"
|
||||
assert_eval expected, string, [], trim: true
|
||||
end
|
||||
|
||||
test "evaluates the source from a given file" do
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
|
||||
result = EEx.eval_file(filename)
|
||||
|
||||
@@ -358,6 +358,28 @@ defmodule Access do
|
||||
|
||||
## Accessors
|
||||
|
||||
@doc false
|
||||
def key(key) do
|
||||
IO.warn "Access.key/1 is deprecated due to erratic behaviour for missing keys, " <>
|
||||
"please use Access.key/2 instead with proper default values " <>
|
||||
"(or Access.key!/1 if you expect the key to always be available)"
|
||||
|
||||
fn
|
||||
:get, data, next ->
|
||||
next.(Map.get(to_map(data), key))
|
||||
:get_and_update, data, next ->
|
||||
value = Map.get(to_map(data), key)
|
||||
case next.(value) do
|
||||
{get, update} -> {get, Map.put(data, key, update)}
|
||||
:pop -> {value, Map.delete(data, key)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp to_map(nil), do: %{}
|
||||
defp to_map(%{} = map), do: map
|
||||
defp to_map(data), do: raise "Access.key/1 expected a map/struct or nil, got: #{inspect data}"
|
||||
|
||||
@doc """
|
||||
Returns a function that accesses the given key in a map/struct.
|
||||
|
||||
@@ -367,13 +389,13 @@ defmodule Access do
|
||||
The returned function uses the default value if the key does not exist.
|
||||
This can be used to specify defaults and safely traverse missing keys:
|
||||
|
||||
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name)])
|
||||
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)])
|
||||
nil
|
||||
|
||||
Such is also useful when using update functions, allowing us to introduce
|
||||
values as we traverse the data-structure for updates:
|
||||
|
||||
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name)], "Mary")
|
||||
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)], "Mary")
|
||||
%{user: %{name: "Mary"}}
|
||||
|
||||
## Examples
|
||||
@@ -381,23 +403,23 @@ defmodule Access do
|
||||
iex> map = %{user: %{name: "john"}}
|
||||
iex> get_in(map, [Access.key(:unknown, %{}), Access.key(:name, "john")])
|
||||
"john"
|
||||
iex> get_and_update_in(map, [Access.key(:user), Access.key(:name)], fn
|
||||
iex> get_and_update_in(map, [Access.key!(:user), Access.key!(:name)], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", %{user: %{name: "JOHN"}}}
|
||||
iex> pop_in(map, [Access.key(:user), Access.key(:name)])
|
||||
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
|
||||
{"john", %{user: %{}}}
|
||||
|
||||
An error is raised if the accessed structure is not a map or a struct:
|
||||
|
||||
iex> get_in(nil, [Access.key(:foo)])
|
||||
iex> get_in(nil, [Access.key(:foo, nil)])
|
||||
** (BadMapError) expected a map, got: nil
|
||||
|
||||
iex> get_in([], [Access.key(:foo)])
|
||||
iex> get_in([], [Access.key(:foo, nil)])
|
||||
** (BadMapError) expected a map, got: []
|
||||
|
||||
"""
|
||||
def key(key, default \\ nil) do
|
||||
def key(key, default) do
|
||||
fn
|
||||
:get, data, next ->
|
||||
next.(Map.get(data, key, default))
|
||||
|
||||
+109
-13
@@ -51,24 +51,24 @@ defmodule Calendar do
|
||||
@type std_offset :: integer
|
||||
|
||||
@typedoc "Any map/struct that contains the date fields"
|
||||
@type date :: %{calendar: calendar, year: year, month: month, day: day}
|
||||
@type date :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day}
|
||||
|
||||
@typedoc "Any map/struct that contains the time fields"
|
||||
@type time :: %{hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
@type time :: %{optional(any) => any, hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
|
||||
@typedoc "Any map/struct that contains the naive_datetime fields"
|
||||
@type naive_date_time :: %{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
@type naive_datetime :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
|
||||
@typedoc "Any map/struct that contains the datetime fields"
|
||||
@type date_time :: %{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}
|
||||
@type datetime :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}
|
||||
|
||||
@doc """
|
||||
Returns the last day of the month for the given year-month pair.
|
||||
Returns how many days there are in the given year-month.
|
||||
"""
|
||||
@callback last_day_of_month(year, month) :: day
|
||||
@callback days_in_month(year, month) :: day
|
||||
|
||||
@doc """
|
||||
Returns true if the given year is a leap year.
|
||||
@@ -90,7 +90,7 @@ defmodule Calendar do
|
||||
@callback date_to_string(year, month, day) :: String.t
|
||||
|
||||
@doc """
|
||||
Coverts the date time (without time zone) into a string according to the calendar.
|
||||
Converts the date time (without time zone) into a string according to the calendar.
|
||||
"""
|
||||
@callback naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) :: String.t
|
||||
|
||||
@@ -175,6 +175,24 @@ defmodule Date do
|
||||
calendar.leap_year?(year)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of days in the given date month.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Date.days_in_month(~D[1900-01-13])
|
||||
31
|
||||
iex> Date.days_in_month(~D[1900-02-09])
|
||||
28
|
||||
iex> Date.days_in_month(~N[2000-02-20 01:23:45])
|
||||
29
|
||||
|
||||
"""
|
||||
@spec days_in_month(Calendar.date) :: Calendar.day
|
||||
def days_in_month(%{calendar: calendar, year: year, month: month}) do
|
||||
calendar.days_in_month(year, month)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Builds a new ISO date.
|
||||
|
||||
@@ -1598,9 +1616,7 @@ defmodule DateTime do
|
||||
|
||||
WARNING: the ISO 8601 datetime format does not contain the time zone nor
|
||||
its abbreviation, which means information is lost when converting to such
|
||||
format. This is also why this module does not provide a `from_iso8601/1`
|
||||
function, as it is impossible to build a proper `DateTime` from only the
|
||||
information in the ISO 8601 string.
|
||||
format.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -1630,6 +1646,86 @@ defmodule DateTime do
|
||||
time_zone, zone_abbr, utc_offset, std_offset)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the extended "Date and time of day" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Since ISO8601 does not include the proper time zone, the given
|
||||
string will be converted to UTC and its offset in seconds will be
|
||||
returned as part of this function. Therefore offset information
|
||||
must be present in the string.
|
||||
|
||||
As specified in the standard, the separator "T" may be omitted if
|
||||
desired as there is no ambiguity within this function.
|
||||
|
||||
Time representations with reduced accuracy are not supported.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23T23:50:07Z")
|
||||
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 23, microsecond: {0, 0}, minute: 50, month: 1, second: 7, std_offset: 0,
|
||||
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 0}
|
||||
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123+02:30")
|
||||
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 21, microsecond: {123000, 3}, minute: 20, month: 1, second: 7, std_offset: 0,
|
||||
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 9000}
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23T23:50:07")
|
||||
{:error, :missing_offset}
|
||||
iex> DateTime.from_iso8601("2015-01-23 23:50:61")
|
||||
{:error, :invalid_time}
|
||||
iex> DateTime.from_iso8601("2015-01-32 23:50:07")
|
||||
{:error, :invalid_date}
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:00")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:60")
|
||||
{:error, :invalid_format}
|
||||
|
||||
"""
|
||||
@spec from_iso8601(String.t) :: {:ok, t, Calendar.utc_offset} | {:error, atom}
|
||||
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep,
|
||||
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>) when sep in [?\s, ?T] do
|
||||
with {year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
{min, ""} <- Integer.parse(min),
|
||||
{sec, ""} <- Integer.parse(sec),
|
||||
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{:ok, date} <- Calendar.ISO.date(year, month, day),
|
||||
{:ok, time} <- Time.new(hour, min, sec, microsec),
|
||||
{:ok, offset} <- parse_offset(rest) do
|
||||
%{year: year, month: month, day: day} = date
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
|
||||
|
||||
erl = {{year, month, day}, {hour, minute, second}}
|
||||
seconds = :calendar.datetime_to_gregorian_seconds(erl)
|
||||
{{year, month, day}, {hour, minute, second}} =
|
||||
:calendar.gregorian_seconds_to_datetime(seconds - offset)
|
||||
|
||||
{:ok, %DateTime{year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}, offset}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
def from_iso8601(_) do
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
defp parse_offset(rest) do
|
||||
case Calendar.ISO.parse_offset(rest) do
|
||||
{offset, ""} when is_integer(offset) -> {:ok, offset}
|
||||
{nil, ""} -> {:error, :missing_offset}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given datetime to a string according to its calendar.
|
||||
|
||||
|
||||
@@ -17,32 +17,32 @@ defmodule Calendar.ISO do
|
||||
@type day :: 1..31
|
||||
|
||||
@doc """
|
||||
Returns the last day of the month for the given year.
|
||||
Returns how many days there are in the given year-month.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.last_day_of_month(1900, 1)
|
||||
iex> Calendar.ISO.days_in_month(1900, 1)
|
||||
31
|
||||
iex> Calendar.ISO.last_day_of_month(1900, 2)
|
||||
iex> Calendar.ISO.days_in_month(1900, 2)
|
||||
28
|
||||
iex> Calendar.ISO.last_day_of_month(2000, 2)
|
||||
iex> Calendar.ISO.days_in_month(2000, 2)
|
||||
29
|
||||
iex> Calendar.ISO.last_day_of_month(2001, 2)
|
||||
iex> Calendar.ISO.days_in_month(2001, 2)
|
||||
28
|
||||
iex> Calendar.ISO.last_day_of_month(2004, 2)
|
||||
iex> Calendar.ISO.days_in_month(2004, 2)
|
||||
29
|
||||
iex> Calendar.ISO.last_day_of_month(2004, 4)
|
||||
iex> Calendar.ISO.days_in_month(2004, 4)
|
||||
30
|
||||
|
||||
"""
|
||||
@spec last_day_of_month(year, month) :: 28..31
|
||||
def last_day_of_month(year, month)
|
||||
@spec days_in_month(year, month) :: 28..31
|
||||
def days_in_month(year, month)
|
||||
|
||||
def last_day_of_month(year, 2) do
|
||||
def days_in_month(year, 2) do
|
||||
if leap_year?(year), do: 29, else: 28
|
||||
end
|
||||
def last_day_of_month(_, month) when month in [4, 6, 9, 11], do: 30
|
||||
def last_day_of_month(_, month) when month in 1..12, do: 31
|
||||
def days_in_month(_, month) when month in [4, 6, 9, 11], do: 30
|
||||
def days_in_month(_, month) when month in 1..12, do: 31
|
||||
|
||||
@doc """
|
||||
Returns if the given year is a leap year.
|
||||
|
||||
@@ -647,7 +647,7 @@ defmodule UndefinedFunctionError do
|
||||
def message(%{reason: :"function not exported", module: module, function: function, arity: arity, exports: exports}) do
|
||||
suffix =
|
||||
if macro_exported?(module, function, arity) do
|
||||
" but #{inspect(module)} defines a macro with the same name and arity." <>
|
||||
". However there is a macro with the same name and arity." <>
|
||||
" Be sure to require #{inspect(module)} if you intend to invoke this macro"
|
||||
else
|
||||
did_you_mean(module, function, arity, exports)
|
||||
|
||||
@@ -203,7 +203,8 @@ defmodule Float do
|
||||
count = count - exp + 1023
|
||||
|
||||
cond do
|
||||
count <= 0 -> # There is no decimal precision
|
||||
count <= 0 or # There is no decimal precision
|
||||
(0 == exp and <<0::52>> == significant) -> #zero or minus zero
|
||||
float
|
||||
|
||||
count >= 104 -> # Precision beyond 15 digits
|
||||
|
||||
@@ -568,8 +568,8 @@ defmodule GenServer do
|
||||
{_, []} -> self()
|
||||
{_, name} -> name
|
||||
end
|
||||
:error_logger.error_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
|
||||
[__MODULE__, proc, msg])
|
||||
:error_logger.warning_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
|
||||
[__MODULE__, proc, msg])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
||||
+71
-64
@@ -2399,7 +2399,11 @@ defmodule Kernel do
|
||||
|
||||
# @attribute(value)
|
||||
defp do_at([arg], meta, name, function?, env) do
|
||||
read? = :lists.keymember(:context, 1, meta)
|
||||
line =
|
||||
case :lists.keymember(:context, 1, meta) do
|
||||
true -> nil
|
||||
false -> env.line
|
||||
end
|
||||
|
||||
cond do
|
||||
function? ->
|
||||
@@ -2413,11 +2417,11 @@ defmodule Kernel do
|
||||
{stack, _} = :elixir_quote.escape(env_stacktrace(env), false)
|
||||
arg = {env.line, arg}
|
||||
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
|
||||
unquote(stack), unquote(read?))
|
||||
unquote(stack), unquote(line))
|
||||
|
||||
true ->
|
||||
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
|
||||
nil, unquote(read?))
|
||||
nil, unquote(line))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3704,7 +3708,7 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Defines a protocol.
|
||||
|
||||
A protocol specifies an API that should be defined by its
|
||||
@@ -3712,43 +3716,46 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
In Elixir, only `false` and `nil` are considered falsy values.
|
||||
Everything else evaluates to `true` in `if/2` clauses. Depending
|
||||
on the application, it may be important to specify a `blank?`
|
||||
protocol that returns a boolean for other data types that should
|
||||
be considered "blank". For instance, an empty list or an empty
|
||||
binary could be considered blank.
|
||||
In Elixir, we have two verbs for checking how many items there
|
||||
are in a data structure: `length` and `size`. `length` means the
|
||||
information must be computed. For example, `length(list)` needs to
|
||||
traverse the whole list to calculate its length. On the other hand,
|
||||
`tuple_size(tuple)` and `byte_size(binary)` do not depend on the
|
||||
tuple and binary size as the size information is precomputed in
|
||||
the data structure.
|
||||
|
||||
Such protocol could be implemented as follows:
|
||||
Although Elixir includes specific functions such as `tuple_size`,
|
||||
`binary_size` and `map_size`, sometimes we want to be able to
|
||||
retrieve the size of a data structure regardless of its type.
|
||||
In Elixir we can write polymorphic code, i.e. code that works
|
||||
with different shapes/types, by using protocols. A size protocol
|
||||
could be implemented as follows:
|
||||
|
||||
defprotocol Blank do
|
||||
@doc "Returns `true` if `data` is considered blank/empty"
|
||||
def blank?(data)
|
||||
defprotocol Size do
|
||||
@doc "Calculates the size (and not the length!) of a data structure"
|
||||
def size(data)
|
||||
end
|
||||
|
||||
Now that the protocol is defined it can be implemented. It needs to be
|
||||
implemented for each Elixir type; for example:
|
||||
Now that the protocol can be implemented for every data structure
|
||||
the protocol may have a compliant implementation for:
|
||||
|
||||
# Integers are never blank
|
||||
defimpl Blank, for: Integer do
|
||||
def blank?(number), do: false
|
||||
defimpl Size, for: Binary do
|
||||
def size(binary), do: byte_size(binary)
|
||||
end
|
||||
|
||||
# The only blank list is the empty one
|
||||
defimpl Blank, for: List do
|
||||
def blank?([]), do: true
|
||||
def blank?(_), do: false
|
||||
defimpl Size, for: Map do
|
||||
def size(map), do: map_size(map)
|
||||
end
|
||||
|
||||
# The only blank atoms are "false" and "nil"
|
||||
defimpl Blank, for: Atom do
|
||||
def blank?(false), do: true
|
||||
def blank?(nil), do: true
|
||||
def blank?(_), do: false
|
||||
defimpl Size, for: Tuple do
|
||||
def size(tuple), do: tuple_size(tuple)
|
||||
end
|
||||
|
||||
The implementation of the `Blank` protocol would need to be defined for all
|
||||
Elixir types. The available types are:
|
||||
Notice we didn't implement it for lists as we don't have the
|
||||
`size` information on lists, rather its value needs to be
|
||||
computed with `length`.
|
||||
|
||||
It is possible to implement protocols for all Elixir types:
|
||||
|
||||
* Structs (see below)
|
||||
* `Tuple`
|
||||
@@ -3768,11 +3775,11 @@ defmodule Kernel do
|
||||
|
||||
The real benefit of protocols comes when mixed with structs.
|
||||
For instance, Elixir ships with many data types implemented as
|
||||
structs, like `MapSet`. We can implement the `Blank` protocol
|
||||
structs, like `MapSet`. We can implement the `Size` protocol
|
||||
for those types as well:
|
||||
|
||||
defimpl Blank, for: MapSet do
|
||||
def blank?(enum_like), do: Enum.empty?(enum_like)
|
||||
defimpl Size, for: MapSet do
|
||||
def size(map_set), do: MapSet.size(map_set)
|
||||
end
|
||||
|
||||
When implementing a protocol for a struct, the `:for` option can
|
||||
@@ -3782,54 +3789,54 @@ defmodule Kernel do
|
||||
defmodule User do
|
||||
defstruct [:email, :name]
|
||||
|
||||
defimpl Blank do
|
||||
def blank?(%User{}), do: false
|
||||
defimpl Size do
|
||||
def size(%User{}), do: 2 # two fields
|
||||
end
|
||||
end
|
||||
|
||||
If a protocol is not found for a given type, it will fallback to
|
||||
`Any`. Protocols that are implemented for maps don't work by default
|
||||
on structs; look at `defstruct/1` for more information about deriving
|
||||
If a protocol implementation is not found for a given type,
|
||||
invoking the protocol will raise unless it is configured to
|
||||
fallback to `Any`. Conveniences for building implementations
|
||||
on top of existing ones are also available, look at `defstruct/1`
|
||||
for more information about deriving
|
||||
protocols.
|
||||
|
||||
## Fallback to any
|
||||
|
||||
In some cases, it may be convenient to provide a default
|
||||
implementation for all types. This can be achieved by
|
||||
setting the `@fallback_to_any` attribute to `true` in the protocol
|
||||
implementation for all types. This can be achieved by setting
|
||||
the `@fallback_to_any` attribute to `true` in the protocol
|
||||
definition:
|
||||
|
||||
defprotocol Blank do
|
||||
defprotocol Size do
|
||||
@fallback_to_any true
|
||||
def blank?(data)
|
||||
def size(data)
|
||||
end
|
||||
|
||||
The `Blank` protocol can now be implemented for `Any`:
|
||||
The `Size` protocol can now be implemented for `Any`:
|
||||
|
||||
defimpl Blank, for: Any do
|
||||
def blank?(_), do: true
|
||||
defimpl Size, for: Any do
|
||||
def size(_), do: 0
|
||||
end
|
||||
|
||||
One may wonder why such behaviour (fallback to any) is not the default one.
|
||||
|
||||
It is two-fold: first, the majority of protocols cannot
|
||||
implement an action in a generic way for all types; in fact,
|
||||
providing a default implementation may be harmful, because users
|
||||
may rely on the default implementation instead of providing a
|
||||
specialized one.
|
||||
|
||||
Second, falling back to `Any` adds an extra lookup to all types,
|
||||
which is unnecessary overhead unless an implementation for `Any` is
|
||||
required.
|
||||
Although the implementation above is arguably not a reasonable
|
||||
one. For example, it makes no sense to say a PID or an Integer
|
||||
have a size of 0. That's one of the reasons why `@fallback_to_any`
|
||||
is an opt-in behaviour. For the majority of protocols, raising
|
||||
an error when a protocol is not implemented is the proper behaviour.
|
||||
|
||||
## Types
|
||||
|
||||
Defining a protocol automatically defines a type named `t`, which
|
||||
can be used as follows:
|
||||
|
||||
@spec present?(Blank.t) :: boolean
|
||||
def present?(blank) do
|
||||
not Blank.blank?(blank)
|
||||
@spec print_size(Size.t) :: :ok
|
||||
def print_size(data) do
|
||||
IO.puts(case Size.size(data) do
|
||||
0 -> "data has no items"
|
||||
1 -> "data has one item"
|
||||
n -> "data has #{n} items"
|
||||
end)
|
||||
end
|
||||
|
||||
The `@spec` above expresses that all types allowed to implement the
|
||||
@@ -3868,12 +3875,12 @@ defmodule Kernel do
|
||||
all implementations are known up-front, Elixir provides a feature
|
||||
called protocol consolidation. For this reason, all protocols are
|
||||
compiled with `debug_info` set to `true`, regardless of the option
|
||||
set by `elixirc` compiler. The debug info though may be removed
|
||||
after consolidation.
|
||||
set by `elixirc` compiler. The debug info though may be removed after
|
||||
consolidation.
|
||||
|
||||
For more information on how to apply protocol consolidation to
|
||||
a given project, please check the functions in the `Protocol`
|
||||
module or the `mix compile.protocols` task.
|
||||
Protocol consolidation is applied by default to all Mix projects.
|
||||
For applying consolidation manually, please check the functions in
|
||||
the `Protocol` module or the `mix compile.protocols` task.
|
||||
"""
|
||||
defmacro defprotocol(name, do_block)
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> x = 1
|
||||
iex> <<x::8>> == <<x::size(8)>>
|
||||
true
|
||||
iex> <<x::8 * 4>> == <<x::size(8)-unit(4)>>
|
||||
iex> <<x::8*4>> == <<x::size(8)-unit(4)>>
|
||||
true
|
||||
|
||||
This syntax reflects the fact the effective size is given by
|
||||
@@ -293,7 +293,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmodule ImageTyper
|
||||
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
@jpg_signature <<255::size(8), 216::size(8)>>
|
||||
|
||||
def type(<<@png_signature, rest::binary>>), do: :png
|
||||
@@ -1241,7 +1241,7 @@ defmodule Kernel.SpecialForms do
|
||||
[2, 4, 6, 8]
|
||||
|
||||
# A comprehension with two generators
|
||||
iex> for x <- [1, 2], y <- [2, 3], do: x*y
|
||||
iex> for x <- [1, 2], y <- [2, 3], do: x * y
|
||||
[2, 3, 4, 6]
|
||||
|
||||
Filters can also be given:
|
||||
@@ -1263,7 +1263,7 @@ defmodule Kernel.SpecialForms do
|
||||
need to organize bitstring streams:
|
||||
|
||||
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
|
||||
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
|
||||
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
|
||||
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
|
||||
|
||||
Variable assignments inside the comprehension, be it in generators,
|
||||
@@ -1301,7 +1301,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
...> do: {:ok, width * height}
|
||||
{:ok, 150}
|
||||
|
||||
If all clauses match, the `do` block is executed, returning its result.
|
||||
@@ -1310,14 +1310,14 @@ defmodule Kernel.SpecialForms do
|
||||
iex> opts = %{width: 10}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
...> do: {:ok, width * height}
|
||||
:error
|
||||
|
||||
Guards can be used in patterns as well:
|
||||
|
||||
iex> users = %{"melany" => "guest", "bob" => :admin}
|
||||
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob"),
|
||||
...> do: {:ok, to_string(role)}
|
||||
...> do: {:ok, to_string(role)}
|
||||
{:ok, "admin"}
|
||||
|
||||
As in `for/1`, variables bound inside `with/1` won't leak;
|
||||
@@ -1328,7 +1328,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> double_width = width * 2,
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, double_width * height}
|
||||
...> do: {:ok, double_width * height}
|
||||
{:ok, 300}
|
||||
iex> width
|
||||
nil
|
||||
|
||||
+16
-24
@@ -808,13 +808,6 @@ defmodule Module do
|
||||
"cannot make private macro #{name}/#{arity} overridable, overriding " <>
|
||||
"private macros is not supported"
|
||||
clause ->
|
||||
{{_def, kind, _line, _file, _check, _location, _defaults}, _clauses} = clause
|
||||
|
||||
# TODO: Remove on v2.0
|
||||
if kind == :defp do
|
||||
IO.warn "making private functions (#{name}/#{arity} in this case) overridable is deprecated"
|
||||
end
|
||||
|
||||
neighbours =
|
||||
if :elixir_compiler.get_opt(:internal) do
|
||||
[]
|
||||
@@ -843,8 +836,6 @@ defmodule Module do
|
||||
@doc """
|
||||
Puts a module attribute with key and value in the given module.
|
||||
|
||||
It returns the value of the attribute after normalization.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
@@ -852,9 +843,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec put_attribute(module, key :: atom, value :: term) :: term
|
||||
@spec put_attribute(module, key :: atom, value :: term) :: :ok
|
||||
def put_attribute(module, key, value) do
|
||||
put_attribute(module, key, value, nil, false)
|
||||
put_attribute(module, key, value, nil, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -910,7 +901,7 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
case :ets.take(table, key) do
|
||||
[{_, value, _accumulated? = true, _}] ->
|
||||
:ets.insert(table, {key, [], true, true})
|
||||
:ets.insert(table, {key, [], true, nil})
|
||||
value
|
||||
[{_, value, _, _}] ->
|
||||
value
|
||||
@@ -920,9 +911,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers an attribute. By registering an attribute, a developer
|
||||
is able to customize how Elixir will store and accumulate the
|
||||
attribute values.
|
||||
Registers an attribute.
|
||||
|
||||
By registering an attribute, a developer is able to customize
|
||||
how Elixir will store and accumulate the attribute values.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -960,7 +952,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
:ets.insert_new(table, {new, [], _accumulated? = true, _read? = true}) ||
|
||||
:ets.insert_new(table, {new, [], _accumulated? = true, _unread_line = nil}) ||
|
||||
:ets.update_element(table, new, {3, true})
|
||||
end
|
||||
|
||||
@@ -1031,7 +1023,7 @@ defmodule Module do
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
:ets.insert(table, {key, new, true, true})
|
||||
:ets.insert(table, {key, new, true, nil})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -1043,7 +1035,7 @@ defmodule Module do
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val, _, _}] ->
|
||||
:ets.update_element(table, key, {4, true})
|
||||
:ets.update_element(table, key, {4, nil})
|
||||
val
|
||||
[] when is_list(stack) ->
|
||||
# TODO: Consider raising instead of warning on v2.0 as it usually cascades
|
||||
@@ -1058,25 +1050,25 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally by Kernel's @.
|
||||
# This function is private and must be used only internally.
|
||||
def put_attribute(module, key, value, stack, read?) when is_atom(key) do
|
||||
def put_attribute(module, key, value, stack, unread_line) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = preprocess_attribute(key, value)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, {line, <<_::binary>>}, accumulated?, _read?}]
|
||||
[{^key, {line, <<_::binary>>}, accumulated?, _unread_line}]
|
||||
when key in [:doc, :typedoc, :moduledoc] and is_list(stack) ->
|
||||
IO.warn "redefining @#{key} attribute previously set at line #{line}", stack
|
||||
:ets.insert(table, {key, value, accumulated?, read?})
|
||||
:ets.insert(table, {key, value, accumulated?, unread_line})
|
||||
|
||||
[{^key, current, _accumulated? = true, _read?}] ->
|
||||
:ets.insert(table, {key, [value | current], true, read?})
|
||||
:ets.insert(table, {key, [value | current], true, unread_line})
|
||||
|
||||
_ ->
|
||||
:ets.insert(table, {key, value, false, read?})
|
||||
:ets.insert(table, {key, value, false, unread_line})
|
||||
end
|
||||
|
||||
value
|
||||
:ok
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -30,35 +30,33 @@ defmodule OptionParser do
|
||||
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.
|
||||
underscores are always returned in the list of invalid switches.
|
||||
|
||||
Without any options, this function will try to parse all switches in the `argv`.
|
||||
When parsing, it is common to list switches and their expected types:
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
iex> OptionParser.parse(["--debug"], switches: [debug: :boolean])
|
||||
{[debug: true], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
iex> OptionParser.parse(["--source", "lib"], switches: [source: :string])
|
||||
{[source: "lib"], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"],
|
||||
...> switches: [source_path: :string, verbose: :boolean])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
We will explore the valid switches and operation modes of option parser below.
|
||||
|
||||
## Options
|
||||
|
||||
The following options are supported:
|
||||
|
||||
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||
* `:allow_nonexistent_atoms` - see the "Parsing dynamic switches" section below
|
||||
* `:aliases` - see the "Aliases" section below
|
||||
|
||||
## Switch definitions
|
||||
|
||||
Often it is better to explicitly list the known
|
||||
switches and their formats. The switches can be specified via one of two
|
||||
options:
|
||||
Switches can be specified via one of two options:
|
||||
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
@@ -89,20 +87,20 @@ defmodule OptionParser do
|
||||
* `:float` - parses the value as a float
|
||||
* `:string` - parses the value as a string
|
||||
|
||||
If a switch can't be parsed according to the given type, it is returned
|
||||
in the invalid options list.
|
||||
If a switch can't be parsed according to the given type, it is
|
||||
returned in the invalid options list.
|
||||
|
||||
### Modifiers
|
||||
|
||||
Switches can be specified with modifiers, which change how
|
||||
they behave. The following modifiers are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items instead of overriding them; works with
|
||||
all types except `:count`. Specifying `switch_name: :keep` assumes the
|
||||
type of `:switch_name` will be `:string`.
|
||||
* `:keep` - keeps duplicated items instead of overriding them;
|
||||
works with all types except `:count`. Specifying `switch_name: :keep`
|
||||
assumes the type of `:switch_name` will be `:string`.
|
||||
|
||||
Note that if you want to use `:keep` with a type other than `:string`, use a list
|
||||
as the type for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||
To use `:keep` with a type other than `:string`, use a list as the type
|
||||
for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||
|
||||
### Negation switches
|
||||
|
||||
@@ -112,6 +110,46 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
### Parsing dynamic switches
|
||||
|
||||
`OptionParser` also includes a dynamic mode where it will attempt to parse
|
||||
switches dynamically. Such can be done by not specifying the `:switches` or
|
||||
`:strict` option.
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{[debug: true], [], []}
|
||||
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string. Switches
|
||||
without an argument, like `--debug` in the examples above, will automatically be
|
||||
set to `true`.
|
||||
|
||||
Since Elixir converts switches to atoms, the dynamic mode will only parse
|
||||
switches that translates to atoms used by the runtime. Therefore, the code below
|
||||
likely won't parse the given option since the `:option_parser_example` atom is
|
||||
never used anywhere:
|
||||
|
||||
OptionParser.parse(["--option-parser-example"])
|
||||
# Does nothing more...
|
||||
|
||||
However, the code below does since the `:option_parser_example` atom is used
|
||||
at some point later (or earlier) on:
|
||||
|
||||
{opts, _, _} = OptionParser.parse(["--option-parser-example"])
|
||||
opts[:option_parser_example]
|
||||
|
||||
In other words, when using dynamic mode, Elixir will do the correct thing and
|
||||
only parse options that are used by the runtime, ignoring all others. If you
|
||||
would like to parse all switches, regardless if they exist or not, you can
|
||||
force creation of atoms by passing `allow_nonexistent_atoms: true` as option.
|
||||
Such option is useful when you are building command-line applications that
|
||||
receive dynamically-named arguments but must be used with care on long-running
|
||||
systems.
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be specified in the `:aliases` option:
|
||||
@@ -203,10 +241,12 @@ defmodule OptionParser do
|
||||
|
||||
## Example
|
||||
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"],
|
||||
...> switches: [source: :string, verbose: :boolean])
|
||||
{[source: "lib"], ["test/enum_test.exs", "--verbose"], []}
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"],
|
||||
...> switches: [source: :string, verbose: :boolean, unlock: :boolean])
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
|
||||
|
||||
"""
|
||||
@@ -226,10 +266,12 @@ defmodule OptionParser do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"])
|
||||
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"],
|
||||
...> switches: [source: :string, verbose: :boolean])
|
||||
{[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!
|
||||
--number : Expected type integer, got "lib"
|
||||
|
||||
@@ -251,8 +293,8 @@ defmodule OptionParser do
|
||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp do_parse(argv, {aliases, switches, strict?} = config, opts, args, invalid, all?) do
|
||||
case next(argv, aliases, switches, strict?) do
|
||||
defp do_parse(argv, {aliases, switches, strict?, allow_nonexistent_atoms?} = config, opts, args, invalid, all?) do
|
||||
case next(argv, aliases, switches, strict?, allow_nonexistent_atoms?) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exists and it was successfully parsed
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
@@ -299,7 +341,6 @@ defmodule OptionParser do
|
||||
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, String.t, String.t | nil, argv} |
|
||||
@@ -307,35 +348,35 @@ defmodule OptionParser do
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
{aliases, switches, strict?} = compile_config(opts)
|
||||
next(argv, aliases, switches, strict?)
|
||||
{aliases, switches, strict?, allow_nonexistent_atoms?} = compile_config(opts)
|
||||
next(argv, aliases, switches, strict?, allow_nonexistent_atoms?)
|
||||
end
|
||||
|
||||
defp next([], _aliases, _switches, _strict?) do
|
||||
defp next([], _aliases, _switches, _strict?, _allow_nonexistent_atoms?) do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--" | _] = argv, _aliases, _switches, _strict?) do
|
||||
defp next(["--" | _] = argv, _aliases, _switches, _strict?, _allow_nonexistent_atoms?) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" | _] = argv, _aliases, _switches, _strict?) do
|
||||
defp next(["-" | _] = argv, _aliases, _switches, _strict?, _allow_nonexistent_atoms?) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict?) do
|
||||
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict?, _allow_nonexistent_atoms?) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
# Handles --foo or --foo=bar
|
||||
defp next(["--" <> option | rest], _aliases, switches, strict?) do
|
||||
defp next(["--" <> option | rest], _aliases, switches, strict?, allow_nonexistent_atoms?) do
|
||||
{option, value} = split_option(option)
|
||||
tagged = tag_option(option, switches)
|
||||
do_next(tagged, value, "--" <> option, rest, switches, strict?)
|
||||
tagged = tag_option(option, switches, allow_nonexistent_atoms?)
|
||||
do_next(tagged, value, "--" <> option, rest, switches, strict?, allow_nonexistent_atoms?)
|
||||
end
|
||||
|
||||
# Handles -a, -abc, -abc=something
|
||||
defp next(["-" <> option | rest] = argv, aliases, switches, strict?) do
|
||||
defp next(["-" <> option | rest] = argv, aliases, switches, strict?, allow_nonexistent_atoms?) do
|
||||
{option, value} = split_option(option)
|
||||
original = "-" <> option
|
||||
|
||||
@@ -345,26 +386,26 @@ defmodule OptionParser do
|
||||
String.contains?(option, ["-", "_"]) ->
|
||||
{:undefined, original, value, rest}
|
||||
String.length(option) > 1 ->
|
||||
key = get_option_key(option)
|
||||
key = get_option_key(option, allow_nonexistent_atoms?)
|
||||
option_key = aliases[key]
|
||||
if key && option_key do
|
||||
IO.warn "multi-letter aliases are deprecated, got: #{inspect(key)}"
|
||||
do_next({:default, option_key}, value, original, rest, switches, strict?)
|
||||
do_next({:default, option_key}, value, original, rest, switches, strict?, allow_nonexistent_atoms?)
|
||||
else
|
||||
next(expand_multiletter_alias(option, value) ++ rest, aliases, switches, strict?)
|
||||
next(expand_multiletter_alias(option, value) ++ rest, aliases, switches, strict?, allow_nonexistent_atoms?)
|
||||
end
|
||||
true ->
|
||||
# We have a regular one-letter alias here
|
||||
tagged = tag_oneletter_alias(option, aliases)
|
||||
do_next(tagged, value, original, rest, switches, strict?)
|
||||
tagged = tag_oneletter_alias(option, aliases, allow_nonexistent_atoms?)
|
||||
do_next(tagged, value, original, rest, switches, strict?, allow_nonexistent_atoms?)
|
||||
end
|
||||
end
|
||||
|
||||
defp next(argv, _aliases, _switches, _strict?) do
|
||||
defp next(argv, _aliases, _switches, _strict?, _allow_nonexistent_atoms?) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp do_next(tagged, value, original, rest, switches, strict?) do
|
||||
defp do_next(tagged, value, original, rest, switches, strict?, allow_nonexistent_atoms?) do
|
||||
if strict? and not option_defined?(tagged, switches) do
|
||||
{:undefined, original, value, rest}
|
||||
else
|
||||
@@ -489,6 +530,7 @@ defmodule OptionParser do
|
||||
|
||||
defp compile_config(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
allow_nonexistent_atoms? = opts[:allow_nonexistent_atoms] || false
|
||||
|
||||
{switches, strict?} = cond do
|
||||
opts[:switches] && opts[:strict] ->
|
||||
@@ -501,7 +543,7 @@ defmodule OptionParser do
|
||||
{[], false}
|
||||
end
|
||||
|
||||
{aliases, switches, strict?}
|
||||
{aliases, switches, strict?, allow_nonexistent_atoms?}
|
||||
end
|
||||
|
||||
defp validate_option(value, kinds) do
|
||||
@@ -552,27 +594,27 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option("no-" <> option = original, switches) do
|
||||
defp tag_option("no-" <> option = original, switches, allow_nonexistent_atoms?) do
|
||||
cond do
|
||||
(negated = get_option_key(option)) && :boolean in List.wrap(switches[negated]) ->
|
||||
(negated = get_option_key(option, allow_nonexistent_atoms?)) && :boolean in List.wrap(switches[negated]) ->
|
||||
{:negated, negated}
|
||||
option_key = get_option_key(original) ->
|
||||
option_key = get_option_key(original, allow_nonexistent_atoms?) ->
|
||||
{:default, option_key}
|
||||
true ->
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option(option, _switches) do
|
||||
if option_key = get_option_key(option) do
|
||||
defp tag_option(option, _switches, allow_nonexistent_atoms?) do
|
||||
if option_key = get_option_key(option, allow_nonexistent_atoms?) do
|
||||
{:default, option_key}
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_oneletter_alias(alias, aliases) when is_binary(alias) do
|
||||
if option_key = aliases[to_existing_key(alias)] do
|
||||
defp tag_oneletter_alias(alias, aliases, allow_nonexistent_atoms?) when is_binary(alias) do
|
||||
if option_key = aliases[to_existing_key(alias, allow_nonexistent_atoms?)] do
|
||||
{:default, option_key}
|
||||
else
|
||||
:unknown
|
||||
@@ -662,13 +704,15 @@ defmodule OptionParser do
|
||||
defp to_underscore(<<>>, acc),
|
||||
do: acc
|
||||
|
||||
def get_option_key(option) do
|
||||
def get_option_key(option, allow_nonexistent_atoms?) do
|
||||
if string = to_underscore(option) do
|
||||
to_existing_key(string)
|
||||
to_existing_key(string, allow_nonexistent_atoms?)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_existing_key(option) do
|
||||
defp to_existing_key(option, true),
|
||||
do: String.to_atom(option)
|
||||
defp to_existing_key(option, false) do
|
||||
try do
|
||||
String.to_existing_atom(option)
|
||||
rescue
|
||||
@@ -702,7 +746,8 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp get_type(option, opts, types) do
|
||||
key = option |> String.trim_leading("-") |> get_option_key()
|
||||
allow_nonexistent_atoms? = opts[:allow_nonexistent_atoms] || false
|
||||
key = option |> String.trim_leading("-") |> get_option_key(allow_nonexistent_atoms?)
|
||||
|
||||
if option_key = opts[:aliases][key] do
|
||||
types[option_key]
|
||||
|
||||
@@ -428,10 +428,10 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Joins a list of strings.
|
||||
Joins a list of paths.
|
||||
|
||||
This function should be used to convert a list of strings to a path.
|
||||
Note that any trailing slash is removed on join.
|
||||
This function should be used to convert a list of paths to a path.
|
||||
Note that any trailing slash is removed when joining.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -445,11 +445,11 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec join([t]) :: binary
|
||||
@spec join(nonempty_list(t)) :: binary
|
||||
def join([name1, name2 | rest]), do:
|
||||
join([join(name1, name2) | rest])
|
||||
def join([name]), do:
|
||||
name
|
||||
IO.chardata_to_string(name)
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
|
||||
@@ -629,7 +629,7 @@ defmodule Regex do
|
||||
[get_index(string, h) | get_indexes(string, t, arity - 1)]
|
||||
end
|
||||
|
||||
{:ok, pattern} = :re.compile(~S"[.^$*+?()\[\]{}\\\|\s#]", [:unicode])
|
||||
{:ok, pattern} = :re.compile(~S"[.^$*+?()\[\]{}\\\|\s#-]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc ~S"""
|
||||
|
||||
@@ -9,7 +9,9 @@ defmodule Set do
|
||||
@type values :: [ value ]
|
||||
@type t :: map
|
||||
|
||||
# TODO: Deprecate every function by 1.4
|
||||
# TODO: Remove by 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
case unquote(set) do
|
||||
|
||||
+19
-18
@@ -789,16 +789,18 @@ defmodule Stream do
|
||||
do_after(after_fun, user_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:suspended, [val], next} ->
|
||||
do_transform_user(val, user_acc, user, fun, :cont, next, inner_acc, inner, after_fun)
|
||||
{_, [val]} ->
|
||||
do_transform_user(val, user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
|
||||
{_, []} ->
|
||||
do_transform(user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
|
||||
{:suspended, vals, next} ->
|
||||
do_transform_user(:lists.reverse(vals), user_acc, user, fun, :cont, next, inner_acc, inner, after_fun)
|
||||
{_, vals} ->
|
||||
do_transform_user(:lists.reverse(vals), user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_user(val, user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
|
||||
defp do_transform_user([], user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
|
||||
do_transform(user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
|
||||
end
|
||||
|
||||
defp do_transform_user([val | vals], user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -808,20 +810,20 @@ defmodule Stream do
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
|
||||
do_transform_user(vals, user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
|
||||
{list, user_acc} when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_op, next, inner_acc, inner,
|
||||
do_list_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner,
|
||||
&Enumerable.List.reduce(list, &1, fun), after_fun)
|
||||
{:halt, user_acc} ->
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
{:halted, elem(inner_acc, 1)}
|
||||
{other, user_acc} ->
|
||||
do_enum_transform(user_acc, user, fun, next_op, next, inner_acc, inner,
|
||||
do_enum_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner,
|
||||
&Enumerable.reduce(other, &1, inner), after_fun)
|
||||
end
|
||||
|
||||
defp do_list_transform(user_acc, user, fun, next_op, next, inner_acc, inner, reduce, after_fun) do
|
||||
defp do_list_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner, reduce, after_fun) do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
@@ -832,17 +834,17 @@ defmodule Stream do
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
{:halted, acc} ->
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
{:halted, acc}
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_transform(user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
|
||||
{:suspended, acc, &do_list_transform(vals, user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_enum_transform(user_acc, user, fun, next_op, next, {op, inner_acc}, inner, reduce, after_fun) do
|
||||
defp do_enum_transform(vals, user_acc, user, fun, next_op, next, {op, inner_acc}, inner, reduce, after_fun) do
|
||||
try do
|
||||
reduce.({op, [:outer | inner_acc]})
|
||||
catch
|
||||
@@ -855,15 +857,15 @@ defmodule Stream do
|
||||
# Only take into account outer halts when the op is not halt itself.
|
||||
# Otherwise, we were the ones wishing to halt, so we should just stop.
|
||||
{:halted, [:outer | acc]} when op != :halt ->
|
||||
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
{:halted, [_ | acc]} ->
|
||||
next.({:halt, []})
|
||||
do_after(after_fun, user_acc)
|
||||
{:halted, acc}
|
||||
{:done, [_ | acc]} ->
|
||||
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
|
||||
{:suspended, [_ | acc], c} ->
|
||||
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
|
||||
{:suspended, acc, &do_enum_transform(vals, user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -902,7 +904,6 @@ defmodule Stream do
|
||||
|
||||
@doc false
|
||||
def uniq(enum, fun) do
|
||||
IO.warn "Stream.uniq/2 is deprecated, use Stream.uniq_by/2 instead"
|
||||
uniq_by(enum, fun)
|
||||
end
|
||||
|
||||
|
||||
+16
-14
@@ -56,28 +56,27 @@ defmodule Task do
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
It is also possible to spawn a task under a supervisor
|
||||
with `start_link/1` and `start_link/3`:
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such tasks can be mounted in your supervision tree as:
|
||||
It is also possible to spawn a task under a supervisor:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
#
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Internally the supervisor will invoke `Task.start_link/1`.
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
By default, most supervision strategies will try to restart
|
||||
a worker after it exits regardless of the reason. If you design the
|
||||
task to terminate normally (as in the example with `IO.puts/2` above),
|
||||
consider passing `restart: :transient` in the options to `Supervisor.Spec.worker/3`.
|
||||
a worker after it exits regardless of the reason. If you design
|
||||
the task to terminate normally (as in the example with `IO.puts/2`
|
||||
above), consider passing `restart: :transient` in the options
|
||||
to `Supervisor.Spec.worker/3`.
|
||||
|
||||
## Dynamically supervised tasks
|
||||
|
||||
@@ -286,16 +285,18 @@ defmodule Task do
|
||||
Returns a stream that runs the given `module`, `function` and `args`
|
||||
concurrently on each item in `enumerable`.
|
||||
|
||||
Each item will be appended to the given `args` and processed by its
|
||||
own task. The tasks will be linked to the current process similar to
|
||||
`async/3`.
|
||||
Each item will be prepended to the given `args` and processed by its
|
||||
own task. The tasks will be linked to an intermediate process that is
|
||||
then linked to the current process. This means a failure in a task
|
||||
terminates the current process and a failure in the current process
|
||||
terminates all tasks.
|
||||
|
||||
When streamed, each task will emit `{:ok, val}` upon successful
|
||||
completion or `{:exit, val}` if the caller is trapping exits. Results
|
||||
are emitted in the same order as the original `enumerable`.
|
||||
|
||||
The level of concurrency can be controlled via the `:max_concurrency`
|
||||
option and defaults to `System.schedulers_online/1`. The timeout
|
||||
option and defaults to `System.schedulers_online/0`. The timeout
|
||||
can also be given as option and defaults to 5000 and it defaults to
|
||||
the maximum amount of time to wait without a task reply.
|
||||
|
||||
@@ -307,9 +308,10 @@ defmodule Task do
|
||||
## Options
|
||||
|
||||
* `:max_concurrency` - sets the maximum number of tasks to run
|
||||
at the same time. Defaults to `System.schedulers_online/1`.
|
||||
at the same time. Defaults to `System.schedulers_online/0`.
|
||||
* `:timeout` - the maximum amount of time to wait without
|
||||
receiving a task reply (across all running tasks).
|
||||
Defaults to `5000`.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -10,29 +10,21 @@ defmodule Task.Supervised do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(caller, link, info, fun) do
|
||||
{:ok, spawn_link(caller, link, info, fun)}
|
||||
def start_link(caller, monitor, info, fun) do
|
||||
{:ok, spawn_link(caller, monitor, info, fun)}
|
||||
end
|
||||
|
||||
def spawn_link(caller, link \\ :nolink, info, fun) do
|
||||
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
|
||||
def spawn_link(caller, monitor \\ :nomonitor, info, fun) do
|
||||
:proc_lib.spawn_link(__MODULE__, :reply, [caller, monitor, info, fun])
|
||||
end
|
||||
|
||||
def reply(caller, link, info, mfa) do
|
||||
def reply(caller, monitor, info, mfa) do
|
||||
initial_call(mfa)
|
||||
case link do
|
||||
:link ->
|
||||
try do
|
||||
Process.link(caller)
|
||||
catch
|
||||
:error, :noproc ->
|
||||
exit({:shutdown, :noproc})
|
||||
end
|
||||
reply(caller, nil, @ref_timeout, info, mfa)
|
||||
case monitor do
|
||||
:monitor ->
|
||||
mref = Process.monitor(caller)
|
||||
reply(caller, mref, @ref_timeout, info, mfa)
|
||||
:nolink ->
|
||||
:nomonitor ->
|
||||
reply(caller, nil, :infinity, info, mfa)
|
||||
end
|
||||
end
|
||||
@@ -157,37 +149,41 @@ defmodule Task.Supervised do
|
||||
parent = self()
|
||||
|
||||
# Start a process responsible for translating down messages.
|
||||
{monitor_pid, monitor_ref} = spawn_monitor(fn -> stream_monitor(parent) end)
|
||||
{:trap_exit, trap_exit} =
|
||||
Process.info(self(), :trap_exit)
|
||||
{monitor_pid, monitor_ref} =
|
||||
Process.spawn(fn -> stream_monitor(parent, mfa, spawn, trap_exit) end, [:link, :monitor])
|
||||
|
||||
send(monitor_pid, {parent, monitor_ref})
|
||||
|
||||
stream_reduce(acc, max_concurrency, 0, 0, %{}, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
end
|
||||
|
||||
defp stream_reduce({:halt, acc}, _max, _spawned, _delivered, waiting, next,
|
||||
_reducer, _mfa, _spawn, monitor_pid, monitor_ref, timeout) do
|
||||
defp stream_reduce({:halt, acc}, _max, _spawned, _delivered, _waiting, next,
|
||||
_reducer, monitor_pid, monitor_ref, timeout) do
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
is_function(next) && next.({:halt, []})
|
||||
stream_close(waiting, monitor_pid, monitor_ref, timeout)
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp stream_reduce({:suspend, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
|
||||
reducer, monitor_pid, monitor_ref, timeout) do
|
||||
{:suspended, acc, &stream_reduce(&1, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)}
|
||||
reducer, monitor_pid, monitor_ref, timeout)}
|
||||
end
|
||||
|
||||
# All spawned, all delivered, next is done.
|
||||
defp stream_reduce({:cont, acc}, _max, spawned, delivered, waiting, next,
|
||||
_reducer, _mfa, _spawn, monitor_pid, monitor_ref, timeout)
|
||||
defp stream_reduce({:cont, acc}, _max, spawned, delivered, _waiting, next,
|
||||
_reducer, monitor_pid, monitor_ref, timeout)
|
||||
when spawned == delivered and next == :done do
|
||||
stream_close(waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
# No more tasks to spawn because max == 0 or next is done.
|
||||
# No more tasks to spawned because max == 0 or next is done.
|
||||
defp stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
when max == 0
|
||||
when next == :done do
|
||||
receive do
|
||||
@@ -195,7 +191,7 @@ defmodule Task.Supervised do
|
||||
%{^position => {pid, :running}} = waiting
|
||||
waiting = Map.put(waiting, position, {pid, {:ok, value}})
|
||||
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
{:down, {^monitor_ref, position}, reason} ->
|
||||
waiting =
|
||||
case waiting do
|
||||
@@ -203,53 +199,53 @@ defmodule Task.Supervised do
|
||||
%{^position => {_, :running}} -> Map.put(waiting, position, {nil, {:exit, reason}})
|
||||
end
|
||||
stream_deliver({:cont, acc}, max + 1, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
{:DOWN, ^monitor_ref, _, ^monitor_pid, reason} ->
|
||||
stream_cleanup_inbox(monitor_ref)
|
||||
stream_cleanup_inbox(monitor_pid, monitor_ref)
|
||||
exit({reason, {__MODULE__, :stream, [timeout]}})
|
||||
after
|
||||
timeout ->
|
||||
stream_close(waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
exit({:timeout, {__MODULE__, :stream, [timeout]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
|
||||
reducer, monitor_pid, monitor_ref, timeout) do
|
||||
try do
|
||||
next.({:cont, []})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
stream_close(waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:suspended, [value], next} ->
|
||||
waiting = stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref)
|
||||
waiting = stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_reduce({:cont, acc}, max - 1, spawned + 1, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
{_, [value]} ->
|
||||
waiting = stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref)
|
||||
waiting = stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_reduce({:cont, acc}, max - 1, spawned + 1, delivered, waiting, :done,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
{_, []} ->
|
||||
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, :done,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_deliver({:suspend, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
|
||||
reducer, monitor_pid, monitor_ref, timeout) do
|
||||
{:suspended, acc, &stream_deliver(&1, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)}
|
||||
reducer, monitor_pid, monitor_ref, timeout)}
|
||||
end
|
||||
defp stream_deliver({:halt, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
|
||||
reducer, monitor_pid, monitor_ref, timeout) do
|
||||
stream_reduce({:halt, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
end
|
||||
defp stream_deliver({:cont, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
|
||||
reducer, monitor_pid, monitor_ref, timeout) do
|
||||
case waiting do
|
||||
%{^delivered => {nil, reply}} ->
|
||||
try do
|
||||
@@ -258,38 +254,44 @@ defmodule Task.Supervised do
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
is_function(next) && next.({:halt, []})
|
||||
stream_close(waiting, monitor_pid, monitor_ref, timeout)
|
||||
stream_close(monitor_pid, monitor_ref, timeout)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
pair ->
|
||||
stream_deliver(pair, max, spawned, delivered + 1, Map.delete(waiting, delivered), next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
end
|
||||
%{} ->
|
||||
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
|
||||
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
|
||||
reducer, monitor_pid, monitor_ref, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_close(waiting, monitor_pid, monitor_ref, timeout) do
|
||||
for {_, {pid, _}} <- waiting do
|
||||
pid && Process.unlink(pid)
|
||||
end
|
||||
send(monitor_pid, {:down, monitor_ref})
|
||||
defp stream_close(monitor_pid, monitor_ref, timeout) do
|
||||
send(monitor_pid, {:stop, monitor_ref})
|
||||
receive do
|
||||
{:DOWN, ^monitor_ref, _, _, {:shutdown, ^monitor_ref}} ->
|
||||
{:DOWN, ^monitor_ref, _, _, :normal} ->
|
||||
stream_cleanup_inbox(monitor_pid, monitor_ref)
|
||||
:ok
|
||||
{:DOWN, ^monitor_ref, _, _, reason} ->
|
||||
stream_cleanup_inbox(monitor_pid, monitor_ref)
|
||||
exit({reason, {__MODULE__, :stream, [timeout]}})
|
||||
end
|
||||
stream_cleanup_inbox(monitor_ref)
|
||||
end
|
||||
|
||||
defp stream_cleanup_inbox(monitor_pid, monitor_ref) do
|
||||
receive do
|
||||
{:EXIT, ^monitor_pid, _} -> stream_cleanup_inbox(monitor_ref)
|
||||
after
|
||||
0 -> stream_cleanup_inbox(monitor_ref)
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_cleanup_inbox(monitor_ref) do
|
||||
receive do
|
||||
{{^monitor_ref, _}, _} ->
|
||||
stream_cleanup_inbox(monitor_ref)
|
||||
{:DOWN, {^monitor_ref, _}, _} ->
|
||||
{:down, {^monitor_ref, _}, _} ->
|
||||
stream_cleanup_inbox(monitor_ref)
|
||||
after
|
||||
0 ->
|
||||
@@ -300,38 +302,47 @@ defmodule Task.Supervised do
|
||||
defp stream_mfa({mod, fun, args}, arg), do: {mod, fun, [arg | args]}
|
||||
defp stream_mfa(fun, arg), do: {:erlang, :apply, [fun, [arg]]}
|
||||
|
||||
defp stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref) do
|
||||
owner = self()
|
||||
{type, pid} = spawn.(owner, stream_mfa(mfa, value))
|
||||
send(monitor_pid, {:up, owner, monitor_ref, spawned, type, pid})
|
||||
Map.put(waiting, spawned, {pid, :running})
|
||||
defp stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout) do
|
||||
send(monitor_pid, {:spawn, spawned, value})
|
||||
|
||||
receive do
|
||||
{:spawned, {^monitor_ref, ^spawned}, pid} ->
|
||||
send(pid, {self(), {monitor_ref, spawned}})
|
||||
Map.put(waiting, spawned, {pid, :running})
|
||||
{:DOWN, ^monitor_ref, _, ^monitor_pid, reason} ->
|
||||
stream_cleanup_inbox(monitor_pid, monitor_ref)
|
||||
exit({reason, {__MODULE__, :stream, [timeout]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_monitor(parent_pid) do
|
||||
defp stream_monitor(parent_pid, mfa, spawn, trap_exit) do
|
||||
Process.flag(:trap_exit, trap_exit)
|
||||
parent_ref = Process.monitor(parent_pid)
|
||||
receive do
|
||||
{^parent_pid, monitor_ref} ->
|
||||
stream_monitor(parent_pid, parent_ref, monitor_ref, %{})
|
||||
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, %{})
|
||||
{:DOWN, ^parent_ref, _, _, reason} ->
|
||||
exit(reason)
|
||||
end
|
||||
end
|
||||
|
||||
defp stream_monitor(parent_pid, parent_ref, monitor_ref, counters) do
|
||||
defp stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters) do
|
||||
receive do
|
||||
{:up, owner, ^monitor_ref, counter, type, pid} ->
|
||||
{:spawn, counter, value} ->
|
||||
{type, pid} = spawn.(parent_pid, stream_mfa(mfa, value))
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {owner, {monitor_ref, counter}})
|
||||
send(parent_pid, {:spawned, {monitor_ref, counter}, pid})
|
||||
counters = Map.put(counters, ref, {counter, type, pid})
|
||||
stream_monitor(parent_pid, parent_ref, monitor_ref, counters)
|
||||
{:down, ^monitor_ref} ->
|
||||
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
|
||||
{:stop, ^monitor_ref} ->
|
||||
Process.flag(:trap_exit, true)
|
||||
for {ref, {_counter, _, pid}} <- counters do
|
||||
Process.exit(pid, :kill)
|
||||
receive do
|
||||
{:DOWN, ^ref, _, _, _} -> :ok
|
||||
end
|
||||
end
|
||||
exit({:shutdown, monitor_ref})
|
||||
exit(:normal)
|
||||
{:DOWN, ^parent_ref, _, _, reason} ->
|
||||
for {_ref, {_counter, :link, pid}} <- counters do
|
||||
Process.exit(pid, reason)
|
||||
@@ -340,7 +351,9 @@ defmodule Task.Supervised do
|
||||
{:DOWN, ref, _, _, reason} ->
|
||||
{{counter, _, _}, counters} = Map.pop(counters, ref)
|
||||
send(parent_pid, {:down, {monitor_ref, counter}, reason})
|
||||
stream_monitor(parent_pid, parent_ref, monitor_ref, counters)
|
||||
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
|
||||
{:EXIT, _, _} ->
|
||||
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -106,7 +106,7 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec async_nolink(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async_nolink(supervisor, module, fun, args) do
|
||||
do_async(supervisor, :monitor, module, fun, args)
|
||||
do_async(supervisor, :nolink, module, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -122,7 +122,7 @@ defmodule Task.Supervisor do
|
||||
are emitted in the same order as the original `enumerable`.
|
||||
|
||||
The level of concurrency can be controlled via the `:max_concurrency`
|
||||
option and defaults to `System.schedulers_online/1`. The timeout
|
||||
option and defaults to `System.schedulers_online/0`. The timeout
|
||||
can also be given as option and defaults to 5000 and it defaults to
|
||||
the maximum amount of time to wait without a task reply.
|
||||
|
||||
@@ -133,9 +133,10 @@ defmodule Task.Supervisor do
|
||||
## Options
|
||||
|
||||
* `:max_concurrency` - sets the maximum number of tasks to run
|
||||
at the same time. Defaults to `System.schedulers_online/1`.
|
||||
at the same time. Defaults to `System.schedulers_online/0`.
|
||||
* `:timeout` - the maximum amount of time to wait without
|
||||
receiving a task reply (across all running tasks).
|
||||
Defaults to `5000`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -181,7 +182,7 @@ defmodule Task.Supervisor do
|
||||
Enumerable.t
|
||||
def async_stream_nolink(supervisor, enumerable, module, function, args, options \\ [])
|
||||
when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
build_stream(supervisor, :monitor, enumerable, {module, function, args}, options)
|
||||
build_stream(supervisor, :nolink, enumerable, {module, function, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -197,7 +198,7 @@ defmodule Task.Supervisor do
|
||||
@spec async_stream_nolink(Supervisor.supervisor, Enumerable.t, (term -> term), Keyword.t) ::
|
||||
Enumerable.t
|
||||
def async_stream_nolink(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
|
||||
build_stream(supervisor, :monitor, enumerable, fun, options)
|
||||
build_stream(supervisor, :nolink, enumerable, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -250,7 +251,7 @@ defmodule Task.Supervisor do
|
||||
|
||||
defp do_async(supervisor, link_type, module, fun, args) do
|
||||
owner = self()
|
||||
args = [owner, link_type, get_info(owner), {module, fun, args}]
|
||||
args = [owner, :monitor, get_info(owner), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
if link_type == :link, do: Process.link(pid)
|
||||
ref = Process.monitor(pid)
|
||||
@@ -258,12 +259,12 @@ defmodule Task.Supervisor do
|
||||
%Task{pid: pid, ref: ref, owner: owner}
|
||||
end
|
||||
|
||||
defp build_stream(supervisor, type, enumerable, fun, options) do
|
||||
defp build_stream(supervisor, link_type, enumerable, fun, options) do
|
||||
&Task.Supervised.stream(enumerable, &1, &2, fun, options, fn owner, mfa ->
|
||||
args = [owner, type, get_info(owner), mfa]
|
||||
args = [owner, :monitor, get_info(owner), mfa]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
if type == :link, do: Process.link(pid)
|
||||
{type, pid}
|
||||
if link_type == :link, do: Process.link(pid)
|
||||
{link_type, pid}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -303,12 +303,12 @@ store_each(Check, Kind, File, Location, Module, Defaults, {function, Ann, Name,
|
||||
|
||||
{FinalAnn, FinalLocation, FinalDefaults} = case ets:take(Defs, {def, Tuple}) of
|
||||
[{_, StoredKind, StoredAnn, StoredFile, StoredCheck,
|
||||
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||
_StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||
check_valid_kind(Ann, File, Name, Arity, Kind, StoredKind),
|
||||
(Check and StoredCheck) andalso
|
||||
check_valid_clause(Ann, File, Name, Arity, Kind, Data, StoredAnn, StoredFile),
|
||||
check_valid_defaults(Ann, File, Name, Arity, Kind, Defaults, StoredDefaults, LastDefaults, LastHasBody),
|
||||
{StoredAnn, StoredLocation, {max(Defaults, StoredDefaults), HasBody, Defaults}};
|
||||
{StoredAnn, Location, {max(Defaults, StoredDefaults), HasBody, Defaults}};
|
||||
[] ->
|
||||
{Ann, Location, {Defaults, HasBody, Defaults}}
|
||||
end,
|
||||
|
||||
@@ -353,5 +353,8 @@ deprecation('Elixir.HashSet', _, _) ->
|
||||
"use the MapSet module instead";
|
||||
deprecation('Elixir.Dict', _, _) ->
|
||||
"use the Map module for working with maps or the Keyword module for working with keyword lists";
|
||||
deprecation('Elixir.Set', _, _) ->
|
||||
"use the MapSet module for working with sets";
|
||||
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -114,7 +114,9 @@ translate_gen(_Meta, Left, Right, T, S) ->
|
||||
SF = SL#elixir_scope{extra=S#elixir_scope.extra, extra_guards=nil},
|
||||
|
||||
{TT, {TFilters, TS}} = translate_filters(T, SF),
|
||||
Guards = ExtraGuards ++ translate_guards(TLeftGuards) ++ TFilters,
|
||||
|
||||
%% The list of guards is kept in reverse order
|
||||
Guards = TFilters ++ translate_guards(TLeftGuards) ++ ExtraGuards,
|
||||
{TLeft, TRight, Guards, TT, TS}.
|
||||
|
||||
translate_guards([]) ->
|
||||
|
||||
@@ -85,7 +85,7 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
Forms3 = attributes_form(Line, File, Data, PersistedAttrs, Forms2),
|
||||
|
||||
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
|
||||
warn_unused_attributes(Line, File, Data, PersistedAttrs),
|
||||
warn_unused_attributes(File, Data, PersistedAttrs),
|
||||
|
||||
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
|
||||
|
||||
@@ -152,26 +152,26 @@ build(Line, File, Module, Docs, Lexical) ->
|
||||
end,
|
||||
|
||||
ets:insert(Data, [
|
||||
% {Key, Value, Accumulate?, Read?}
|
||||
{after_compile, [], true, true},
|
||||
{before_compile, [], true, true},
|
||||
{behaviour, [], true, true},
|
||||
{compile, [], true, true},
|
||||
{derive, [], true, true},
|
||||
{dialyzer, [], true, true},
|
||||
{external_resource, [], true, true},
|
||||
{moduledoc, nil, false, true},
|
||||
{on_definition, OnDefinition, true, true},
|
||||
{on_load, [], true, true},
|
||||
% {Key, Value, Accumulate?, UnreadLine}
|
||||
{after_compile, [], true, nil},
|
||||
{before_compile, [], true, nil},
|
||||
{behaviour, [], true, nil},
|
||||
{compile, [], true, nil},
|
||||
{derive, [], true, nil},
|
||||
{dialyzer, [], true, nil},
|
||||
{external_resource, [], true, nil},
|
||||
{moduledoc, nil, false, nil},
|
||||
{on_definition, OnDefinition, true, nil},
|
||||
{on_load, [], true, nil},
|
||||
|
||||
% Types
|
||||
{callback, [], true, true},
|
||||
{opaque, [], true, true},
|
||||
{optional_callbacks, [], true, true},
|
||||
{macrocallback, [], true, true},
|
||||
{spec, [], true, true},
|
||||
{type, [], true, true},
|
||||
{typep, [], true, true}
|
||||
{callback, [], true, nil},
|
||||
{opaque, [], true, nil},
|
||||
{optional_callbacks, [], true, nil},
|
||||
{macrocallback, [], true, nil},
|
||||
{spec, [], true, nil},
|
||||
{type, [], true, nil},
|
||||
{typep, [], true, nil}
|
||||
]),
|
||||
|
||||
Persisted = [behaviour, on_load, compile, external_resource, dialyzer, vsn],
|
||||
@@ -425,11 +425,11 @@ check_module_availability(Line, File, Module) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
warn_unused_attributes(Line, File, Data, PersistedAttrs) ->
|
||||
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition | PersistedAttrs],
|
||||
Keys = ets:select(Data, [{{'$1', '_', '_', false}, [{is_atom, '$1'}], ['$1']}]),
|
||||
warn_unused_attributes(File, Data, PersistedAttrs) ->
|
||||
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition, impl | PersistedAttrs],
|
||||
Keys = ets:select(Data, [{{'$1', '_', '_', '$2'}, [{is_atom, '$1'}, {is_integer, '$2'}], [['$1', '$2']]}]),
|
||||
[elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_attribute, Key}) ||
|
||||
Key <- Keys, not lists:member(Key, ReservedAttrs)].
|
||||
[Key, Line] <- Keys, not lists:member(Key, ReservedAttrs)].
|
||||
|
||||
% __INFO__
|
||||
|
||||
|
||||
@@ -189,12 +189,15 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
|
||||
do_quote_tuple({'__aliases__', AliasMeta, [H | T]}, Q, E);
|
||||
do_quote_tuple('__aliases__', AliasMeta, [H | T], Q, E);
|
||||
|
||||
%% Vars
|
||||
|
||||
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true, imports_hygiene=true} = Q, E) when is_atom(Left) ->
|
||||
do_quote_import(Left, Meta, Q#elixir_quote.context, Q, E);
|
||||
|
||||
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
|
||||
do_quote_tuple({Left, Meta, Q#elixir_quote.context}, Q, E);
|
||||
do_quote_tuple(Left, Meta, Q#elixir_quote.context, Q, E);
|
||||
|
||||
%% Unquote
|
||||
|
||||
@@ -207,28 +210,11 @@ do_quote({{'.', Meta, [Left, unquote]}, _, [Expr]}, #elixir_quote{unquote=true}
|
||||
%% Imports
|
||||
|
||||
do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
|
||||
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, E);
|
||||
|
||||
do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
NewMeta = case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, E) of
|
||||
false ->
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end;
|
||||
Receiver ->
|
||||
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
||||
end,
|
||||
|
||||
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
do_quote_import(Name, Meta, ArgsOrAtom, Q, E);
|
||||
|
||||
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context),
|
||||
@@ -300,6 +286,26 @@ bad_escape(Arg) ->
|
||||
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>).
|
||||
|
||||
do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q, E) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
NewMeta = case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, E) of
|
||||
false ->
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end;
|
||||
Receiver ->
|
||||
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
||||
end,
|
||||
|
||||
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
|
||||
do_quote_tuple(Annotated, Q, E).
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
||||
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
|
||||
Q#elixir_quote.context],
|
||||
@@ -313,13 +319,16 @@ do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
|
||||
false -> Meta;
|
||||
Receiver -> keystore(import_fa, Meta, {Receiver, Q#elixir_quote.context})
|
||||
end,
|
||||
do_quote_tuple({Target, NewMeta, Args}, Q, E).
|
||||
do_quote_tuple(Target, NewMeta, Args, Q, E).
|
||||
|
||||
do_quote_tuple({Left, Meta, Right}, Q, E) ->
|
||||
do_quote_tuple(Left, Meta, Right, Q, E).
|
||||
|
||||
% In a def unquote(name)(args) expression name will be an atom literal,
|
||||
% thus location: :keep will not have enough information to generate the proper file/line annotation.
|
||||
% This alters metadata to force Elixir to show the file to which the definition is added
|
||||
% instead of the file where definition is quoted (i.e. we behave the opposite to location: :keep).
|
||||
do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?defs(Left) ->
|
||||
do_quote_tuple(Left, Meta, [{{unquote, _, _}, _, _}, _] = Right, Q, E) when ?defs(Left) ->
|
||||
{TLeft, LQ} = do_quote(Left, Q, E),
|
||||
{[Head, Body], RQ} = do_quote(Right, LQ, E),
|
||||
{'{}', [], [HLeft, HMeta, HRight]} = Head,
|
||||
@@ -327,7 +336,7 @@ do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?d
|
||||
NewHead = {'{}', [], [HLeft, NewMeta, HRight]},
|
||||
{{'{}', [], [TLeft, meta(Meta, Q), [NewHead, Body]]}, RQ};
|
||||
|
||||
do_quote_tuple({Left, Meta, Right}, Q, E) ->
|
||||
do_quote_tuple(Left, Meta, Right, Q, E) ->
|
||||
{TLeft, LQ} = do_quote(Left, Q, E),
|
||||
{TRight, RQ} = do_quote(Right, LQ, E),
|
||||
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
|
||||
|
||||
@@ -264,16 +264,16 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
test "format_exit with call with exception" do
|
||||
Process.flag(:trap_exit, true)
|
||||
# Fake reason to prevent error_logger printing to stdout
|
||||
fsm_reason = {%ArgumentError{}, [{:not_a_real_module, :function, 0, []}]}
|
||||
reason = try do
|
||||
:gen_fsm.sync_send_event(spawn(fn() ->
|
||||
Process.sleep(200)
|
||||
exit(fsm_reason)
|
||||
end), :hello)
|
||||
catch
|
||||
:exit, reason -> reason
|
||||
end
|
||||
exit_reason = {%ArgumentError{}, [{:not_a_real_module, :function, 0, []}]}
|
||||
exit_fun = fn() -> receive do: (_ -> exit(exit_reason)) end
|
||||
reason =
|
||||
try do
|
||||
:gen_fsm.sync_send_event(spawn_link(exit_fun), :hello)
|
||||
catch
|
||||
:exit, reason -> reason
|
||||
end
|
||||
|
||||
formatted = Exception.format_exit(reason)
|
||||
assert formatted =~ ~r"exited in: :gen_fsm\.sync_send_event\(#PID<\d+\.\d+\.\d+>, :hello\)"
|
||||
@@ -283,14 +283,16 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
test "format_exit with nested calls" do
|
||||
Process.flag(:trap_exit, true)
|
||||
# Fake reason to prevent error_logger printing to stdout
|
||||
event_fun = fn() ->
|
||||
Process.sleep(200)
|
||||
exit(:normal)
|
||||
end
|
||||
server_pid = spawn(fn()-> :gen_event.call(spawn(event_fun), :handler, :hello) end)
|
||||
exit_fun = fn() -> receive do: (_ -> exit(:normal)) end
|
||||
outer_pid =
|
||||
spawn_link(fn()->
|
||||
Process.flag(:trap_exit, true)
|
||||
:gen_event.call(spawn_link(exit_fun), :handler, :hello)
|
||||
end)
|
||||
reason = try do
|
||||
:gen_server.call(server_pid, :hi)
|
||||
:gen_server.call(outer_pid, :hi)
|
||||
catch
|
||||
:exit, reason -> reason
|
||||
end
|
||||
@@ -302,15 +304,17 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
test "format_exit with nested calls and exception" do
|
||||
Process.flag(:trap_exit, true)
|
||||
# Fake reason to prevent error_logger printing to stdout
|
||||
event_reason = {%ArgumentError{}, [{:not_a_real_module, :function, 0, []}]}
|
||||
event_fun = fn() ->
|
||||
Process.sleep(200)
|
||||
exit(event_reason)
|
||||
end
|
||||
server_pid = spawn(fn()-> :gen_event.call(spawn(event_fun), :handler, :hello) end)
|
||||
exit_reason = {%ArgumentError{}, [{:not_a_real_module, :function, 0, []}]}
|
||||
exit_fun = fn() -> receive do: (_ -> exit(exit_reason)) end
|
||||
outer_pid =
|
||||
spawn_link(fn()->
|
||||
Process.flag(:trap_exit, true)
|
||||
:gen_event.call(spawn_link(exit_fun), :handler, :hello)
|
||||
end)
|
||||
reason = try do
|
||||
:gen_server.call(server_pid, :hi)
|
||||
:gen_server.call(outer_pid, :hi)
|
||||
catch
|
||||
:exit, reason -> reason
|
||||
end
|
||||
@@ -386,8 +390,9 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
test "UndefinedFunctionError when the mfa is a macro but require wasn't called" do
|
||||
_ = Code.ensure_loaded(Integer)
|
||||
assert %UndefinedFunctionError{module: Integer, function: :is_odd, arity: 1} |> message ==
|
||||
"function Integer.is_odd/1 is undefined or private but Integer defines " <>
|
||||
"function Integer.is_odd/1 is undefined or private. However there is " <>
|
||||
"a macro with the same name and arity. Be sure to require Integer if " <>
|
||||
"you intend to invoke this macro"
|
||||
end
|
||||
|
||||
@@ -80,6 +80,7 @@ defmodule FloatTest do
|
||||
assert Float.ceil(0.32453e-10) === 1.0
|
||||
assert Float.ceil(-0.32453e-10) === 0.0
|
||||
assert Float.ceil(1.32453e-10) === 1.0
|
||||
assert Float.ceil(0.0) === 0.0
|
||||
end
|
||||
|
||||
test "ceil with precision" do
|
||||
@@ -94,6 +95,8 @@ defmodule FloatTest do
|
||||
|
||||
assert Float.ceil(12.32453e-20, 2) === 0.01
|
||||
assert Float.ceil(-12.32453e-20, 2) === 0.0
|
||||
|
||||
assert Float.ceil(0.0, 2) === 0.0
|
||||
end
|
||||
|
||||
test "round" do
|
||||
|
||||
@@ -3,6 +3,8 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule GenServerTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
@@ -161,4 +163,13 @@ defmodule GenServerTest do
|
||||
{:ok, _} = GenServer.start(Stack, [], name: :stack_for_stop)
|
||||
assert GenServer.stop(:stack_for_stop, :normal) == :ok
|
||||
end
|
||||
|
||||
test "handle_info/2 warning" do
|
||||
{:ok, pid} = GenServer.start(Stack, [])
|
||||
log = capture_log(fn ->
|
||||
send(pid, :foo)
|
||||
GenServer.stop(pid)
|
||||
end)
|
||||
assert log =~ "[warn] GenServerTest.Stack #{inspect pid} received unexpected message in handle_info/2: :foo"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,6 +51,12 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert for(x when x == 3 when x == 7 <- 1..10, do: x) == [3, 7]
|
||||
end
|
||||
|
||||
test "for comprehensions with guards and filters" do
|
||||
assert for({var, _} when is_atom(var) <- [{:foo, 1}, {2, :bar}],
|
||||
var = Atom.to_string(var),
|
||||
do: var) == ["foo"]
|
||||
end
|
||||
|
||||
test "for comprehensions with map key matching" do
|
||||
maps = [%{x: 1}, %{y: 2}, %{x: 3}]
|
||||
assert for(%{x: v} <- maps, do: v * 2) == [2, 6]
|
||||
|
||||
@@ -153,7 +153,7 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
|
||||
test "locals: in guards" do
|
||||
assert expand(quote(do: fn pid when :erlang.==(pid, self) -> pid end)) ==
|
||||
assert expand_and_clean(quote(do: fn pid when :erlang.==(pid, self) -> pid end), [:import, :context]) ==
|
||||
quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end)
|
||||
end
|
||||
|
||||
@@ -337,22 +337,22 @@ defmodule Kernel.ExpansionTest do
|
||||
## Cond
|
||||
|
||||
test "cond: expands each clause" do
|
||||
assert expand_and_clean(quote do: (cond do x = 1 -> x; _ -> x end)) ==
|
||||
assert expand(quote do: (cond do x = 1 -> x; _ -> x end)) ==
|
||||
quote do: (cond do x = 1 -> x; _ -> x() end)
|
||||
end
|
||||
|
||||
test "cond: does not share lexical scope between clauses" do
|
||||
assert expand_and_clean(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
assert expand(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
quote do: (cond do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
|
||||
end
|
||||
|
||||
test "cond: does not leaks vars on head" do
|
||||
assert expand_and_clean(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
|
||||
quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x(), y()))
|
||||
end
|
||||
|
||||
test "cond: leaks vars" do
|
||||
assert expand_and_clean(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
|
||||
quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
@@ -365,27 +365,27 @@ defmodule Kernel.ExpansionTest do
|
||||
## Case
|
||||
|
||||
test "case: expands each clause" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x; _ -> x end)) ==
|
||||
assert expand(quote do: (case w do x -> x; _ -> x end)) ==
|
||||
quote do: (case w() do x -> x; _ -> x() end)
|
||||
end
|
||||
|
||||
test "case: does not share lexical scope between clauses" do
|
||||
assert expand_and_clean(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
assert expand(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
quote do: (case w() do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
|
||||
end
|
||||
|
||||
test "case: expands guards" do
|
||||
assert expand_and_clean(quote do: (case w do x when x when __ENV__.context -> true end)) ==
|
||||
assert expand(quote do: (case w do x when x when __ENV__.context -> true end)) ==
|
||||
quote do: (case w() do x when x when :guard -> true end)
|
||||
end
|
||||
|
||||
test "case: does not leaks vars on head" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
quote do: (case w() do x -> x; y -> y end; :erlang.+(x(), y()))
|
||||
end
|
||||
|
||||
test "case: leaks vars" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
quote do: (case w() do x -> x = x; y -> y = y end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
@@ -398,32 +398,32 @@ defmodule Kernel.ExpansionTest do
|
||||
## Receive
|
||||
|
||||
test "receive: expands each clause" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x; _ -> x end)) ==
|
||||
assert expand(quote do: (receive do x -> x; _ -> x end)) ==
|
||||
quote do: (receive do x -> x; _ -> x() end)
|
||||
end
|
||||
|
||||
test "receive: does not share lexical scope between clauses" do
|
||||
assert expand_and_clean(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
assert expand(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
|
||||
quote do: (receive do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
|
||||
end
|
||||
|
||||
test "receive: expands guards" do
|
||||
assert expand_and_clean(quote do: (receive do x when x when __ENV__.context -> true end)) ==
|
||||
assert expand(quote do: (receive do x when x when __ENV__.context -> true end)) ==
|
||||
quote do: (receive do x when x when :guard -> true end)
|
||||
end
|
||||
|
||||
test "receive: does not leaks clause vars" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
quote do: (receive do x -> x; y -> y end; :erlang.+(x(), y()))
|
||||
end
|
||||
|
||||
test "receive: leaks vars" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
assert expand(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
test "receive: leaks vars on after" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
|
||||
assert expand(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
|
||||
quote do: (receive do x -> x = x after y() -> y(); w = y() end; :erlang.+(x, w))
|
||||
end
|
||||
|
||||
@@ -540,8 +540,8 @@ defmodule Kernel.ExpansionTest do
|
||||
13
|
||||
end
|
||||
|
||||
defp expand_and_clean(expr) do
|
||||
cleaner = &Keyword.drop(&1, [:export])
|
||||
defp expand_and_clean(expr, vars) do
|
||||
cleaner = &Keyword.drop(&1, vars)
|
||||
expr
|
||||
|> expand_env(__ENV__)
|
||||
|> elem(0)
|
||||
|
||||
@@ -434,4 +434,18 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
|
||||
test "explicitly overridden imports" do
|
||||
assert with_length() == 5
|
||||
end
|
||||
|
||||
defmodule BinaryUtils do
|
||||
defmacro int32 do
|
||||
quote do
|
||||
integer-size(32)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "checks the context also for variables to zero-arity functions" do
|
||||
import BinaryUtils
|
||||
{:int32, meta, __MODULE__} = quote do: int32
|
||||
assert meta[:import] == BinaryUtils
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,31 @@ defmodule Kernel.WarningTest do
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test "unused variable in redefined function in different file" do
|
||||
output = capture_err(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
def function(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
||||
Code.eval_string("""
|
||||
defmodule RedefineSample do
|
||||
use Sample
|
||||
def function(var123), do: nil
|
||||
end
|
||||
""", [], file: "redefine_sample.ex")
|
||||
end)
|
||||
assert output =~ "redefine_sample.ex:3"
|
||||
assert output =~ "variable \"var123\" is unused"
|
||||
after
|
||||
purge Sample
|
||||
purge RedefineSample
|
||||
end
|
||||
|
||||
test "useless literal" do
|
||||
message = "code block contains unused literal \"oops\""
|
||||
|
||||
@@ -685,12 +710,11 @@ defmodule Kernel.WarningTest do
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
@at "Something"
|
||||
Module.put_attribute(__MODULE__, :put_attribute, "Something")
|
||||
end
|
||||
"""
|
||||
end)
|
||||
assert content =~ "module attribute @at was set but never used"
|
||||
assert content =~ "module attribute @put_attribute was set but never used"
|
||||
assert content =~ "nofile:2"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
@@ -60,7 +60,7 @@ defmodule ModuleTest do
|
||||
|
||||
test "module attributes returns value" do
|
||||
in_module do
|
||||
assert (@return [:foo, :bar]) == [:foo, :bar]
|
||||
assert (@return [:foo, :bar]) == :ok
|
||||
_ = @return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -256,6 +256,11 @@ defmodule OptionParserTest do
|
||||
== {[foo: true, boo: "-"], ["-"], []}
|
||||
end
|
||||
|
||||
test "allow nonexistent atoms" do
|
||||
assert OptionParser.parse(["--option-key-creates-atom"], allow_nonexistent_atoms: true) ==
|
||||
{[{String.to_atom("option_key_creates_atom"), true}], [], []}
|
||||
end
|
||||
|
||||
test "correctly handles negative integers" do
|
||||
assert OptionParser.parse(["arg1", "-43"])
|
||||
== {[], ["arg1", "-43"], []}
|
||||
|
||||
@@ -211,6 +211,8 @@ defmodule PathTest do
|
||||
assert Path.join(['/foo/', "/bar/"]) == "/foo/bar"
|
||||
assert Path.join(["/", ""]) == "/"
|
||||
assert Path.join(["/", "", "bar"]) == "/bar"
|
||||
assert Path.join(['foo', [?b, "a", ?r]]) == "foo/bar"
|
||||
assert Path.join([[?f, 'o', "o"]]) == "foo"
|
||||
end
|
||||
|
||||
test "join two" do
|
||||
|
||||
@@ -247,6 +247,7 @@ defmodule RegexTest do
|
||||
assert matches_escaped?("# lol")
|
||||
|
||||
assert matches_escaped?("\\A.^$*+?()[{\\| \t\n\x20\\z #hello\u202F\u205F")
|
||||
assert Regex.match? Regex.compile!("[" <> Regex.escape("!-#") <> "]"), "-"
|
||||
|
||||
assert Regex.escape("{}") == "\\{\\}"
|
||||
assert Regex.escape("[]") == "\\[\\]"
|
||||
|
||||
@@ -481,6 +481,14 @@ defmodule StreamTest do
|
||||
assert Process.get(:stream_transform)
|
||||
end
|
||||
|
||||
test "transform/3 (via flat_map) handles multiple returns from suspension" do
|
||||
assert [false]
|
||||
|> Stream.take(1)
|
||||
|> Stream.concat([true])
|
||||
|> Stream.flat_map(&[&1])
|
||||
|> Enum.to_list() == [false, true]
|
||||
end
|
||||
|
||||
test "iterate/2" do
|
||||
stream = Stream.iterate(0, &(&1+2))
|
||||
assert Enum.take(stream, 5) == [0, 2, 4, 6, 8]
|
||||
|
||||
@@ -449,7 +449,8 @@ defmodule StringTest do
|
||||
# CLRF
|
||||
assert String.graphemes("\r\n\f") == ["\r\n", "\f"]
|
||||
# Regional indicator
|
||||
assert String.graphemes("\u{1F1E6}\u{1F1E7}\u{1F1E8}") == ["\u{1F1E6}\u{1F1E7}\u{1F1E8}"]
|
||||
assert String.graphemes("\u{1F1E6}\u{1F1E7}") == ["\u{1F1E6}\u{1F1E7}"]
|
||||
assert String.graphemes("\u{1F1E6}\u{1F1E7}\u{1F1E8}") == ["\u{1F1E6}\u{1F1E7}", "\u{1F1E8}"]
|
||||
# Hangul
|
||||
assert String.graphemes("\u1100\u115D\uB4A4") == ["ᄀᅝ뒤"]
|
||||
# Special Marking with Extended
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule SystemTest do
|
||||
assert is_binary build_info[:version]
|
||||
|
||||
if build_info[:revision] != "" do
|
||||
assert String.length(build_info[:revision]) == 7
|
||||
assert String.length(build_info[:revision]) >= 7
|
||||
end
|
||||
|
||||
version_file = Path.join([__DIR__, "../../../..", "VERSION"]) |> Path.expand
|
||||
|
||||
@@ -492,6 +492,7 @@ defmodule TaskTest do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert 1..4 |> Task.async_stream(&exit/1, @opts) |> Enum.to_list ==
|
||||
[exit: 1, exit: 2, exit: 3, exit: 4]
|
||||
refute_received {:EXIT, _, _}
|
||||
end
|
||||
|
||||
test "shuts down unused tasks" do
|
||||
|
||||
+139
-62
@@ -52,20 +52,6 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Hangul L
|
||||
for codepoint <- cluster["L"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_hangul_l_size(rest, unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Hangul T
|
||||
for codepoint <- cluster["T"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_hangul_t_size(rest, unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Regional
|
||||
for codepoint <- cluster["Regional_Indicator"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
@@ -73,14 +59,48 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
# Handle extended entries
|
||||
# Handle Hangul L
|
||||
for codepoint <- cluster["L"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_hangul_l_size(rest, unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Hangul V
|
||||
for codepoint <- cluster["LV"] ++ cluster["V"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_hangul_v_size(rest, unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Hangul T
|
||||
for codepoint <- cluster["LVT"] ++ cluster["T"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_hangul_t_size(rest, unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle E_Base
|
||||
for codepoint <- cluster["E_Base"] ++ cluster["E_Base_GAZ"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_extend_size(rest, unquote(byte_size(codepoint)), :e_base)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle ZWJ
|
||||
for codepoint <- cluster["ZWJ"] do
|
||||
def next_grapheme_size(<<unquote(codepoint), rest::binary>>) do
|
||||
next_extend_size(rest, unquote(byte_size(codepoint)), :zwj)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle extended entries
|
||||
def next_grapheme_size(<<cp::utf8, rest::binary>>) do
|
||||
case cp do
|
||||
x when x <= 0x007F -> next_extend_size(rest, 1)
|
||||
x when x <= 0x07FF -> next_extend_size(rest, 2)
|
||||
x when x <= 0xFFFF -> next_extend_size(rest, 3)
|
||||
_ -> next_extend_size(rest, 4)
|
||||
x when x <= 0x007F -> next_extend_size(rest, 1, :other)
|
||||
x when x <= 0x07FF -> next_extend_size(rest, 2, :other)
|
||||
x when x <= 0xFFFF -> next_extend_size(rest, 3, :other)
|
||||
_ -> next_extend_size(rest, 4, :other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,73 +112,120 @@ defmodule String.Unicode do
|
||||
nil
|
||||
end
|
||||
|
||||
# Handle Hangul L
|
||||
for codepoint <- cluster["L"] do
|
||||
defp next_hangul_l_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_hangul_l_size(rest, size + unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["LV"] do
|
||||
defp next_hangul_l_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_hangul_v_size(rest, size + unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["LVT"] do
|
||||
defp next_hangul_l_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_hangul_t_size(rest, size + unquote(byte_size(codepoint)))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle hanguls
|
||||
defp next_hangul_l_size(rest, size) do
|
||||
next_hangul_v_size(rest, size)
|
||||
end
|
||||
|
||||
# Handle Hangul V
|
||||
for codepoint <- cluster["V"] do
|
||||
defp next_hangul_v_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_hangul_v_size(rest, size + unquote(byte_size(codepoint)))
|
||||
case next_hangul(rest, size) do
|
||||
{:l, rest, size} -> next_hangul_l_size(rest, size)
|
||||
{:v, rest, size} -> next_hangul_v_size(rest, size)
|
||||
{:lv, rest, size} -> next_hangul_v_size(rest, size)
|
||||
{:lvt, rest, size} -> next_hangul_t_size(rest, size)
|
||||
_ -> next_extend_size(rest, size, :other)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul_v_size(rest, size) do
|
||||
next_hangul_t_size(rest, size)
|
||||
end
|
||||
|
||||
# Handle Hangul T
|
||||
for codepoint <- cluster["T"] do
|
||||
defp next_hangul_t_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_hangul_t_size(rest, size + unquote(byte_size(codepoint)))
|
||||
case next_hangul(rest, size) do
|
||||
{:v, rest, size} -> next_hangul_v_size(rest, size)
|
||||
{:t, rest, size} -> next_hangul_t_size(rest, size)
|
||||
_ -> next_extend_size(rest, size, :other)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul_t_size(rest, size) do
|
||||
next_extend_size(rest, size)
|
||||
case next_hangul(rest, size) do
|
||||
{:t, rest, size} -> next_hangul_t_size(rest, size)
|
||||
_ -> next_extend_size(rest, size, :other)
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["L"] do
|
||||
defp next_hangul(<<unquote(codepoint), rest::binary>>, size) do
|
||||
{:l, rest, size + unquote(byte_size(codepoint))}
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["V"] do
|
||||
defp next_hangul(<<unquote(codepoint), rest::binary>>, size) do
|
||||
{:v, rest, size + unquote(byte_size(codepoint))}
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["T"] do
|
||||
defp next_hangul(<<unquote(codepoint), rest::binary>>, size) do
|
||||
{:t, rest, size + unquote(byte_size(codepoint))}
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["LV"] do
|
||||
defp next_hangul(<<unquote(codepoint), rest::binary>>, size) do
|
||||
{:lv, rest, size + unquote(byte_size(codepoint))}
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["LVT"] do
|
||||
defp next_hangul(<<unquote(codepoint), rest::binary>>, size) do
|
||||
{:lvt, rest, size + unquote(byte_size(codepoint))}
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul(_, _) do
|
||||
false
|
||||
end
|
||||
|
||||
# Handle regional
|
||||
for codepoint <- cluster["Regional_Indicator"] do
|
||||
defp next_regional_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_regional_size(rest, size + unquote(byte_size(codepoint)))
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :other)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_regional_size(rest, size) do
|
||||
next_extend_size(rest, size)
|
||||
next_extend_size(rest, size, :other)
|
||||
end
|
||||
|
||||
# Handle Extend+SpacingMark
|
||||
for codepoint <- cluster["Extend"] ++ cluster["SpacingMark"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)))
|
||||
# Handle Extend+SpacingMark+ZWJ
|
||||
for codepoint <- cluster["Extend"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, marker) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), keep_ebase(marker))
|
||||
end
|
||||
end
|
||||
|
||||
defp next_extend_size(rest, size) do
|
||||
for codepoint <- cluster["SpacingMark"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, _marker) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :other)
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["ZWJ"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, _marker) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :zwj)
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["E_Modifier"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, :e_base) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :other)
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["Glue_After_Zwj"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, :zwj) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :other)
|
||||
end
|
||||
end
|
||||
|
||||
for codepoint <- cluster["E_Base_GAZ"] do
|
||||
defp next_extend_size(<<unquote(codepoint), rest::binary>>, size, :zwj) do
|
||||
next_extend_size(rest, size + unquote(byte_size(codepoint)), :e_base)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_extend_size(rest, size, _) do
|
||||
{size, rest}
|
||||
end
|
||||
|
||||
defp keep_ebase(:e_base), do: :e_base
|
||||
defp keep_ebase(_), do: :other
|
||||
|
||||
# Handle Prepend
|
||||
for codepoint <- cluster["Prepend"] do
|
||||
defp next_prepend_size(<<unquote(codepoint), rest::binary>>, size) do
|
||||
@@ -166,8 +233,18 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
# However, if we see a control character, we have to break it
|
||||
for codepoint <- cluster["CR"] ++ cluster["LF"] ++ cluster["Control"] do
|
||||
defp next_prepend_size(<<unquote(codepoint), _::binary>> = rest, size) do
|
||||
{size, rest}
|
||||
end
|
||||
end
|
||||
|
||||
defp next_prepend_size(rest, size) do
|
||||
{size, rest}
|
||||
case next_grapheme_size(rest) do
|
||||
{more, rest} -> {more + size, rest}
|
||||
nil -> {size, rest}
|
||||
end
|
||||
end
|
||||
|
||||
# Graphemes
|
||||
|
||||
+116
-50
@@ -1,20 +1,22 @@
|
||||
defmodule IEx.Autocomplete do
|
||||
@moduledoc false
|
||||
|
||||
def expand('') do
|
||||
def expand(expr, server \\ IEx.Server)
|
||||
|
||||
def expand('', _server) do
|
||||
expand_import("")
|
||||
end
|
||||
|
||||
def expand([h | t]=expr) do
|
||||
def expand([h | t]=expr, server) do
|
||||
cond do
|
||||
h === ?. and t != [] ->
|
||||
expand_dot(reduce(t))
|
||||
expand_dot(reduce(t), server)
|
||||
h === ?: and t == [] ->
|
||||
expand_erlang_modules()
|
||||
identifier?(h) ->
|
||||
expand_expr(reduce(expr))
|
||||
expand_expr(reduce(expr), server)
|
||||
(h == ?/) and t != [] and identifier?(hd(t)) ->
|
||||
expand_expr(reduce(t))
|
||||
expand_expr(reduce(t), server)
|
||||
h in '([{' ->
|
||||
expand('')
|
||||
true ->
|
||||
@@ -26,31 +28,33 @@ defmodule IEx.Autocomplete do
|
||||
(h in ?a..?z) or (h in ?A..?Z) or (h in ?0..?9) or h in [?_, ??, ?!]
|
||||
end
|
||||
|
||||
defp expand_dot(expr) do
|
||||
defp expand_dot(expr, server) do
|
||||
case Code.string_to_quoted expr do
|
||||
{:ok, atom} when is_atom(atom) ->
|
||||
expand_call(atom, "")
|
||||
expand_call(atom, "", server)
|
||||
{:ok, {:__aliases__, _, list}} ->
|
||||
expand_elixir_modules(list, "")
|
||||
expand_elixir_modules(list, "", server)
|
||||
{:ok, {_, _, _} = ast_node} ->
|
||||
expand_call(ast_node, "", server)
|
||||
_ ->
|
||||
no()
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_expr(expr) do
|
||||
defp expand_expr(expr, server) do
|
||||
case Code.string_to_quoted expr do
|
||||
{:ok, atom} when is_atom(atom) ->
|
||||
expand_erlang_modules(Atom.to_string(atom))
|
||||
{:ok, {atom, _, nil}} when is_atom(atom) ->
|
||||
expand_import(Atom.to_string(atom))
|
||||
{:ok, {:__aliases__, _, [root]}} ->
|
||||
expand_elixir_modules([], Atom.to_string(root))
|
||||
expand_elixir_modules([], Atom.to_string(root), server)
|
||||
{:ok, {:__aliases__, _, [h | _] = list}} when is_atom(h) ->
|
||||
hint = Atom.to_string(List.last(list))
|
||||
list = Enum.take(list, length(list) - 1)
|
||||
expand_elixir_modules(list, hint)
|
||||
{:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) ->
|
||||
expand_call(mod, Atom.to_string(fun))
|
||||
expand_elixir_modules(list, hint, server)
|
||||
{:ok, {{:., _, [ast_node, fun]}, _, []}} when is_atom(fun) ->
|
||||
expand_call(ast_node, Atom.to_string(fun), server)
|
||||
_ ->
|
||||
no()
|
||||
end
|
||||
@@ -105,21 +109,38 @@ defmodule IEx.Autocomplete do
|
||||
## Expand calls
|
||||
|
||||
# :atom.fun
|
||||
defp expand_call(mod, hint) when is_atom(mod) do
|
||||
defp expand_call(mod, hint, _server) when is_atom(mod) do
|
||||
expand_require(mod, hint)
|
||||
end
|
||||
|
||||
# Elixir.fun
|
||||
defp expand_call({:__aliases__, _, list}, hint) do
|
||||
expand_alias(list)
|
||||
|> normalize_module
|
||||
|> expand_require(hint)
|
||||
defp expand_call({:__aliases__, _, list}, hint, server) do
|
||||
case expand_alias(list, server) do
|
||||
{:ok, alias} -> expand_require(alias, hint)
|
||||
:error -> no()
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_call(_, _) do
|
||||
# variable.fun_or_key
|
||||
defp expand_call({_, _, _} = ast_node, hint, server) do
|
||||
case value_from_binding(ast_node, server) do
|
||||
{:ok, mod} when is_atom(mod) -> expand_call(mod, hint, server)
|
||||
{:ok, map} when is_map(map) -> expand_map_field_access(map, hint)
|
||||
_otherwise -> no()
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_call(_, _, _) do
|
||||
no()
|
||||
end
|
||||
|
||||
defp expand_map_field_access(map, hint) do
|
||||
case match_map_fields(map, hint) do
|
||||
[%{kind: :map_key, name: name, value_is_map: false}] when name == hint -> no()
|
||||
map_fields when is_list(map_fields) -> format_expansion(map_fields, hint)
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_require(mod, hint) do
|
||||
format_expansion match_module_funs(mod, hint), hint
|
||||
end
|
||||
@@ -145,43 +166,38 @@ defmodule IEx.Autocomplete do
|
||||
|
||||
## Elixir modules
|
||||
|
||||
defp expand_elixir_modules([], hint) do
|
||||
expand_elixir_modules(Elixir, hint, match_aliases(hint))
|
||||
defp expand_elixir_modules([], hint, server) do
|
||||
aliases = match_aliases(hint, server)
|
||||
expand_elixir_modules_from_aliases(Elixir, hint, aliases)
|
||||
end
|
||||
|
||||
defp expand_elixir_modules(list, hint) do
|
||||
expand_alias(list)
|
||||
|> normalize_module
|
||||
|> expand_elixir_modules(hint, [])
|
||||
defp expand_elixir_modules(list, hint, server) do
|
||||
case expand_alias(list, server) do
|
||||
{:ok, alias} -> expand_elixir_modules_from_aliases(alias, hint, [])
|
||||
:error -> no()
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_elixir_modules(mod, hint, aliases) do
|
||||
defp expand_elixir_modules_from_aliases(mod, hint, aliases) do
|
||||
aliases
|
||||
|> Kernel.++(match_elixir_modules(mod, hint))
|
||||
|> Kernel.++(match_module_funs(mod, hint))
|
||||
|> format_expansion(hint)
|
||||
end
|
||||
|
||||
defp expand_alias([name | rest] = list) do
|
||||
module = Module.concat(Elixir, name)
|
||||
Enum.find_value env_aliases(), list, fn {alias, mod} ->
|
||||
if alias === module do
|
||||
case Atom.to_string(mod) do
|
||||
"Elixir." <> mod ->
|
||||
Module.concat [mod | rest]
|
||||
_ ->
|
||||
mod
|
||||
end
|
||||
end
|
||||
defp expand_alias([name | rest], server) when is_atom(name) do
|
||||
case Keyword.fetch(aliases_from_env(server), Module.concat(Elixir, name)) do
|
||||
{:ok, name} when rest == [] -> {:ok, name}
|
||||
{:ok, name} -> {:ok, Module.concat([name | rest])}
|
||||
:error -> {:ok, Module.concat([name | rest])}
|
||||
end
|
||||
end
|
||||
|
||||
defp env_aliases do
|
||||
Application.get_env(:iex, :autocomplete_server).current_env.aliases
|
||||
defp expand_alias([_ | _], _) do
|
||||
:error
|
||||
end
|
||||
|
||||
defp match_aliases(hint) do
|
||||
for {alias, _mod} <- env_aliases(),
|
||||
defp match_aliases(hint, server) do
|
||||
for {alias, _mod} <- aliases_from_env(server),
|
||||
[name] = Module.split(alias),
|
||||
starts_with?(name, hint) do
|
||||
%{kind: :module, type: :alias, name: name}
|
||||
@@ -203,14 +219,6 @@ defmodule IEx.Autocomplete do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_module(mod) do
|
||||
if is_list(mod) do
|
||||
Module.concat(mod)
|
||||
else
|
||||
mod
|
||||
end
|
||||
end
|
||||
|
||||
defp match_modules(hint, root) do
|
||||
get_modules(root)
|
||||
|> :lists.usort()
|
||||
@@ -269,6 +277,14 @@ defmodule IEx.Autocomplete do
|
||||
end
|
||||
end
|
||||
|
||||
defp match_map_fields(map, hint) do
|
||||
for {key, value} <- Map.to_list(map),
|
||||
is_atom(key),
|
||||
key = Atom.to_string(key),
|
||||
String.starts_with?(key, hint),
|
||||
do: %{kind: :map_key, name: key, value_is_map: is_map(value)}
|
||||
end
|
||||
|
||||
defp get_module_funs(mod) do
|
||||
docs = Code.get_docs(mod, :docs) || []
|
||||
module_info_funs(mod) |> Enum.reject(&hidden_fun?(&1, docs))
|
||||
@@ -316,6 +332,10 @@ defmodule IEx.Autocomplete do
|
||||
for a <- :lists.sort(arities), do: "#{name}/#{a}"
|
||||
end
|
||||
|
||||
defp to_entries(%{kind: :map_key, name: name}) do
|
||||
[name]
|
||||
end
|
||||
|
||||
defp to_uniq_entries(%{kind: :module}) do
|
||||
[]
|
||||
end
|
||||
@@ -324,6 +344,10 @@ defmodule IEx.Autocomplete do
|
||||
to_entries(fun)
|
||||
end
|
||||
|
||||
defp to_uniq_entries(%{kind: :map_key}) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp to_hint(%{kind: :module, name: name}, hint) when name == hint do
|
||||
format_hint(name, name) <> "."
|
||||
end
|
||||
@@ -336,8 +360,50 @@ defmodule IEx.Autocomplete do
|
||||
format_hint(name, hint)
|
||||
end
|
||||
|
||||
defp to_hint(%{kind: :map_key, name: name, value_is_map: true}, hint) when name == hint do
|
||||
format_hint(name, hint) <> "."
|
||||
end
|
||||
|
||||
defp to_hint(%{kind: :map_key, name: name}, hint) do
|
||||
format_hint(name, hint)
|
||||
end
|
||||
|
||||
defp format_hint(name, hint) do
|
||||
hint_size = byte_size(hint)
|
||||
:binary.part(name, hint_size, byte_size(name) - hint_size)
|
||||
end
|
||||
|
||||
defp aliases_from_env(server) do
|
||||
with evaluator when is_pid(evaluator) <- server.evaluator,
|
||||
{:ok, aliases} <- IEx.Evaluator.value_from_env(evaluator, :aliases) do
|
||||
aliases
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp value_from_binding(ast_node, server) do
|
||||
with evaluator when is_pid(evaluator) <- server.evaluator(),
|
||||
{var, map_key_path} <- extract_from_ast(ast_node, []) do
|
||||
IEx.Evaluator.value_from_binding(evaluator, var, map_key_path)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_ast(var_name, acc) when is_atom(var_name) do
|
||||
{var_name, acc}
|
||||
end
|
||||
|
||||
defp extract_from_ast({var_name, _, nil}, acc) when is_atom(var_name) do
|
||||
{var_name, acc}
|
||||
end
|
||||
|
||||
defp extract_from_ast({{:., _, [ast_node, fun]}, _, []}, acc) when is_atom(fun) do
|
||||
extract_from_ast(ast_node, [fun | acc])
|
||||
end
|
||||
|
||||
defp extract_from_ast(_ast_node, _acc) do
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,20 +24,64 @@ defmodule IEx.Evaluator do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a value out of the binding, using the provided
|
||||
variable name and map key path.
|
||||
"""
|
||||
@spec value_from_binding(pid, atom, [atom]) :: {:ok, any} | :error
|
||||
def value_from_binding(evaluator, var_name, map_key_path) do
|
||||
ref = make_ref()
|
||||
send evaluator, {:value_from_binding, ref, self(), var_name, map_key_path}
|
||||
|
||||
receive do
|
||||
{^ref, result} -> result
|
||||
after
|
||||
5000 -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current session environment if a session exists.
|
||||
"""
|
||||
@spec value_from_env(pid, atom) :: {:ok, term} | :error
|
||||
def value_from_env(evaluator, key) do
|
||||
ref = make_ref()
|
||||
send evaluator, {:value_from_env, ref, self(), key}
|
||||
|
||||
receive do
|
||||
{^ref, result} -> result
|
||||
after
|
||||
5000 -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp loop(server, history, state) do
|
||||
receive do
|
||||
{:eval, ^server, code, iex_state} ->
|
||||
{result, history, state} = eval(code, iex_state, history, state)
|
||||
send server, {:evaled, self(), result}
|
||||
loop(server, history, state)
|
||||
{:peek_env, receiver} ->
|
||||
send receiver, {:peek_env, state.env}
|
||||
{:value_from_env, ref, receiver, key} ->
|
||||
send receiver, {ref, Map.fetch(state.env, key)}
|
||||
loop(server, history, state)
|
||||
{:value_from_binding, ref, receiver, var_name, map_key_path} ->
|
||||
value = traverse_binding(state.binding, var_name, map_key_path)
|
||||
send receiver, {ref, value}
|
||||
loop(server, history, state)
|
||||
{:done, ^server} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp traverse_binding(binding, var_name, map_key_path) do
|
||||
accumulator = Keyword.fetch(binding, var_name)
|
||||
|
||||
Enum.reduce map_key_path, accumulator, fn
|
||||
key, {:ok, map} when is_map(map) -> Map.fetch(map, key)
|
||||
_key, _acc -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp loop_state(opts) do
|
||||
env =
|
||||
if env = opts[:env] do
|
||||
|
||||
+13
-16
@@ -43,19 +43,15 @@ defmodule IEx.Server do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current session environment if a session exists.
|
||||
Returns the PID of the IEx evaluator process if it exists.
|
||||
"""
|
||||
@spec current_env :: Macro.Env.t
|
||||
def current_env() do
|
||||
case IEx.Server.whereis() do
|
||||
nil -> %Macro.Env{}
|
||||
server ->
|
||||
send(server, {:peek_env, self()})
|
||||
receive do
|
||||
{:peek_env, %Macro.Env{} = env} -> env
|
||||
after
|
||||
5000 -> %Macro.Env{}
|
||||
end
|
||||
@spec evaluator :: pid | nil
|
||||
def evaluator() do
|
||||
case IEx.Server.local do
|
||||
nil -> nil
|
||||
pid ->
|
||||
{:dictionary, dictionary} = Process.info(pid, :dictionary)
|
||||
dictionary[:evaluator]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,7 +140,11 @@ defmodule IEx.Server do
|
||||
loop(run_state(opts), evaluator, Process.monitor(evaluator))
|
||||
end
|
||||
|
||||
defp start_evaluator(opts) do
|
||||
@doc """
|
||||
Starst an evaluator using the provided options.
|
||||
"""
|
||||
@spec start_evaluator(Keyword.t) :: pid
|
||||
def start_evaluator(opts) do
|
||||
self_pid = self()
|
||||
self_leader = Process.group_leader
|
||||
evaluator = opts[:evaluator] ||
|
||||
@@ -192,9 +192,6 @@ defmodule IEx.Server do
|
||||
exit_loop(evaluator, evaluator_ref)
|
||||
{:input, ^input, {:error, :terminated}} ->
|
||||
exit_loop(evaluator, evaluator_ref)
|
||||
{:peek_env, receiver} ->
|
||||
send evaluator, {:peek_env, receiver}
|
||||
wait_input(state, evaluator, evaluator_ref, input)
|
||||
msg ->
|
||||
handle_take_over(msg, evaluator, evaluator_ref, input, fn ->
|
||||
wait_input(state, evaluator, evaluator_ref, input)
|
||||
|
||||
@@ -11,7 +11,6 @@ defmodule IEx.Mixfile do
|
||||
[registered: [IEx.Supervisor, IEx.Config],
|
||||
mod: {IEx.App, []},
|
||||
env: [
|
||||
autocomplete_server: IEx.Server,
|
||||
colors: [],
|
||||
inspect: [pretty: true],
|
||||
history_size: 20,
|
||||
|
||||
@@ -3,8 +3,29 @@ Code.require_file "../test_helper.exs", __DIR__
|
||||
defmodule IEx.AutocompleteTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
setup context do
|
||||
previous_line = context[:previous_line]
|
||||
|
||||
if previous_line do
|
||||
ExUnit.CaptureIO.capture_io(fn ->
|
||||
evaluator = IEx.Server.start_evaluator([])
|
||||
Process.put(:evaluator, evaluator)
|
||||
send evaluator, {:eval, self(), previous_line <> "\n", %IEx.State{}}
|
||||
assert_receive {:evaled, _, _}
|
||||
end)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defmodule MyServer do
|
||||
def evaluator do
|
||||
Process.get(:evaluator)
|
||||
end
|
||||
end
|
||||
|
||||
def expand(expr) do
|
||||
IEx.Autocomplete.expand(Enum.reverse expr)
|
||||
IEx.Autocomplete.expand(Enum.reverse(expr), MyServer)
|
||||
end
|
||||
|
||||
test "Erlang module completion" do
|
||||
@@ -69,6 +90,7 @@ defmodule IEx.AutocompleteTest do
|
||||
assert expand('.') == {:no, '', []}
|
||||
assert expand('Xyz') == {:no, '', []}
|
||||
assert expand('x.Foo') == {:no, '', []}
|
||||
assert expand('x.Foo.get_by') == {:no, '', []}
|
||||
end
|
||||
|
||||
test "Elixir root submodule completion" do
|
||||
@@ -93,6 +115,62 @@ defmodule IEx.AutocompleteTest do
|
||||
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
|
||||
end
|
||||
|
||||
@tag previous_line: "mod = String"
|
||||
test "function completion using a variable bound to a module" do
|
||||
assert expand('mod.print') == {:yes, 'able?', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "map = %{foo: 1, bar_1: 23, bar_2: 14}"
|
||||
test "map atom key completion is supported" do
|
||||
assert expand('map.f') == {:yes, 'oo', []}
|
||||
assert expand('map.b') == {:yes, 'ar_', []}
|
||||
assert expand('map.bar_') == {:yes, '', ['bar_1', 'bar_2']}
|
||||
assert expand('map.c') == {:no, '', []}
|
||||
assert expand('map.') == {:yes, '', ['bar_1', 'bar_2', 'foo']}
|
||||
assert expand('map.foo') == {:no, '', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "map = %{nested: %{deeply: %{foo: 1, bar_1: 23, bar_2: 14, mod: String, num: 1}}}"
|
||||
test "nested map atom key completion is supported" do
|
||||
assert expand('map.nested.deeply.f') == {:yes, 'oo', []}
|
||||
assert expand('map.nested.deeply.b') == {:yes, 'ar_', []}
|
||||
assert expand('map.nested.deeply.bar_') == {:yes, '', ['bar_1', 'bar_2']}
|
||||
assert expand('map.nested.deeply.') == {:yes, '', ['bar_1', 'bar_2', 'foo', 'mod', 'num']}
|
||||
assert expand('map.nested.deeply.mod.print') == {:yes, 'able?', []}
|
||||
|
||||
assert expand('map.nested') == {:yes, '.', []}
|
||||
assert expand('map.nested.deeply') == {:yes, '.', []}
|
||||
assert expand('map.nested.deeply.foo') == {:no, '', []}
|
||||
|
||||
assert expand('map.nested.deeply.c') == {:no, '', []}
|
||||
assert expand('map.a.b.c.f') == {:no, '', []}
|
||||
end
|
||||
|
||||
@tag previous_line: ~s(map = %{"foo" => 1})
|
||||
test "map string key completion is not supported" do
|
||||
assert expand('map.f') == {:no, '', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "num = 5; map = %{nested: %{num: 23}}"
|
||||
test "autocompletion off a bound variable only works for modules and maps" do
|
||||
assert expand('num.print') == {:no, '', []}
|
||||
assert expand('map.nested.num.f') == {:no, '', []}
|
||||
assert expand('map.nested.num.key.f') == {:no, '', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "map = %{nested: %{deeply: %{num: 23}}}"
|
||||
test "autocompletion using access syntax does is not supported" do
|
||||
assert expand('map[:nested][:deeply].n') == {:no, '', []}
|
||||
assert expand('map[:nested].deeply.n') == {:no, '', []}
|
||||
assert expand('map.nested.[:deeply].n') == {:no, '', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "num = 5"
|
||||
test "autocompletion off of unbound variables is not supported" do
|
||||
assert expand('other_var.f') == {:no, '', []}
|
||||
assert expand('a.b.c.d') == {:no, '', []}
|
||||
end
|
||||
|
||||
test "macro completion" do
|
||||
{:yes, '', list} = expand('Kernel.is_')
|
||||
assert is_list(list)
|
||||
@@ -136,23 +214,15 @@ defmodule IEx.AutocompleteTest do
|
||||
assert expand('IEx.AutocompleteTest.SublevelTest.') == {:yes, 'LevelA', []}
|
||||
end
|
||||
|
||||
defmodule MyServer do
|
||||
def current_env do
|
||||
%Macro.Env{aliases: [{MyList, List}, {EList, :lists}]}
|
||||
end
|
||||
end
|
||||
|
||||
@tag previous_line: "alias List, as: MyList"
|
||||
test "complete aliases of Elixir modules" do
|
||||
Application.put_env(:iex, :autocomplete_server, MyServer)
|
||||
|
||||
assert expand('MyL') == {:yes, 'ist', []}
|
||||
assert expand('MyList') == {:yes, '.', []}
|
||||
assert expand('MyList.to_integer') == {:yes, [], ['to_integer/1', 'to_integer/2']}
|
||||
end
|
||||
|
||||
@tag previous_line: "alias :lists, as: EList"
|
||||
test "complete aliases of Erlang modules" do
|
||||
Application.put_env(:iex, :autocomplete_server, MyServer)
|
||||
|
||||
assert expand('EL') == {:yes, 'ist', []}
|
||||
assert expand('EList') == {:yes, '.', []}
|
||||
assert expand('EList.map') == {:yes, [], ['map/2', 'mapfoldl/3', 'mapfoldr/3']}
|
||||
@@ -160,21 +230,21 @@ defmodule IEx.AutocompleteTest do
|
||||
|
||||
test "completion for functions added when compiled module is reloaded" do
|
||||
{:module, _, bytecode, _} =
|
||||
defmodule Elixir.Sample do
|
||||
defmodule Sample do
|
||||
def foo(), do: 0
|
||||
end
|
||||
File.write!("Elixir.Sample.beam", bytecode)
|
||||
File.write!("Elixir.IEx.AutocompleteTest.Sample.beam", bytecode)
|
||||
assert Code.get_docs(Sample, :docs)
|
||||
assert expand('Sample.foo') == {:yes, '', ['foo/0']}
|
||||
assert expand('IEx.AutocompleteTest.Sample.foo') == {:yes, '', ['foo/0']}
|
||||
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
defmodule Elixir.Sample do
|
||||
defmodule Sample do
|
||||
def foo(), do: 0
|
||||
def foobar(), do: 0
|
||||
end
|
||||
assert expand('Sample.foo') == {:yes, '', ['foo/0', 'foobar/0']}
|
||||
assert expand('IEx.AutocompleteTest.Sample.foo') == {:yes, '', ['foo/0', 'foobar/0']}
|
||||
after
|
||||
File.rm("Elixir.Sample.beam")
|
||||
File.rm("Elixir.IEx.AutocompleteTest.Sample.beam")
|
||||
Code.compiler_options(ignore_module_conflict: false)
|
||||
:code.purge(Sample)
|
||||
:code.delete(Sample)
|
||||
@@ -184,7 +254,12 @@ defmodule IEx.AutocompleteTest do
|
||||
defstruct [:my_val]
|
||||
end
|
||||
|
||||
test "completion for structs" do
|
||||
test "completion for struct names" do
|
||||
assert expand('%IEx.AutocompleteTest.MyStr') == {:yes, 'uct', []}
|
||||
end
|
||||
|
||||
@tag previous_line: "struct = %IEx.AutocompleteTest.MyStruct{}"
|
||||
test "completion for struct keys" do
|
||||
assert expand('struct.my') == {:yes, '_val', []}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -150,6 +150,21 @@ defmodule Logger.TranslatorTest do
|
||||
"""s
|
||||
end
|
||||
|
||||
test "translates Task async_stream crashes with neighbour" do
|
||||
fun = fn -> Task.async_stream([:oops], :erlang, :error, []) |> Enum.to_list() end
|
||||
{:ok, pid} = Task.start(__MODULE__, :task, [self(), fun])
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, :go)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
end) =~ ~r"""
|
||||
Neighbours:
|
||||
#{inspect pid}
|
||||
Initial Call: Logger\.TranslatorTest\.task/2
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates Task undef module crash" do
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
|
||||
@@ -244,24 +259,6 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, :message)
|
||||
send(pid, :go)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
end) =~ ~r"""
|
||||
\[error\] Process #PID<\d+\.\d+\.\d+> terminating
|
||||
\*\* \(exit\) an exception was raised:
|
||||
\*\* \(RuntimeError\) oops
|
||||
.*
|
||||
Initial Call: Logger.TranslatorTest.task/1
|
||||
Ancestors: \[#PID<\d+\.\d+\.\d+>\]
|
||||
"""s
|
||||
end
|
||||
|
||||
test "translates Process crashes" do
|
||||
assert capture_log(:info, fn ->
|
||||
{_, ref} = spawn_monitor(fn() -> raise "oops" end)
|
||||
@@ -275,6 +272,24 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
|
||||
send(pid, :go)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
end) =~ ~r"""
|
||||
\[error\] Process #PID<\d+\.\d+\.\d+> terminating
|
||||
\*\* \(exit\) an exception was raised:
|
||||
\*\* \(RuntimeError\) oops
|
||||
.*
|
||||
Initial Call: Logger.TranslatorTest.task/1
|
||||
Ancestors: \[#PID<\d+\.\d+\.\d+>\]
|
||||
"""s
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes with name" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task,
|
||||
[self(), fn() ->
|
||||
@@ -648,5 +663,4 @@ defmodule Logger.TranslatorTest do
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
receive do: ({:EXIT, _, _} -> exit(:stop))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -36,6 +36,10 @@ defmodule Mix.Compilers.Test do
|
||||
Mix.shell.info "No stale tests."
|
||||
:noop
|
||||
|
||||
[] when test_patterns == [] ->
|
||||
Mix.shell.info "There are no tests to run"
|
||||
:noop
|
||||
|
||||
[] ->
|
||||
Mix.shell.error "Test patterns did not match any file: " <> Enum.join(test_patterns, ", ")
|
||||
:noop
|
||||
|
||||
@@ -264,11 +264,13 @@ defmodule Mix.Dep.Loader do
|
||||
{dep, deps}
|
||||
end
|
||||
|
||||
defp rebar_dep(%Mix.Dep{app: app, opts: opts} = dep, children, manager) do
|
||||
config = File.cd!(opts[:dest], fn ->
|
||||
Mix.Rebar.load_config(".")
|
||||
end)
|
||||
extra = Mix.Rebar.merge_config(dep.extra, config)
|
||||
defp rebar_dep(%Mix.Dep{app: app, opts: opts, extra: overrides} = dep, children, manager) do
|
||||
config =
|
||||
File.cd!(opts[:dest], fn -> Mix.Rebar.load_config(".") end)
|
||||
|
||||
config =
|
||||
Mix.Rebar.apply_overrides(app, config, overrides)
|
||||
|
||||
deps =
|
||||
if children do
|
||||
from = Path.join(opts[:dest], "rebar.config")
|
||||
@@ -277,9 +279,10 @@ defmodule Mix.Dep.Loader do
|
||||
# files in the dependency
|
||||
Enum.map(children, &to_dep(&1, from, manager))
|
||||
else
|
||||
rebar_children(app, config, extra, manager)
|
||||
rebar_children(config, manager, opts[:dest])
|
||||
end
|
||||
{%{dep | extra: extra}, deps}
|
||||
|
||||
{%{dep | extra: config}, deps}
|
||||
end
|
||||
|
||||
defp make_dep(dep) do
|
||||
@@ -301,17 +304,18 @@ defmodule Mix.Dep.Loader do
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
defp rebar_children(app, root_config, extra, manager) do
|
||||
from = Path.absname("rebar.config")
|
||||
defp rebar_children(root_config, manager, dest) do
|
||||
from = Path.absname(Path.join(dest, "rebar.config"))
|
||||
Mix.Rebar.recur(root_config, fn config ->
|
||||
app
|
||||
|> Mix.Rebar.deps(config, overrides(manager, extra))
|
||||
|> Enum.map(fn dep -> %{to_dep(dep, from, manager) | extra: extra} end)
|
||||
overrides = overrides(manager, config)
|
||||
config
|
||||
|> Mix.Rebar.deps()
|
||||
|> Enum.map(fn dep -> %{to_dep(dep, from, manager) | extra: overrides} end)
|
||||
end) |> Enum.concat
|
||||
end
|
||||
|
||||
defp overrides(:rebar3, extra), do: extra[:overrides] || []
|
||||
defp overrides(_, _extra), do: []
|
||||
defp overrides(:rebar3, config), do: config[:overrides] || []
|
||||
defp overrides(_, _config), do: []
|
||||
|
||||
defp validate_app(%Mix.Dep{opts: opts, requirement: req, app: app} = dep) do
|
||||
opts_app = opts[:app]
|
||||
|
||||
@@ -233,9 +233,10 @@ defmodule Mix.Project do
|
||||
@spec apps_paths() :: %{atom => Path.t} | nil
|
||||
def apps_paths(config \\ config()) do
|
||||
if apps_path = config[:apps_path] do
|
||||
Mix.ProjectStack.read_cache(:apps_path) ||
|
||||
Mix.ProjectStack.write_cache(:apps_path,
|
||||
config[:apps] |> umbrella_apps(apps_path) |> to_apps_path(apps_path))
|
||||
key = {:apps_paths, Mix.Project.get!}
|
||||
Mix.ProjectStack.read_cache(key) ||
|
||||
Mix.ProjectStack.write_cache(key,
|
||||
config[:apps] |> umbrella_apps(apps_path) |> to_apps_paths(apps_path))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -249,7 +250,7 @@ defmodule Mix.Project do
|
||||
apps
|
||||
end
|
||||
|
||||
defp to_apps_path(apps, apps_path) do
|
||||
defp to_apps_paths(apps, apps_path) do
|
||||
for app <- apps,
|
||||
path = path_with_mix_exs_otherwise_warn(app, apps_path),
|
||||
do: {app, path},
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule Mix.ProjectStack do
|
||||
get_and_update fn %{stack: stack} = state ->
|
||||
# Consider the first children to always have io_done
|
||||
# because we don't need to print anything unless another
|
||||
# project talks ahold of the shell.
|
||||
# project takes ahold of the shell.
|
||||
io_done? = stack == []
|
||||
|
||||
config = Keyword.merge(config, state.post_config)
|
||||
|
||||
+59
-146
@@ -61,108 +61,6 @@ defmodule Mix.Rebar do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges a rebar3 parent config with a child config.
|
||||
"""
|
||||
# From https://github.com/rebar/rebar3/blob/b1da2ec0674df89599564252734bd4d794436425/src/rebar_opts.erl#L103
|
||||
def merge_config(old, new) do
|
||||
Keyword.merge(old, new, fn
|
||||
:deps, old, _new -> old
|
||||
{:deps, _}, _old, new -> new
|
||||
:plugins, _old, new -> new
|
||||
{:plugins, _}, _old, new -> new
|
||||
:profiles, old, new -> merge_config(old, new)
|
||||
:mib_first_files, value, value -> value
|
||||
:mib_first_files, old, new -> old ++ new
|
||||
:relx, old, new -> tuple_merge(new, old)
|
||||
_key, old, new when is_list(new) ->
|
||||
case :io_lib.printable_list(new) do
|
||||
true when new == [] ->
|
||||
if :io_lib.printable_list(old), do: new, else: old
|
||||
true ->
|
||||
new
|
||||
false ->
|
||||
tuple_merge(old, new)
|
||||
end
|
||||
_key, _old, new -> new
|
||||
end)
|
||||
end
|
||||
|
||||
# From https://github.com/rebar/rebar3/blob/b1da2ec0674df89599564252734bd4d794436425/src/rebar_utils.erl#L282
|
||||
defp tuple_merge(old, new),
|
||||
do: do_tuple_merge(tuple_sort(old), tuple_sort(new))
|
||||
|
||||
defp do_tuple_merge(old, []),
|
||||
do: old
|
||||
defp do_tuple_merge(olds, [new | news]),
|
||||
do: do_tuple_umerge_dedup(umerge(:new, olds, [], news, new), [])
|
||||
|
||||
defp umerge(_, [], [], acc, current),
|
||||
do: [current | acc]
|
||||
defp umerge(:new, [], news, acc, current),
|
||||
do: Enum.reverse(news, [current | acc])
|
||||
defp umerge(:old, olds, [], acc, current),
|
||||
do: Enum.reverse(olds, [current | acc])
|
||||
defp umerge(:new, [old | olds], news, acc, current) do
|
||||
{dir, merged, new_current} = compare({:new, current}, {:old, old})
|
||||
umerge(dir, olds, news, [merged | acc], new_current)
|
||||
end
|
||||
defp umerge(:old, olds, [new | news], acc, current) do
|
||||
{dir, merged, new_current} = compare({:new, new}, {:old, current})
|
||||
umerge(dir, olds, news, [merged | acc], new_current)
|
||||
end
|
||||
|
||||
defp compare({priority, a}, {secondary, b}) when is_tuple(a) and is_tuple(b) do
|
||||
ka = elem(a, 0)
|
||||
kb = elem(b, 0)
|
||||
cond do
|
||||
ka == kb -> {secondary, a, b}
|
||||
ka < kb -> {secondary, a, b}
|
||||
ka > kb -> {priority, b, a}
|
||||
end
|
||||
end
|
||||
defp compare({priority, a}, {secondary, b}) when not is_tuple(a) and not is_tuple(b) do
|
||||
cond do
|
||||
a == b -> {secondary, a, b}
|
||||
a < b -> {secondary, a, b}
|
||||
a > b -> {priority, b, a}
|
||||
end
|
||||
end
|
||||
defp compare({priority, a}, {secondary, b}) when is_tuple(a) and not is_tuple(b) do
|
||||
ka = elem(a, 0)
|
||||
cond do
|
||||
ka == b -> {secondary, a, b}
|
||||
ka < b -> {secondary, a, b}
|
||||
ka > b -> {priority, b, a}
|
||||
end
|
||||
end
|
||||
defp compare({priority, a}, {secondary, b}) when not is_tuple(a) and is_tuple(b) do
|
||||
kb = elem(b, 0)
|
||||
cond do
|
||||
a == kb -> {secondary, a, b}
|
||||
a < kb -> {secondary, a, b}
|
||||
a > kb -> {priority, b, a}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_tuple_umerge_dedup([], acc), do: acc
|
||||
defp do_tuple_umerge_dedup([h | t], acc) do
|
||||
if h in t do
|
||||
do_tuple_umerge_dedup(t, acc)
|
||||
else
|
||||
do_tuple_umerge_dedup(t, [h | acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp tuple_sort(list) do
|
||||
Enum.sort(list, fn
|
||||
a, b when is_tuple(a) and is_tuple(b) -> elem(a, 0) <= elem(b, 0)
|
||||
a, b when is_tuple(a) -> elem(a, 0) <= b
|
||||
a, b when is_tuple(b) -> a <= elem(b, 0)
|
||||
a, b -> a <= b
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Serializes a Rebar config to a term file.
|
||||
"""
|
||||
@@ -178,7 +76,7 @@ defmodule Mix.Rebar do
|
||||
def dependency_config(config) do
|
||||
Enum.map(config, fn
|
||||
{:erl_opts, opts} ->
|
||||
{:erl_opts, Enum.reject(opts, &(&1 == :warnings_as_errors))}
|
||||
{:erl_opts, List.delete(opts, :warnings_as_errors)}
|
||||
other ->
|
||||
other
|
||||
end)
|
||||
@@ -187,10 +85,9 @@ defmodule Mix.Rebar do
|
||||
@doc """
|
||||
Parses the dependencies in given `rebar.config` to Mix's dependency format.
|
||||
"""
|
||||
def deps(app, config, overrides) do
|
||||
def deps(config) do
|
||||
# We don't have to handle rebar3 profiles because dependencies
|
||||
# are always in the default profile which cannot be customized
|
||||
config = apply_overrides(app, config, overrides)
|
||||
if deps = config[:deps] do
|
||||
Enum.map(deps, &parse_dep/1)
|
||||
else
|
||||
@@ -201,23 +98,23 @@ defmodule Mix.Rebar do
|
||||
@doc """
|
||||
Runs `fun` for the given config and for each `sub_dirs` in the
|
||||
given Rebar config.
|
||||
"""
|
||||
def recur(config, fun) when is_binary(config) do
|
||||
recur(load_config(config), fun)
|
||||
end
|
||||
|
||||
`sub_dirs` is only supported in Rebar 2. In Rebar 3, the equivalent
|
||||
to umbrella apps cannot be used as dependencies, so we don't need
|
||||
to worry about such cases in Mix.
|
||||
"""
|
||||
def recur(config, fun) do
|
||||
subs =
|
||||
(config[:sub_dirs] || [])
|
||||
|> Enum.map(&Path.wildcard(&1))
|
||||
|> Enum.concat
|
||||
|> Enum.flat_map(&Path.wildcard(&1))
|
||||
|> Enum.filter(&File.dir?(&1))
|
||||
|> Enum.map(&recur(&1, fun))
|
||||
|> Enum.concat
|
||||
|> Enum.flat_map(&recur(load_config(&1), fun))
|
||||
|
||||
[fun.(config) | subs]
|
||||
end
|
||||
|
||||
# Translate a rebar dependency declaration to a mix declaration
|
||||
# From http://www.rebar3.org/docs/dependencies#section-declaring-dependencies
|
||||
defp parse_dep(app) when is_atom(app) do
|
||||
{app, ">= 0.0.0"}
|
||||
end
|
||||
@@ -226,10 +123,6 @@ defmodule Mix.Rebar do
|
||||
{app, List.to_string(req)}
|
||||
end
|
||||
|
||||
defp parse_dep({app, req, {:pkg, package}}) when is_list(req) do
|
||||
{app, List.to_string(req), hex: package}
|
||||
end
|
||||
|
||||
defp parse_dep({app, source}) when is_tuple(source) do
|
||||
parse_dep({app, nil, source, []})
|
||||
end
|
||||
@@ -239,25 +132,33 @@ defmodule Mix.Rebar do
|
||||
end
|
||||
|
||||
defp parse_dep({app, req, source, opts}) do
|
||||
[scm, url | source] = Tuple.to_list(source)
|
||||
|
||||
ref =
|
||||
case source do
|
||||
["" | _] -> [branch: "HEAD"]
|
||||
[{:branch, branch} | _] -> [branch: to_string(branch)]
|
||||
[{:tag, tag} | _] -> [tag: to_string(tag)]
|
||||
[{:ref, ref} | _] -> [ref: to_string(ref)]
|
||||
[ref | _] -> [ref: to_string(ref)]
|
||||
_ -> []
|
||||
end
|
||||
source = parse_source(source)
|
||||
|
||||
compile =
|
||||
if :proplists.get_value(:raw, opts, false),
|
||||
do: [compile: false],
|
||||
else: []
|
||||
|
||||
mix_opts = [{scm, to_string(url)}] ++ ref ++ compile
|
||||
{app, compile_req(req), mix_opts}
|
||||
{app, compile_req(req), source ++ compile}
|
||||
end
|
||||
|
||||
defp parse_source({:pkg, pkg}) do
|
||||
[hex: pkg]
|
||||
end
|
||||
defp parse_source(source) do
|
||||
[scm, url | source] = Tuple.to_list(source)
|
||||
|
||||
ref =
|
||||
case source do
|
||||
["" | _] -> [branch: "HEAD"]
|
||||
[{:branch, branch} | _] -> [branch: to_string(branch)]
|
||||
[{:tag, tag} | _] -> [tag: to_string(tag)]
|
||||
[{:ref, ref} | _] -> [ref: to_string(ref)]
|
||||
[ref | _] -> [ref: to_string(ref)]
|
||||
_ -> []
|
||||
end
|
||||
|
||||
[{scm, to_string(url)}] ++ ref
|
||||
end
|
||||
|
||||
defp compile_req(nil) do
|
||||
@@ -265,11 +166,18 @@ defmodule Mix.Rebar do
|
||||
end
|
||||
|
||||
defp compile_req(req) do
|
||||
case Regex.compile(List.to_string(req)) do
|
||||
{:ok, re} ->
|
||||
re
|
||||
{:error, reason} ->
|
||||
Mix.raise "Unable to compile version regex: #{inspect req}, #{reason}"
|
||||
req = List.to_string(req)
|
||||
|
||||
case Version.parse_requirement(req) do
|
||||
{:ok, _} ->
|
||||
req
|
||||
:error ->
|
||||
case Regex.compile(req) do
|
||||
{:ok, re} ->
|
||||
re
|
||||
{:error, reason} ->
|
||||
Mix.raise "Unable to compile version regex: #{inspect req}, #{reason}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -312,9 +220,11 @@ defmodule Mix.Rebar do
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_overrides(app, config, overrides) do
|
||||
@doc """
|
||||
Applies the given overrides for app config.
|
||||
"""
|
||||
def apply_overrides(app, config, overrides) do
|
||||
# Inefficient. We want the order we get here though.
|
||||
|
||||
config =
|
||||
Enum.reduce(overrides, config, fn
|
||||
{:override, overrides}, config ->
|
||||
@@ -327,7 +237,7 @@ defmodule Mix.Rebar do
|
||||
|
||||
config =
|
||||
Enum.reduce(overrides, config, fn
|
||||
{:override, oapp, overrides}, config when oapp == app ->
|
||||
{:override, ^app, overrides}, config ->
|
||||
Enum.reduce(overrides, config, fn {key, value}, config ->
|
||||
Keyword.put(config, key, value)
|
||||
end)
|
||||
@@ -335,14 +245,17 @@ defmodule Mix.Rebar do
|
||||
config
|
||||
end)
|
||||
|
||||
Enum.reduce(overrides, config, fn
|
||||
{:add, oapp, overrides}, config when oapp == app ->
|
||||
Enum.reduce(overrides, config, fn {key, value}, config ->
|
||||
old_value = Keyword.get(config, key, [])
|
||||
Keyword.put(config, key, value ++ old_value)
|
||||
config =
|
||||
Enum.reduce(overrides, config, fn
|
||||
{:add, ^app, overrides}, config ->
|
||||
Enum.reduce(overrides, config, fn {key, value}, config ->
|
||||
old_value = Keyword.get(config, key, [])
|
||||
Keyword.put(config, key, value ++ old_value)
|
||||
end)
|
||||
_, config ->
|
||||
config
|
||||
end)
|
||||
_, config ->
|
||||
config
|
||||
end)
|
||||
|
||||
Keyword.update(config, :overrides, overrides, &(overrides ++ &1))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,14 +7,24 @@ defmodule Mix.Tasks.Archive.Build do
|
||||
Builds an archive according to the specification of the
|
||||
[Erlang Archive Format](http://www.erlang.org/doc/man/code.html).
|
||||
|
||||
Archives are meant to contain small projects, usually installed
|
||||
locally. Archives may be installed into a Mix environment by
|
||||
running `mix archive.install`. Once installed, the archive is
|
||||
available to all Mix projects. For this reason, the functionality
|
||||
behind archives is limited. For instance, archives do not include
|
||||
dependencies, as those would conflict with any dependency in a
|
||||
Mix project after the archive is installed. In general, we recommend
|
||||
the usage of archives to be limited for extensions of Mix, such
|
||||
as custom SCMs, package managers, etc. For general scripts to be
|
||||
installed into machines, please see `mix escript.build`.
|
||||
|
||||
The archive will be created in the current directory (which is
|
||||
expected to be the project root), unless an argument `-o` is
|
||||
provided with the file name.
|
||||
|
||||
Archives are meant to contain small projects, usually installed
|
||||
locally. By default, this command archives the current project
|
||||
but the `-i` and `-o` options can be used to archive any directory.
|
||||
For example, `mix archive.build` with no options translates to:
|
||||
By default, this command archives the current project but the `-i`
|
||||
option can be used to archive any directory. For example,
|
||||
`mix archive.build` with no options translates to:
|
||||
|
||||
mix archive.build -i _build/ENV/lib/APP -o APP-VERSION.ez
|
||||
|
||||
|
||||
@@ -6,14 +6,15 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
@moduledoc """
|
||||
Installs an archive locally.
|
||||
|
||||
If no argument is supplied but there is an archive in the project's root directory
|
||||
(created with `mix archive.build`), then the archive will be installed
|
||||
locally. For example:
|
||||
If no argument is supplied but there is an archive in the project's
|
||||
root directory (created with `mix archive.build`), then the archive
|
||||
will be installed locally. For example:
|
||||
|
||||
mix do archive.build, archive.install
|
||||
|
||||
If an argument is provided, it should be a local path or a URL to a prebuilt archive,
|
||||
a git repository, a github repository, or a hex package.
|
||||
If an argument is provided, it should be a local path or a URL to a
|
||||
prebuilt archive, a git repository, a github repository, or a hex
|
||||
package.
|
||||
|
||||
mix archive.install archive.ez
|
||||
mix archive.install path/to/archive.ez
|
||||
@@ -105,7 +106,7 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
|
||||
defp archives(name) do
|
||||
Mix.Local.path_for(:archive)
|
||||
|> Path.join(name)
|
||||
|> Path.join(name <> "{,*.ez}")
|
||||
|> Path.wildcard
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule Mix.Tasks.Compile.App do
|
||||
function in your `mix.exs` with the following options:
|
||||
|
||||
* `:applications` - all applications your application depends
|
||||
on at runtime. By default, this list is automatically inflected
|
||||
on at runtime. By default, this list is automatically inferred
|
||||
from your dependencies. Any extra Erlang/Elixir dependency
|
||||
must be specified in `:extra_applications`. Mix and other tools
|
||||
use the application list in order to start your dependencies
|
||||
|
||||
@@ -191,19 +191,29 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
Mix.raise "\"#{manager}\" installation failed"
|
||||
end
|
||||
|
||||
defp do_make(%{opts: opts} = dep, config) do
|
||||
os_type = :os.type
|
||||
defp do_make(dep, config) do
|
||||
command = make_command(dep)
|
||||
do_command(dep, config, command, true, [{"IS_DEP", "1"}])
|
||||
end
|
||||
|
||||
defp make_command(dep) do
|
||||
makefile_win? = makefile_win?(dep)
|
||||
|
||||
command =
|
||||
cond do
|
||||
match?({:win32, _}, os_type) and File.regular?(Path.join(opts[:dest], "Makefile.win")) ->
|
||||
case :os.type do
|
||||
{:win32, _} when makefile_win? ->
|
||||
"nmake /F Makefile.win"
|
||||
match?({:unix, type} when type in [:freebsd, :openbsd], os_type) ->
|
||||
{:unix, type} when type in [:freebsd, :openbsd] ->
|
||||
"gmake"
|
||||
true ->
|
||||
_ ->
|
||||
"make"
|
||||
end
|
||||
do_command(dep, config, command, true, [{"IS_DEP", "1"}])
|
||||
|
||||
if erlang_mk?(dep) do
|
||||
"#{command} clean && #{command}"
|
||||
else
|
||||
command
|
||||
end
|
||||
end
|
||||
|
||||
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
||||
@@ -248,4 +258,12 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
req
|
||||
end
|
||||
end
|
||||
|
||||
defp erlang_mk?(%Mix.Dep{opts: opts}) do
|
||||
File.regular?(Path.join(opts[:dest], "erlang.mk"))
|
||||
end
|
||||
|
||||
defp makefile_win?(%Mix.Dep{opts: opts}) do
|
||||
File.regular?(Path.join(opts[:dest], "Makefile.win"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,10 +197,13 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
|
||||
extra_apps =
|
||||
if function_exported?(mod, :application, 0) do
|
||||
mod.application[:applications]
|
||||
app = mod.application()
|
||||
Keyword.get(app, :applications, []) ++ Keyword.get(app, :extra_applications, [])
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
Enum.filter(extra_apps || [], &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
|
||||
Enum.filter(extra_apps, &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
|
||||
end
|
||||
|
||||
defp app_files(app) do
|
||||
|
||||
@@ -122,8 +122,8 @@ defmodule Mix.Tasks.Test do
|
||||
## Configuration
|
||||
|
||||
* `:test_paths` - list of paths containing test files, defaults to
|
||||
`["test"]`. It is expected all test paths to contain a `test_helper.exs`
|
||||
file.
|
||||
`["test"]` if the `test` directory exists, otherwise it defaults to `[]`.
|
||||
It is expected all test paths to contain a `test_helper.exs` file.
|
||||
|
||||
* `:test_pattern` - a pattern to load test files, defaults to `*_test.exs`.
|
||||
|
||||
@@ -225,7 +225,7 @@ defmodule Mix.Tasks.Test do
|
||||
ex_unit_opts = ex_unit_opts(opts)
|
||||
ExUnit.configure(ex_unit_opts)
|
||||
|
||||
test_paths = project[:test_paths] || ["test"]
|
||||
test_paths = project[:test_paths] || default_test_paths()
|
||||
Enum.each(test_paths, &require_test_helper(&1))
|
||||
ExUnit.configure(merge_helper_opts(ex_unit_opts))
|
||||
|
||||
@@ -365,4 +365,12 @@ defmodule Mix.Tasks.Test do
|
||||
Mix.raise "Cannot run tests because test helper file #{inspect file} does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
defp default_test_paths do
|
||||
if File.dir?("test") do
|
||||
["test"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,7 +138,11 @@ defmodule Mix.Utils do
|
||||
|
||||
def extract_files(paths, pattern) do
|
||||
Enum.flat_map(paths, fn path ->
|
||||
if File.regular?(path), do: [path], else: Path.wildcard("#{path}/**/#{pattern}")
|
||||
case :elixir_utils.read_file_type(path) do
|
||||
{:ok, :directory} -> Path.wildcard("#{path}/**/#{pattern}")
|
||||
{:ok, :regular} -> [path]
|
||||
_ -> []
|
||||
end
|
||||
end) |> Enum.uniq
|
||||
end
|
||||
|
||||
|
||||
+213
-194
@@ -33,212 +33,231 @@ defmodule Mix.RebarTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "load Rebar config" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep")
|
||||
config = Mix.Rebar.load_config(path)
|
||||
assert config[:sub_dirs] == ['apps/*']
|
||||
assert config[:SCRIPT] == 'rebar.config.script'
|
||||
end
|
||||
|
||||
test "execute rebar.config.script on dependency directory" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep_script")
|
||||
config = Mix.Rebar.load_config(path)
|
||||
assert config[:dir] == {:ok, String.to_charlist(path)}
|
||||
end
|
||||
|
||||
test "parse Rebar dependencies" do
|
||||
config = [deps: [{:git_rebar, '~> 1.0'}]]
|
||||
assert [{:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 1.0', {:pkg, :rebar_fork}}]]
|
||||
assert [{:git_rebar, "~> 1.0", hex: :rebar_fork}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: [{:git_rebar, '0.1..*', {:git, '../../test/fixtures/git_rebar', :master}}]]
|
||||
assert [{:git_rebar, ~r"0.1..*", [git: "../../test/fixtures/git_rebar", ref: "master"]}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: [{:git_rebar, {:git, '../../test/fixtures/git_rebar', :master}}]]
|
||||
assert [{:git_rebar, ">= 0.0.0", [git: "../../test/fixtures/git_rebar", ref: "master"]}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: [{:git_rebar, '0.1..*', {:git, '../../test/fixtures/git_rebar'}, [:raw]}]]
|
||||
assert [{:git_rebar, ~r"0.1..*", [git: "../../test/fixtures/git_rebar", compile: false]}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: [{:git_rebar, '', {:git, '../../test/fixtures/git_rebar', {:ref, '64691eb'}}}]]
|
||||
assert [{:git_rebar, ~r"", [git: "../../test/fixtures/git_rebar", ref: "64691eb"]}] ==
|
||||
Mix.Rebar.deps(:foo, config, [])
|
||||
|
||||
config = [deps: {:git_rebar, '~> 2.0'}]
|
||||
overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert [{:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, overrides)
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 2.0'}]]
|
||||
overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert [{:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, overrides)
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 2.0'}]]
|
||||
overrides = [{:override, :bar, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert [{:git_rebar, "~> 2.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, overrides)
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 2.0'}]]
|
||||
overrides = [{:override, :foo, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert [{:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, overrides)
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 1.0'}]]
|
||||
overrides = [{:add, :foo, [deps: [{:git_rebar2, '~> 2.0'}]]}]
|
||||
assert [{:git_rebar2, "~> 2.0"}, {:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(:foo, config, overrides)
|
||||
|
||||
end
|
||||
|
||||
test "convert rebar config to dependency config" do
|
||||
config = Mix.Rebar.load_config(fixture_path("rebar_dep"))
|
||||
dep_config = Mix.Rebar.dependency_config(config)
|
||||
|
||||
assert config[:erl_opts] == [:warnings_as_errors]
|
||||
assert dep_config[:erl_opts] == []
|
||||
end
|
||||
|
||||
test "parse Rebar dependencies from rebar.config" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
deps = Mix.Dep.loaded([])
|
||||
assert Enum.find(deps, &(&1.app == :rebar_dep))
|
||||
|
||||
assert Enum.find(deps, fn %Mix.Dep{app: app, opts: opts} ->
|
||||
if app == :git_rebar do
|
||||
assert Enum.find(opts, &match?({:git, _}, &1))
|
||||
assert Enum.find(opts, &match?({:ref, "master"}, &1))
|
||||
true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "inherit Rebar manager" do
|
||||
Mix.Project.push(Rebar3AsDep)
|
||||
|
||||
deps = Mix.Dep.loaded([])
|
||||
assert Enum.all?(deps, &(&1.manager == :rebar3))
|
||||
end
|
||||
|
||||
test "Rebar overrides" do
|
||||
Mix.Project.push(RebarOverrideAsDep)
|
||||
|
||||
in_tmp "Rebar overrides", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert Mix.Dep.loaded([]) |> Enum.map(& &1.app) ==
|
||||
[:git_repo, :git_rebar, :rebar_override]
|
||||
describe "load_config/1" do
|
||||
test "loads rebar.config" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep")
|
||||
config = Mix.Rebar.load_config(path)
|
||||
assert config[:sub_dirs] == ['apps/*']
|
||||
assert config[:SCRIPT] == 'rebar.config.script'
|
||||
end
|
||||
|
||||
test "loads rebar.config.script on dependency directory" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep_script")
|
||||
config = Mix.Rebar.load_config(path)
|
||||
assert config[:dir] == {:ok, String.to_charlist(path)}
|
||||
end
|
||||
after
|
||||
purge [GitRepo.Mixfile]
|
||||
end
|
||||
|
||||
test "recurs over sub dirs" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep")
|
||||
describe "deps/1" do
|
||||
test "parses Rebar dependencies" do
|
||||
config = [deps: [{:git_rebar, '~> 1.0'}]]
|
||||
assert [{:git_rebar, "~> 1.0"}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
File.cd! path, fn ->
|
||||
config = Mix.Rebar.load_config(path)
|
||||
config = [deps: [{:git_rebar, '~> 1.0', {:pkg, :rebar_fork}}]]
|
||||
assert [{:git_rebar, "~> 1.0", hex: :rebar_fork}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
Mix.Rebar.recur(config, fn config ->
|
||||
if config[:sub_dirs] == ['from_apps_another'] do
|
||||
Process.put(:inside_apps_another, true)
|
||||
config = [deps: [{:git_rebar, {:pkg, :rebar_fork}}]]
|
||||
assert [{:git_rebar, ">= 0.0.0", hex: :rebar_fork}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
config = [deps: [{:git_rebar, '0.1..*', {:git, '../../test/fixtures/git_rebar', :master}}]]
|
||||
assert [{:git_rebar, ~r"0.1..*", [git: "../../test/fixtures/git_rebar", ref: "master"]}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
config = [deps: [{:git_rebar, {:git, '../../test/fixtures/git_rebar', :master}}]]
|
||||
assert [{:git_rebar, ">= 0.0.0", [git: "../../test/fixtures/git_rebar", ref: "master"]}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
config = [deps: [{:git_rebar, '0.1..*', {:git, '../../test/fixtures/git_rebar'}, [:raw]}]]
|
||||
assert [{:git_rebar, ~r"0.1..*", [git: "../../test/fixtures/git_rebar", compile: false]}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
|
||||
config = [deps: [{:git_rebar, '', {:git, '../../test/fixtures/git_rebar', {:ref, '64691eb'}}}]]
|
||||
assert [{:git_rebar, ~r"", [git: "../../test/fixtures/git_rebar", ref: "64691eb"]}] ==
|
||||
Mix.Rebar.deps(config)
|
||||
end
|
||||
end
|
||||
|
||||
describe "apply_overrides/3" do
|
||||
test "applies overrides" do
|
||||
config = [deps: {:git_rebar, '~> 2.0'}]
|
||||
overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
|
||||
[deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 2.0'}]]
|
||||
overrides = [{:override, :bar, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
|
||||
[deps: [{:git_rebar, '~> 2.0'}], overrides: overrides]
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 2.0'}]]
|
||||
overrides = [{:override, :foo, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
|
||||
[deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]
|
||||
|
||||
config = [deps: [{:git_rebar, '~> 1.0'}]]
|
||||
overrides = [{:add, :foo, [deps: [{:git_rebar2, '~> 2.0'}]]}]
|
||||
assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
|
||||
[deps: [{:git_rebar2, '~> 2.0'}, {:git_rebar, '~> 1.0'}], overrides: overrides]
|
||||
end
|
||||
|
||||
test "concatenates overrides" do
|
||||
config = [deps: {:git_rebar, '~> 2.0'}, overrides: [{:add, :bar, []}]]
|
||||
overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]
|
||||
assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
|
||||
[deps: [{:git_rebar, '~> 1.0'}], overrides: overrides ++ [{:add, :bar, []}]]
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependency_config/1" do
|
||||
test "converts Rebar config to dependency config" do
|
||||
config = Mix.Rebar.load_config(fixture_path("rebar_dep"))
|
||||
dep_config = Mix.Rebar.dependency_config(config)
|
||||
|
||||
assert config[:erl_opts] == [:warnings_as_errors]
|
||||
assert dep_config[:erl_opts] == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "recur/1" do
|
||||
test "recurs over sub dirs" do
|
||||
path = MixTest.Case.fixture_path("rebar_dep")
|
||||
|
||||
File.cd! path, fn ->
|
||||
config = Mix.Rebar.load_config(path)
|
||||
|
||||
Mix.Rebar.recur(config, fn config ->
|
||||
if config[:sub_dirs] == ['from_apps_another'] do
|
||||
Process.put(:inside_apps_another, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
unless Process.get(:inside_apps_another) do
|
||||
flunk "Expected inside_apps_another to return true"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration with Mix" do
|
||||
test "inherits Rebar manager" do
|
||||
Mix.Project.push(Rebar3AsDep)
|
||||
deps = Mix.Dep.loaded([])
|
||||
assert Enum.all?(deps, &(&1.manager == :rebar3))
|
||||
end
|
||||
|
||||
|
||||
test "parses Rebar dependencies from rebar.config" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
deps = Mix.Dep.loaded([])
|
||||
assert Enum.find(deps, &(&1.app == :rebar_dep))
|
||||
|
||||
assert Enum.find(deps, fn %Mix.Dep{app: app, opts: opts} ->
|
||||
if app == :git_rebar do
|
||||
assert Enum.find(opts, &match?({:git, _}, &1))
|
||||
assert Enum.find(opts, &match?({:ref, "master"}, &1))
|
||||
true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
unless Process.get(:inside_apps_another) do
|
||||
flunk "Expected inside_apps_another to return true"
|
||||
end
|
||||
end
|
||||
test "handles Rebar overrides" do
|
||||
Mix.Project.push(RebarOverrideAsDep)
|
||||
|
||||
test "get and compile dependencies for Rebar" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for Rebar", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert_received {:mix_shell, :info, ["* Getting git_rebar" <> _]}
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
|
||||
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
|
||||
assert :git_rebar.any_function == :ok
|
||||
assert :rebar_dep.any_function == :ok
|
||||
|
||||
load_paths = Mix.Dep.loaded([])
|
||||
|> Enum.map(&Mix.Dep.load_paths(&1))
|
||||
|> Enum.concat
|
||||
|
||||
assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
|
||||
assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")
|
||||
|
||||
# Assert we have no .compile.lock as a .compile.lock
|
||||
# means we check for the Elixir version on every command.
|
||||
refute File.exists?("_build/dev/lib/rebar_dep/.compile.lock")
|
||||
refute File.exists?("_build/dev/lib/git_rebar/.compile.lock")
|
||||
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
|
||||
end
|
||||
end
|
||||
|
||||
test "get and compile dependencies for rebar3" do
|
||||
Mix.Project.push(Rebar3AsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for rebar3", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert_received {:mix_shell, :info, ["* Getting git_rebar " <> _]}
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
|
||||
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
|
||||
assert :git_rebar.any_function == :ok
|
||||
assert :rebar_dep.any_function == :ok
|
||||
|
||||
load_paths = Mix.Dep.loaded([])
|
||||
|> Enum.map(&Mix.Dep.load_paths(&1))
|
||||
|> Enum.concat
|
||||
|
||||
assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
|
||||
assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")
|
||||
|
||||
# Assert we have no .compile.lock as a .compile.lock
|
||||
# means we check for the Elixir version on every command.
|
||||
refute File.exists?("_build/dev/lib/rebar_dep/.compile.lock")
|
||||
refute File.exists?("_build/dev/lib/git_rebar/.compile.lock")
|
||||
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
|
||||
end
|
||||
end
|
||||
|
||||
test "get and compile dependencies for Rebar with Mix" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for Rebar with Mix", fn ->
|
||||
File.write! MixTest.Case.tmp_path("rebar_dep/mix.exs"), """
|
||||
defmodule RebarDep.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :rebar_dep,
|
||||
version: "0.0.1"]
|
||||
end
|
||||
in_tmp "Rebar overrides", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert Mix.Dep.loaded([]) |> Enum.map(& &1.app) ==
|
||||
[:git_repo, :git_rebar, :rebar_override]
|
||||
end
|
||||
"""
|
||||
after
|
||||
purge [GitRepo.Mixfile]
|
||||
end
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :info, ["==> rebar_dep"]}
|
||||
assert_received {:mix_shell, :info, ["Generated rebar_dep app"]}
|
||||
assert File.regular?("_build/dev/lib/rebar_dep/ebin/rebar_dep.app")
|
||||
test "gets and compiles dependencies for Rebar" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for Rebar", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert_received {:mix_shell, :info, ["* Getting git_rebar" <> _]}
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
|
||||
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
|
||||
assert :git_rebar.any_function == :ok
|
||||
assert :rebar_dep.any_function == :ok
|
||||
|
||||
load_paths = Mix.Dep.loaded([])
|
||||
|> Enum.map(&Mix.Dep.load_paths(&1))
|
||||
|> Enum.concat
|
||||
|
||||
assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
|
||||
assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")
|
||||
|
||||
# Assert we have no .compile.lock as a .compile.lock
|
||||
# means we check for the Elixir version on every command.
|
||||
refute File.exists?("_build/dev/lib/rebar_dep/.compile.lock")
|
||||
refute File.exists?("_build/dev/lib/git_rebar/.compile.lock")
|
||||
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
|
||||
end
|
||||
end
|
||||
|
||||
test "gets and compiles dependencies for rebar3" do
|
||||
Mix.Project.push(Rebar3AsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for rebar3", fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert_received {:mix_shell, :info, ["* Getting git_rebar " <> _]}
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
|
||||
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
|
||||
assert :git_rebar.any_function == :ok
|
||||
assert :rebar_dep.any_function == :ok
|
||||
|
||||
load_paths = Mix.Dep.loaded([])
|
||||
|> Enum.map(&Mix.Dep.load_paths(&1))
|
||||
|> Enum.concat
|
||||
|
||||
assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
|
||||
assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")
|
||||
|
||||
# Assert we have no .compile.lock as a .compile.lock
|
||||
# means we check for the Elixir version on every command.
|
||||
refute File.exists?("_build/dev/lib/rebar_dep/.compile.lock")
|
||||
refute File.exists?("_build/dev/lib/git_rebar/.compile.lock")
|
||||
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
|
||||
assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
|
||||
end
|
||||
end
|
||||
|
||||
test "gets and compiles dependencies for Rebar with Mix" do
|
||||
Mix.Project.push(RebarAsDep)
|
||||
|
||||
in_tmp "get and compile dependencies for Rebar with Mix", fn ->
|
||||
File.write! MixTest.Case.tmp_path("rebar_dep/mix.exs"), """
|
||||
defmodule RebarDep.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :rebar_dep,
|
||||
version: "0.0.1"]
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
assert_received {:mix_shell, :info, ["==> rebar_dep"]}
|
||||
assert_received {:mix_shell, :info, ["Generated rebar_dep app"]}
|
||||
assert File.regular?("_build/dev/lib/rebar_dep/ebin/rebar_dep.app")
|
||||
end
|
||||
after
|
||||
File.rm MixTest.Case.tmp_path("rebar_dep/mix.exs")
|
||||
end
|
||||
after
|
||||
File.rm MixTest.Case.tmp_path("rebar_dep/mix.exs")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,7 +85,7 @@ defmodule Mix.Tasks.Compile.AppTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "automatically inflects applications" do
|
||||
test "automatically infers applications" do
|
||||
Mix.Project.push CustomDeps
|
||||
|
||||
in_fixture "no_mixfile", fn ->
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
end
|
||||
|
||||
def application do
|
||||
[applications: []]
|
||||
[applications: [], extra_applications: [:crypto]]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -108,6 +108,14 @@ defmodule Mix.Tasks.TestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "logs test absence for a project with no test paths" do
|
||||
in_fixture "test_stale", fn ->
|
||||
File.rm_rf! "test"
|
||||
|
||||
assert_run_output "There are no tests to run"
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: runs tests after input" do
|
||||
in_fixture "test_stale", fn ->
|
||||
port = mix_port(~w[test --stale --listen-on-stdin])
|
||||
@@ -190,6 +198,10 @@ defmodule Mix.Tasks.TestTest do
|
||||
end
|
||||
|
||||
defp assert_stale_run_output(opts \\ [], expected) do
|
||||
assert mix(~w[test --stale] ++ opts) =~ expected
|
||||
assert_run_output(["--stale" | opts], expected)
|
||||
end
|
||||
|
||||
defp assert_run_output(opts \\ [], expected) do
|
||||
assert mix(["test" | opts]) =~ expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,6 +33,11 @@ defmodule Mix.UtilsTest do
|
||||
assert Path.basename(hd(files)) == "local.sample.ex"
|
||||
end
|
||||
|
||||
|
||||
test "extract files with empty string returns empty list" do
|
||||
assert Mix.Utils.extract_files([""], ".ex") == []
|
||||
end
|
||||
|
||||
test "extract stale" do
|
||||
time = {{2030, 1, 1}, {0, 0, 0}}
|
||||
assert Mix.Utils.extract_stale([__ENV__.file], [time]) == []
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.4.0-rc.0"},
|
||||
{vsn, "1.4.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user