Compare commits

...
57 Commits
Author SHA1 Message Date
José Valim b2b974dc71 Release v1.4.3 2017-05-15 14:38:10 +02:00
José Valim 7c0f6dd121 Show overriding information when using path with query string 2017-05-15 14:31:28 +02:00
José Valim f8d775cb68 Warn when overriding __struct__ key 2017-05-15 14:18:09 +02:00
José Valim da10a7d2dd Do not log exits on IEx.Helpers.c failures
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-05-15 13:40:40 +02:00
whatyouhide d4756f26da Polish the "Writing Documentation" page in the documentation
[ci skip]
2017-05-14 23:17:05 +02:00
Andrea Leopardi b9e2197ae3 Refactor Version.parse/1 to not use regexes (#6107) 2017-05-14 15:16:12 +02:00
Andrea Leopardi 16ea29d518 Properly escape fields passed to defrecord (#6086) 2017-05-09 21:40:33 +02:00
José Valim 16a90cedda Update CHANGELOG 2017-05-08 21:06:34 +02:00
Andrea Leopardi 812b1cdc16 Remove use of Regex in the Version.Parser module (#6077)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-05-08 21:00:30 +02:00
José Valim c6f7958ae8 Recompile projects if OTP version changes, closes #6066 2017-05-07 22:02:20 +02:00
José Valim 68c5c31f81 Remove unnecessary regexes in IO.ANSI.Docs 2017-05-07 18:38:36 +02:00
José Valim dd79a3eda7 Remove more regexes from stdlib and logger
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-05-07 17:08:30 +02:00
James Fish aca50d4d86 Handle OTP 20 GenServer log messages (#6071)
Signed-off-by: James Fish <james@fishcakez.com>
2017-05-07 15:05:52 +01:00
José Valim e33f3002ad Remove regexes from ExUnit, IEx and Mix 2017-05-07 15:04:39 +02:00
José Valim 9074a99199 Add check for OTP 20 2017-05-07 11:21:03 +02:00
José Valim c7729df924 Add Regex.recompile/1 and Regex.recompile\!/1 2017-05-07 11:19:22 +02:00
José Valim 61bb6c147f Avoid race conditions on capture_err reuse 2017-05-07 10:48:34 +02:00
José Valim f27df203ab Fix warnings on OTP 20 2017-05-07 10:27:55 +02:00
José Valim f252c9e00e Do not set file attributes to generated, closes #6015 2017-05-04 14:44:06 +02:00
Aleksei Magusev dad832d5a3 Fix quoting of bitstring literals 2017-04-20 16:41:32 +02:00
Marcel Otto 094b64c13b Fix MatchError in ExUnit when comparing maps with nil or boolean keys (#5957)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-04-02 09:23:32 +09:00
José Valim 1ca103d048 No need to reverse lists on MapSet
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-04-02 09:23:26 +09:00
Eksperimental f3cbf1cdec Mention alternatives to deprecated functions in CHANGELOG v1.4 (#5805) 2017-02-22 22:42:07 +01:00
Eksperimental ee4f3d55eb Update CHANGELOG.md (#5804)
Add backticks to `Access.key/2`
2017-02-22 22:17:48 +01:00
José Valim 3d756ac5ad Do not assume all load path exist
A path can be added to the load path and
then removed from the filesystem.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-02-20 17:37:32 +01:00
José Valim e6ed1d58d7 Use proper table type for keeping clauses 2017-02-19 20:38:39 +01:00
kiennt 176501cb62 Fix a bug in URI.merge/2 (#5780)
It happens when reference does not have scheme, but has authority part.
2017-02-19 12:10:40 +01:00
José Valim be4b7cd78d Fix typo on changelog 2017-02-16 15:55:16 +01:00
José Valim 27fdc68ff4 Release v1.4.2 2017-02-16 15:15:31 +01:00
José Valim 2d4722a12f Allow consuming multiple items from suspended enumerable in Stream.transform/3
Closes #5763.
Closes #5772.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-02-16 13:50:16 +01:00
José Valim e002ac55b2 Incorporate new grapheme rules in Unicode 9
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-02-13 15:01:34 +01:00
José Valim fb89d5548d Support middle expressions on trim mode, closes #5752
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-02-09 20:41:50 +01:00
José Valim 8618d8effa Properly cache apps_paths configuration, closes #5622
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-02-05 11:06:37 +01:00
Eric Meadows-Jönsson bd1c3ab8d7 Delete previous .ez archives
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2017-02-01 23:19:33 +01:00
José Valim 2110342661 Improve docs for OptionParser
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-31 11:06:01 +01:00
Gal Tsubery 8533df25a9 Fix redefined function source location (#5720)
When functions are redefined in a different file, the translation uses
the first definition source location for all subsequent definitions.
This manifests itself as warnings that are ascribed to the wrong file.

Fixes #5719

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-31 11:05:55 +01:00
José Valim 9e8ac51fb3 Only expand aliases known at compile time, closes #5721
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-30 21:21:47 +01:00
Andrew Dryga 451b8eb9a3 Implement allow_inexistent_atoms for OptionParser (#5709)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-28 22:45:59 +01:00
Aleksei Magusev 23742f1237 Correct datetime types spelling in Calendar 2017-01-26 13:34:20 +01:00
José Valim 3f9ffbeb82 Also mention new versions file 2017-01-26 13:24:15 +01:00
José Valim 0397a46bb7 Release v1.4.1 2017-01-26 13:02:51 +01:00
Eric Meadows-Jönsson b87e58e7e3 Run make clean for erlang.mk (#5698)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-26 12:59:35 +01:00
José Valim 6cb09a77e6 Update CHANGELOG 2017-01-24 14:13:11 +01:00
José Valim 12edab720c Remove warning when making private functions overridable 2017-01-23 17:01:49 +01:00
José Valim 74f9a34015 Only pass overrides from parent to child rebar dep (#5687)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-23 15:52:00 +01:00
Eric Meadows-Jönsson 49af97a800 Support rebar3 dependency package declaration (#5678)
Currently only {app, “~> 1.0.0”, {pkg, hex_package}} is supported. This
change will also support {app, {pkg, hex_package}}.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-23 12:44:08 +01:00
José Valim b806ee8559 override will likely be impl 2017-01-20 23:02:54 +01:00
James Fish 7d09c2b857 Wait for message before crash in exception tests
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-17 16:41:08 -07:00
José Valim f38646781b Do not warn on unused override attribute 2017-01-17 16:17:32 -07:00
José Valim b5777de582 appended -> prepended, closes #5656
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-14 11:11:45 +01:00
Andrea Leopardi 67f77ec1cd Fix Path.join/1 for lists of one element (#5639)
Also fix the spec for Path.join/1 to mention that the input list of paths has to
be non-empty, and fix the documentation to mention that this function takes a
list of paths (Path.t), not strings like it said before.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-10 11:02:40 +01:00
Saša Jurić 8a91f648a8 Fix typespecs in Calendar (#5635)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-09 20:23:16 +01:00
Devon Estes 7f61b0ff05 Add - to Regex.escape/1 (#5626)
We were not escaping the `-` character, commonly found in character
classes, in cases where it is being used on its own. I've added a test
for this new escape behavior, and also implemented the change.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-08 18:16:06 +01:00
José Valim c830fde31e Improve task docs, closes #5618 2017-01-05 11:02:53 +01:00
Matt Widman 019e6b85e6 Correct documentation referencing System.schedulers_online/1 to System.schedulers_online/0 (#5568)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-05 10:56:17 +01:00
José Valim 097fe87eb7 Disable ansi detection for powershell to avoid false positives
Closes #5615
2017-01-05 10:49:00 +01:00
Wojtek Mach 6ec1e7f7b4 Add new functions Time.utc_now/0, NaiveDateTime.utc_now/0 to CHANGELOG (#5617) 2017-01-05 10:42:23 +01:00
71 changed files with 1390 additions and 862 deletions
+90 -3
View File
@@ -109,6 +109,92 @@ You can now also install archives from Hex in this way. Since they are fetched a
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.3 (2017-05-15)
This version includes changes that make Elixir fully compatible with Erlang OTP 20-rc.1.
### 1. Enhancements
#### Elixir
* [Kernel] Improve compilation time for modules with many clauses
* [Map] Warn when attempting to override `__struct__` key
* [Regex] Add `recompile/1` and `recompile!/1` to ease transition to OTP 20 for archives and stored regexes
#### Logger
* [Logger.Translator] Handle OTP 20 GenServer log messages
#### Mix
* [mix compile] Recompile projects if OTP version changes
### 2. Bug fixes
#### Elixir
* [Kernel] Fix code generation when non-binary bitstrings are in AST
* [Record] Properly escape fields passed to `defrecord`
#### ExUnit
* [ExUnit.Diff] Do not fail when comparing maps with nil or boolean keys
#### IEx
* [IEx.Helpers] Do not log exits on `IEx.Helpers.c/2` failures
#### Mix
* [mix archive.install] Detect proper path on URLs with query strings
* [mix loadpaths] Do not assume all paths in loadpaths exist
## 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_nonexistent_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
@@ -119,6 +205,7 @@ It is also possible to install escripts and archives by providing a Git/GitHub r
* [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
@@ -223,10 +310,10 @@ It is also possible to install escripts and archives by providing a Git/GitHub r
#### Elixir
* [Access] `Access.key/1` is deprecated due to erratic behaviour for missing keys, please use Access.key/2 instead with proper default values
 * [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)
* [Float] `Float.to_char_list/2` and `Float.to_string/2` are deprecated (use the `:erlang.float_to_list/2` and `:erlang.float_to_binary/2` 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`
@@ -239,7 +326,7 @@ It is also possible to install escripts and archives by providing a Git/GitHub r
#### Mix
* [Mix.Utils] `underscore/1` and `camelize/1` are deprecated
* [Mix.Utils] `underscore/1` and `camelize/1` are deprecated in favor of `Macro.underscore/1` and `Macro.camelize/1`
## v1.3
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -1 +1 @@
1.4.0
1.4.3
-21
View File
@@ -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%
+3 -1
View File
@@ -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
-1
View File
@@ -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
+13
View File
@@ -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)
+7 -7
View File
@@ -51,19 +51,19 @@ 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 how many days there are in the given year-month.
+30 -18
View File
@@ -2,6 +2,7 @@ defmodule IO.ANSI.Docs do
@moduledoc false
@bullets [?*, ?-, ?+]
@spaces [" ", "\n", "\t"]
@doc """
The default options used by this module.
@@ -204,7 +205,7 @@ defmodule IO.ANSI.Docs do
|> Enum.join(" ")
|> handle_links
|> handle_inline(options)
|> String.split(~r{\s})
|> String.split(@spaces)
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
unless no_wrap, do: newline_after_block()
@@ -266,8 +267,10 @@ defmodule IO.ANSI.Docs do
count = Enum.map(lines, &length/1) |> Enum.max
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
widths = for line <- lines, do:
(for {_col, length} <- line, do: length)
widths =
for line <- lines do
for {_col, length} <- line, do: length
end
col_widths = Enum.reduce(widths,
List.duplicate(0, count),
@@ -280,15 +283,16 @@ defmodule IO.ANSI.Docs do
line
|> String.trim("|")
|> String.trim()
|> String.split(~r/\s\|\s/)
|> String.split(" | ")
|> Enum.map(&render_column(&1, options))
end
defp render_column(col, options) do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(options)
col =
col
|> String.replace("\\\|", "|")
|> handle_links
|> handle_inline(options)
{col, length_without_escape(col, 0)}
end
@@ -319,8 +323,17 @@ defmodule IO.ANSI.Docs do
defp render_table([], _, _),
do: nil
defp table_header?(row), do:
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
defp table_header?(row) do
Enum.all?(row, fn {col, _} -> table_header_column?(col) end)
end
defp table_header_column?(":" <> row), do: table_header_contents?(row)
defp table_header_column?(row), do: table_header_contents?(row)
defp table_header_contents?("-" <> row), do: table_header_contents?(row)
defp table_header_contents?(":"), do: true
defp table_header_contents?(""), do: true
defp table_header_contents?(_), do: false
defp draw_table_row(cols_and_widths, options, heading \\ false) do
columns =
@@ -336,11 +349,7 @@ defmodule IO.ANSI.Docs do
end
defp table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x, line)
line =~ " | "
end
## Helpers
@@ -416,14 +425,17 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
Regex.replace(~r{https?\S*}, text, &String.replace(&1, "_", "\\_"))
~r{https?\S*}
|> Regex.recompile!
|> Regex.replace(text, &String.replace(&1, "_", "\\_"))
end
defp remove_square_brackets_in_link(text) do
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
~r{\[(.*?)\]\((.*?)\)}
|> Regex.recompile!
|> Regex.replace(text, "\\1 (\\2)")
end
# We have four entries: **, *, _ and `.
#
# The first three behave the same while the last one is simpler
+2 -6
View File
@@ -93,18 +93,14 @@ defmodule MapSet do
end
defp do_new([], acc) do
acc
|> :lists.reverse
|> :maps.from_list
:maps.from_list(acc)
end
defp do_new([item | rest], acc) do
do_new(rest, [{item, true} | acc])
end
defp do_new_transform([], _fun, acc) do
acc
|> :lists.reverse
|> :maps.from_list
:maps.from_list(acc)
end
defp do_new_transform([item | rest], fun, acc) do
do_new_transform(rest, fun, [{fun.(item), true} | acc])
-7
View File
@@ -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
[]
+99 -54
View File
@@ -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]
+5 -5
View File
@@ -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.
+1
View File
@@ -316,6 +316,7 @@ defmodule Record do
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
fields = Macro.escape(fields)
case Macro.expand(args, caller) do
{:{}, _, [^atom | list]} when length(list) == length(fields) ->
record = List.to_tuple([atom | list])
+98 -23
View File
@@ -1,14 +1,13 @@
defmodule Regex do
@moduledoc ~S"""
Provides regular expressions for Elixir. Built on top of Erlang's `:re`
module.
Provides regular expressions for Elixir.
As the `:re` module, Regex is based on PCRE
(Perl Compatible Regular Expressions). More information can be
found in the [`:re` module documentation](http://www.erlang.org/doc/man/re.html).
Regex is based on PCRE (Perl Compatible Regular Expressions) and
built on top of Erlang's `:re` module. More information can be found
in the [`:re` module documentation](http://www.erlang.org/doc/man/re.html).
Regular expressions in Elixir can be created using `Regex.compile!/2`
or using the special form with [`~r`](Kernel.html#sigil_r/2) or [`~R`](Kernel.html#sigil_R/2):
Regular expressions in Elixir can be created using the sigils
[`~r`](Kernel.html#sigil_r/2) or [`~R`](Kernel.html#sigil_R/2):
# A simple regular expressions that matches foo anywhere in the string
~r/foo/
@@ -16,9 +15,38 @@ defmodule Regex do
# A regular expression with case insensitive and Unicode options
~r/foo/iu
Regular expressions created via sigils are pre-compiled and stored
in the `.beam` file. Notice this may be a problem if you are precompiling
Elixir, see the "Precompilation" section for more information.
A Regex is represented internally as the `Regex` struct. Therefore,
`%Regex{}` can be used whenever there is a need to match on them.
Keep in mind it is not guaranteed two regular expressions from the
same source are equal, for example:
~r/(?<foo>.)(?<bar>.)/ == ~r/(?<foo>.)(?<bar>.)/
may return `true` or `false` depending on your machine, endianess,
available optimizations and others. You can, however, retrieve the source
of a compiled regular expression by accessing the `source` field, and then
compare those directly:
~r/(?<foo>.)(?<bar>.)/.source == ~r/(?<foo>.)(?<bar>.)/.source
## Precompilation
Regular expressions built with sigil are precompiled and stored in `.beam`
files. This may be a problem if you are precompiling Elixir to run in
different OTP releases, as OTP releases may update the underlying regular
expression engine at any time.
For such reasons, we always recomend precompiling Elixir projects using
the OTP version meant to run in production. In case cross-compilation is
really necessary, you can manually invoke `Regex.recompile/1` or `Regex.
recompile!/1` to perform a runtime version check and recompile the regex
if necessary.
## Modifiers
The modifiers available when creating a Regex are:
@@ -78,7 +106,7 @@ defmodule Regex do
"""
defstruct re_pattern: nil, source: "", opts: ""
defstruct re_pattern: nil, source: "", opts: "", re_version: ""
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
@@ -91,7 +119,7 @@ defmodule Regex do
The given options can either be a binary with the characters
representing the same regex options given to the `~r` sigil,
or a list of options, as expected by the Erlang's [`:re` module](http://www.erlang.org/doc/man/re.html).
or a list of options, as expected by the Erlang's `:re` module.
It returns `{:ok, regex}` in case of success,
`{:error, reason}` otherwise.
@@ -106,40 +134,84 @@ defmodule Regex do
"""
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, any}
def compile(source, options \\ "")
def compile(source, options \\ "") do
compile(source, options, version())
end
def compile(source, options) when is_binary(options) do
defp compile(source, options, version) when is_binary(options) do
case translate_options(options, []) do
{:error, rest} ->
{:error, {:invalid_option, rest}}
translated_options ->
compile(source, translated_options, options)
compile(source, translated_options, options, version)
end
end
def compile(source, options) when is_list(options) do
compile(source, options, "")
defp compile(source, options, version) when is_list(options) do
compile(source, options, "", version)
end
defp compile(source, opts, doc_opts) when is_binary(source) do
defp compile(source, opts, doc_opts, version) when is_binary(source) do
case :re.compile(source, opts) do
{:ok, re_pattern} ->
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: doc_opts}}
{:ok, %Regex{re_pattern: re_pattern, re_version: version, source: source, opts: doc_opts}}
error ->
error
end
end
@doc """
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
Compiles the regular expression and raises `Regex.CompileError` in case of errors.
"""
@spec compile!(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
{:error, {reason, at}} -> raise Regex.CompileError, message: "#{reason} at position #{at}"
{:error, {reason, at}} -> raise Regex.CompileError, "#{reason} at position #{at}"
end
end
@doc """
Recompiles the existing regular expression if necessary.
This checks the version stored in the regular expression
and recompiles the regex in case of version mismatch.
"""
@spec recompile(t) :: t
def recompile(%Regex{} = regex) do
version = version()
# We use Map.get/3 by choice to support old regexes versions.
case Map.get(regex, :re_version, :error) do
^version ->
{:ok, regex}
_ ->
%{source: source, opts: opts} = regex
compile(source, opts, version)
end
end
@doc """
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
"""
@spec recompile!(t) :: t
def recompile!(regex) do
case recompile(regex) do
{:ok, regex} -> regex
{:error, {reason, at}} -> raise Regex.CompileError, "#{reason} at position #{at}"
end
end
@doc """
Returns the version of the underlying Regex engine.
"""
# TODO: No longer check for function_exported? on OTP 20+.
def version do
if function_exported?(:re, :version, 0) do
:re.version()
else
"8.33 2013-05-29"
end
end
@@ -629,9 +701,6 @@ defmodule Regex do
[get_index(string, h) | get_indexes(string, t, arity - 1)]
end
{:ok, pattern} = :re.compile(~S"[.^$*+?()\[\]{}\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc ~S"""
Escapes a string to be literally matched in a regex.
@@ -646,9 +715,15 @@ defmodule Regex do
"""
@spec escape(String.t) :: String.t
def escape(string) when is_binary(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, {:return, :binary}])
escape(string, [])
end
for char <- '.^$*+?()[]{}|#-\\\t\n\v\f\r\s' do
defp escape(<<unquote(char), rest::binary>>, acc), do: escape(rest, [acc, ?\\, unquote(char)])
end
defp escape(<<char, rest::binary>>, acc), do: escape(rest, [acc, char])
defp escape(<<>>, acc), do: IO.iodata_to_binary(acc)
# Helpers
@doc false
+19 -17
View File
@@ -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
+12 -12
View File
@@ -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,7 +285,7 @@ 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
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
@@ -297,7 +296,7 @@ defmodule Task 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.
@@ -309,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
+3 -2
View File
@@ -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
+7 -3
View File
@@ -400,8 +400,8 @@ defmodule URI do
def parse(%URI{} = uri), do: uri
def parse(string) when is_binary(string) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
# From https://tools.ietf.org/html/rfc3986#appendix-B
regex = Regex.recompile!(~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i)
parts = nillify(Regex.run(regex, string))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -419,7 +419,8 @@ defmodule URI do
# Split an authority into its userinfo, host and port parts.
defp split_authority(string) do
components = Regex.run(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, string || "")
regex = Regex.recompile!(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/)
components = Regex.run(regex, string || "")
destructure [_, _, userinfo, host, _, port], nillify(components)
host = if host, do: host |> String.trim_leading("[") |> String.trim_trailing("]")
@@ -473,6 +474,9 @@ defmodule URI do
def merge(_base, %URI{scheme: rel_scheme} = rel) when rel_scheme != nil do
rel
end
def merge(base, %URI{authority: authority} = rel) when authority != nil do
%{rel | scheme: base.scheme}
end
def merge(%URI{} = base, %URI{path: rel_path} = rel) when rel_path in ["", nil] do
%{base | query: rel.query || base.query, fragment: rel.fragment}
end
+70 -54
View File
@@ -238,9 +238,10 @@ defmodule Version do
@spec parse(String.t) :: {:ok, t} | :error
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{:ok, {major, minor, patch, pre}} ->
{:ok, {major, minor, patch, pre, build_parts}} ->
build = if build_parts == [], do: nil, else: Enum.join(build_parts, "")
version = %Version{major: major, minor: minor, patch: patch,
pre: pre, build: get_build(string)}
pre: pre, build: build}
{:ok, version}
:error ->
:error
@@ -312,23 +313,13 @@ defmodule Version do
defp to_matchable(string, allow_pre?) do
case Version.Parser.parse_version(string) do
{:ok, {major, minor, patch, pre}} ->
{:ok, {major, minor, patch, pre, _build_parts}} ->
{major, minor, patch, pre, allow_pre?}
:error ->
raise InvalidVersionError, message: string
end
end
defp get_build(string) do
case Regex.run(~r/\+([^\s]+)$/, string) do
nil ->
nil
[_, build] ->
build
end
end
defmodule Parser.DSL do
@moduledoc false
@@ -396,67 +387,92 @@ defmodule Version do
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex ~r/^
(\d+) # major
(?:\.(\d+))? # minor
(?:\.(\d+))? # patch
(?:\-([\d\w\.\-]+))? # pre
(?:\+([\d\w\.\-]+))? # build
$/x
@spec parse_requirement(String.t) :: {:ok, term} | :error
def parse_requirement(source) do
lexed = lexer(source, [])
to_matchspec(lexed)
end
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
def parse_version(string, approximate? \\ false) when is_binary(string) do
if parsed = Regex.run(@version_regex, string) do
destructure [_, major, minor, patch, pre], parsed
patch = nillify(patch)
pre = nillify(pre)
destructure [version_with_pre, build], String.split(string, "+", parts: 2)
destructure [version, pre], String.split(version_with_pre, "-", parts: 2)
destructure [major, minor, patch], String.split(version, ".")
if is_nil(minor) or (is_nil(patch) and not approximate?) do
:error
else
major = String.to_integer(major)
minor = String.to_integer(minor)
patch = patch && String.to_integer(patch)
with {:ok, major} <- require_digits(major),
{:ok, minor} <- require_digits(minor),
{:ok, patch} <- maybe_patch(patch, approximate?),
{:ok, pre_parts} <- optional_dot_separated(pre),
{:ok, pre_parts} <- convert_parts_to_integer(pre_parts, []),
{:ok, build_parts} <- optional_dot_separated(build) do
{:ok, {major, minor, patch, pre_parts, build_parts}}
else
_other -> :error
end
end
case parse_pre(pre) do
{:ok, pre} ->
{:ok, {major, minor, patch, pre}}
:error ->
:error
end
end
defp require_digits(nil), do: :error
defp require_digits(string) do
if leading_zero?(string), do: :error, else: parse_digits(string, "")
end
defp leading_zero?(<<?0, _, _::binary>>), do: true
defp leading_zero?(_), do: false
defp parse_digits(<<char, rest::binary>>, acc) when char in ?0..?9,
do: parse_digits(rest, <<acc::binary, char>>)
defp parse_digits(<<>>, acc) when byte_size(acc) > 0,
do: {:ok, String.to_integer(acc)}
defp parse_digits(_, _acc),
do: :error
defp maybe_patch(patch, approximate?)
defp maybe_patch(nil, true), do: {:ok, nil}
defp maybe_patch(patch, _), do: require_digits(patch)
defp optional_dot_separated(nil), do: {:ok, []}
defp optional_dot_separated(string) do
parts = String.split(string, ".")
if Enum.all?(parts, &(&1 != "" and valid_identifier?(&1))) do
{:ok, parts}
else
:error
end
end
defp parse_pre(nil), do: {:ok, []}
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
defp parse_pre([piece | t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [String.to_integer(piece) | acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
parse_pre(t, [piece | acc])
defp convert_parts_to_integer([part | rest], acc) do
case parse_digits(part, "") do
{:ok, integer} ->
if leading_zero?(part) do
:error
else
convert_parts_to_integer(rest, [integer | acc])
end
:error ->
convert_parts_to_integer(rest, [part | acc])
end
end
defp parse_pre([], acc) do
defp convert_parts_to_integer([], acc) do
{:ok, Enum.reverse(acc)}
end
defp valid_identifier?(<<char, rest::binary>>)
when char in ?0..?9
when char in ?a..?z
when char in ?A..?Z
when char == ?- do
valid_identifier?(rest)
end
defp valid_identifier?(<<>>) do
true
end
defp valid_identifier?(_other) do
false
end
defp valid_requirement?([]), do: false
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
@@ -574,7 +590,7 @@ defmodule Version do
defp parse_condition(version, approximate? \\ false) do
case parse_version(version, approximate?) do
{:ok, version} -> version
{:ok, {major, minor, patch, pre, _build}} -> {major, minor, patch, pre}
:error -> throw :invalid_matchspec
end
end
+14 -14
View File
@@ -4,10 +4,10 @@ Elixir treats documentation as a first-class citizen. This means documentation s
## Markdown
Elixir documentation is written using Markdown. There are plenty of guides on Markdown online, we recommend the ones available at GitHub as a getting started point:
Elixir documentation is written using Markdown. There are plenty of guides on Markdown online, we recommend the ones available on GitHub as a getting started point:
* https://help.github.com/articles/markdown-basics/
* https://help.github.com/articles/github-flavored-markdown/
* [https://help.github.com/articles/markdown-basics/](https://help.github.com/articles/markdown-basics/)
* [https://help.github.com/articles/markdown-basics/](https://help.github.com/articles/github-flavored-markdown/)
## Module Attributes
@@ -34,7 +34,7 @@ Documentation in Elixir is usually attached to module attributes. Let's see an e
end
end
The `@moduledoc` attribute is used to add documentation to the module. `@doc` is used before a function to provide documentation for it. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs.
The `@moduledoc` attribute is used to add documentation to the module. `@doc` is used before a function to provide documentation for the function that follows. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs.
## Function Arguments
@@ -55,11 +55,11 @@ The compiler will infer this argument as `map`. Sometimes the inference will be
When writing documentation:
* Keep the first paragraph of the documentation concise and simple, typically one-line. Tools like [ExDoc](https://github.com/elixir-lang/ex_doc/) use the first line to generate a summary.
* Keep the first paragraph of the documentation concise and simple, typically one line. Tools like [ExDoc](https://github.com/elixir-lang/ex_doc/) use the first line to generate a summary.
* Reference modules by their full name.
Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when module or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``.
Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when module or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``.
* Reference functions by name and arity if they are local, as in `` `world/1` ``, or by module, name and arity if pointing to an external module: `` `MyApp.Hello.world/1` ``.
@@ -73,21 +73,21 @@ When writing documentation:
## Doctests
We recommend that developers include examples in their documentation, often under their own `## Examples` heading. To ensure examples do not get out of date, Elixir's test framework (ExUnit) provides a feature called doctests that allows developers to test the examples in their documentation. Doctests work by parsing out code samples starting with `iex>` from the documentation. You can read more about it at `ExUnit.DocTest`.
We recommend that developers include examples in their documentation, often under their own `## Examples` heading. To ensure examples do not get out of date, Elixir's test framework (ExUnit) provides a feature called doctests that allows developers to test the examples in their documentation. Doctests work by parsing out code samples starting with `iex>` from the documentation. You can read more about it in the documentation for `ExUnit.DocTest`.
Notice doctests have limitations. When you cannot doctest a function, because it relies on state or side-effects, we recommend developers include examples directly without the `iex>` prompt.
Note that doctests have limitations. When you cannot doctest a function, because it relies on state or side-effects, we recommend developers include examples directly without the `iex>` prompt.
## Documentation != Comments
## Documentation is not Comments
Elixir treats documentation and code comments as different concepts. Documentation is for users of your Application Programming Interface (API), be it your co-worker or your future self. Modules and functions must always be documented if they are part of your API.
Code comments are for developers reading the code. They are useful to mark improvements, leave notes for developers reading the code (for example, you decided not to call a function due to a bug in a library) and so forth.
Code comments are for developers reading the code. They are useful to mark improvements, leave notes for developers reading the code (for example, you decided not to call a function due to a bug in a library), and so on.
In other words: documentation is required, code comments are optional.
## Hiding Internal Modules and Functions
Besides the modules and functions libraries provide as part of their public interface, libraries may also implement important functionality that is not part of their API. While these modules and functions can be accessed, they are meant to be internal to the library and thus should not have documentation for end users.
Besides the modules and functions that libraries provide as part of their public interface, libraries may also implement important functionality that is not part of their API. While these modules and functions can be accessed, they are meant to be internal to the library and thus should not have documentation for end users.
Luckily, Elixir allows developers to hide modules and functions from the documentation. For example, one common practice for documenting internal behaviour is to set the `@moduledoc` attribute to `false` while documenting each function:
@@ -102,7 +102,7 @@ Luckily, Elixir allows developers to hide modules and functions from the documen
end
end
Similarly, developers can add `@doc false` to functions they do not want to be publicly exposed:
Similarly, developers can add `@doc false` to functions they do not want to show up in the documentation:
defmodule MyApp.Sample do
@doc false
@@ -113,7 +113,7 @@ However, keep in mind that adding `@doc false` does not make the function privat
* Move the undocumented function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. Remember you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they suggest to anyone reading the code that they're meant to be used privately.
## Documenting Private Functions
@@ -121,6 +121,6 @@ Elixir warns if a private function has a `@doc` attribute and discards its conte
Private functions may still need internal documentation for maintainers, though. That can be accomplished with code comments.
## Code.get_docs/2
## Retrieving Documentation
Elixir stores documentation inside pre-defined chunks in the bytecode. It can be accessed from Elixir by using the `Code.get_docs/2` function. This also means documentation is only accessed when required and not when modules are loaded by the Virtual Machine. The only downside is that modules defined in-memory, like the ones defined in IEx, cannot have their documentation accessed as they do not have their bytecode written to disk.
+5
View File
@@ -47,6 +47,11 @@ start(_Type, _Args) ->
erlang:halt(1)
end,
%% We need to make sure the re module is preloaded
%% to make function_exported checks on it fast.
%% TODO: Remove this once we support OTP 20+.
_ = code:ensure_loaded(re),
case code:ensure_loaded(?system) of
{module, ?system} ->
Endianness = ?system:endianness(),
+6 -6
View File
@@ -271,11 +271,11 @@ export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
function_for_stored_definition(Ann, {Name, Arity}, Clauses) ->
{function, Ann, Name, Arity, Clauses}.
add_definition(_Line, nil, Body, {Head, Tail}) ->
add_definition(_Ann, nil, Body, {Head, Tail}) ->
{[Body | Head], Tail};
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Line, file, Location}, Body | Tail]}.
add_definition(Ann, Location, Body, {Head, Tail}) ->
FileAnn = erl_anno:set_generated(false, Ann),
{Head, [{attribute, FileAnn, file, Location}, Body | Tail]}.
default_function_for(Kind, Name, {clause, Ann, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop ->
@@ -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,
+5
View File
@@ -140,6 +140,11 @@ handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
%% Properly format keys using inspect.
handle_file_warning(_, File, {Line, v3_core, {map_key_repeated, Key}}) ->
Message = io_lib:format("key ~ts will be overridden in map", ['Elixir.Kernel':inspect(Key)]),
warn(Line, File, Message);
%% Make no_effect clauses pretty
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
{Fmt, Args} = case erl_internal:comp_op(F, A) of
+21 -14
View File
@@ -12,8 +12,19 @@ expand_map(Meta, Args, E) ->
validate_kv(Meta, EArgs, Args, E),
{{'%{}', Meta, EArgs}, EA}.
expand_struct(Meta, Left, Right, #{context := Context} = E) ->
{[ELeft, ERight], EE} = elixir_exp:expand_args([Left, Right], E),
expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, #{context := Context} = E) ->
CleanArgs =
case lists:keytake('__struct__', 1, MapArgs) of
{value, _, ValueArgs} ->
elixir_errors:warn(?line(Meta), ?m(E, file),
"key :__struct__ is ignored when building structs"),
ValueArgs;
false ->
MapArgs
end,
{[ELeft | EArgs], EE} = elixir_exp:expand_args([Left | CleanArgs], E),
ERight = {'%{}', MapMeta, EArgs},
case validate_struct(ELeft, Context) of
true when is_atom(ELeft) ->
@@ -38,14 +49,10 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
false -> Meta
end,
case ERight of
{'%{}', _, _} -> ok;
_ -> compile_error(Meta, ?m(E, file),
"expected struct to be followed by a map, got: ~ts",
['Elixir.Macro':to_string(ERight)])
end,
{{'%', EMeta, [ELeft, ERight]}, EE}.
{{'%', EMeta, [ELeft, ERight]}, EE};
expand_struct(Meta, _Left, Right, E) ->
compile_error(Meta, ?m(E, file), "expected struct to be followed by a map, got: ~ts",
['Elixir.Macro':to_string(Right)]).
validate_struct({'^', _, [{Var, _, Ctx}]}, match) when is_atom(Var), is_atom(Ctx) -> true;
validate_struct({Var, _Meta, Ctx}, match) when is_atom(Var), is_atom(Ctx) -> true;
@@ -66,7 +73,7 @@ translate_map(Meta, Args, S) ->
translate_map(Meta, Assocs, TUpdate, US).
translate_struct(_Meta, Name, {'%{}', MapMeta, Assocs}, S) when is_tuple(Name) ->
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, S);
translate_map(MapMeta, [{'__struct__', Name} | Assocs], nil, S);
translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
{Assocs, TUpdate, US} = extract_assoc_update(Args, S),
@@ -98,11 +105,11 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
{clause, Generated, [Var], [], [elixir_utils:erl_call(Ann, erlang, error, [Error])]}
]}, TS};
match ->
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
translate_map(MapMeta, [{'__struct__', Name} | Assocs], nil, US);
expand ->
Keys = [K || {K, _} <- Assocs],
Keys = ['__struct__'] ++ [K || {K, _} <- Assocs],
{StructAssocs, _} = elixir_quote:escape(maps:to_list(maps:without(Keys, Struct)), false),
translate_map(MapMeta, StructAssocs ++ Assocs ++ [{'__struct__', Name}], nil, US)
translate_map(MapMeta, [{'__struct__', Name}] ++ StructAssocs ++ Assocs, nil, US)
end.
%% Helpers
+2 -2
View File
@@ -141,7 +141,7 @@ build(Line, File, Module, Docs, Lexical) ->
end,
Data = ets:new(Module, [set, public]),
Defs = ets:new(Module, [bag, public]),
Defs = ets:new(Module, [duplicate_bag, public]),
Ref = elixir_code_server:call({defmodule, self(),
{Module, Data, Defs, Line, File}}),
@@ -426,7 +426,7 @@ check_module_availability(Line, File, Module) ->
end.
warn_unused_attributes(File, Data, PersistedAttrs) ->
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition | 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, Line] <- Keys, not lists:member(Key, ReservedAttrs)].
+1 -1
View File
@@ -238,7 +238,7 @@ do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitStri
{BitString, Q};
Size ->
<<Bits:Size, Bytes/binary>> = BitString,
{{'<<>>', [], [{'::', [], [Bits, Size]}, Bytes]}, Q}
{{'<<>>', [], [{'::', [], [Bits, {size, [], [Size]}]}, {'::', [], [Bytes, {binary, [], []}]}]}, Q}
end;
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
+17 -32
View File
@@ -263,34 +263,17 @@ defmodule ExceptionTest do
assert Exception.format_exit(reason) |> String.starts_with?(expected_to_start_with)
end
test "format_exit with call with exception" do
# 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
formatted = Exception.format_exit(reason)
assert formatted =~ ~r"exited in: :gen_fsm\.sync_send_event\(#PID<\d+\.\d+\.\d+>, :hello\)"
assert formatted =~ ~r"\s{4}\*\* \(EXIT\) an exception was raised:\n"
assert formatted =~ ~r"\s{8}\*\* \(ArgumentError\) argument error\n"
assert formatted =~ ~r"\s{12}:not_a_real_module\.function/0"
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 +285,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
@@ -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\""
@@ -348,6 +373,19 @@ defmodule Kernel.WarningTest do
purge Sample
end
# TODO: Remove this check once we depend only on 19
if :erlang.system_info(:otp_release) >= '20' do
test "duplicate map keys" do
assert capture_err(fn ->
defmodule DuplicateKeys do
assert %{a: :b, a: :c} == %{a: :c}
assert %{1 => 2, 1 => 3} == %{1 => 3}
assert %{:a => :b, a: :c} == %{a: :c}
end
end) =~ "key :a will be overridden in map"
end
end
test "unused guard" do
assert capture_err(fn ->
Code.eval_string """
+1 -1
View File
@@ -43,7 +43,7 @@ defmodule MacroTest do
end
test "escape handles bitstring" do
assert {:<<>>, [], [{:::, [], [1, 4]}, ","]} == Macro.escape(<<300::12>>)
assert {:<<>>, [], [{:::, [], [1, {:size, [], [4]}]}, {:::, [], [",", {:binary, [], []}]}]} == Macro.escape(<<300::12>>)
end
test "escape works recursively" do
-6
View File
@@ -86,12 +86,6 @@ defmodule MapTest do
assert %{1 => 2, a: :b,} == %{1 => 2, a: :b}
end
test "maps with duplicate keys" do
assert %{a: :b, a: :c} == %{a: :c}
assert %{1 => 2, 1 => 3} == %{1 => 3}
assert %{:a => :b, a: :c} == %{a: :c}
end
test "update maps" do
assert %{@sample | a: 3} == %{a: 3, b: 2}
@@ -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"], []}
+2
View File
@@ -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
+30 -1
View File
@@ -136,7 +136,7 @@ defmodule RecordTest do
binary: <<1, 2, 3>>,
charlist: 'abc'
test "records with literal defaults" do
test "records with literal defaults and on-the-fly record" do
assert defaults(defaults()) == [
struct: ~D[2016-01-01],
map: %{},
@@ -163,6 +163,35 @@ defmodule RecordTest do
assert defaults(defaults(), :charlist) == 'abc'
end
test "records with literal defaults and record in a variable" do
defaults = defaults()
assert defaults(defaults) == [
struct: ~D[2016-01-01],
map: %{},
tuple_zero: {},
tuple_one: {1},
tuple_two: {1, 2},
tuple_three: {1, 2, 3},
list: [1, 2, 3],
call: MapSet.new,
string: "abc",
binary: <<1, 2, 3>>,
charlist: 'abc'
]
assert defaults(defaults, :struct) == ~D[2016-01-01]
assert defaults(defaults, :map) == %{}
assert defaults(defaults, :tuple_zero) == {}
assert defaults(defaults, :tuple_one) == {1}
assert defaults(defaults, :tuple_two) == {1, 2}
assert defaults(defaults, :tuple_three) == {1, 2, 3}
assert defaults(defaults, :list) == [1, 2, 3]
assert defaults(defaults, :call) == MapSet.new
assert defaults(defaults, :string) == "abc"
assert defaults(defaults, :binary) == <<1, 2, 3>>
assert defaults(defaults, :charlist) == 'abc'
end
test "records with dynamic arguments" do
record = file_info()
assert file_info(record, :size) == :undefined
+50 -7
View File
@@ -91,7 +91,55 @@ defmodule RegexTest do
refute <<?<, 255, ?>>> =~ ~r/<.>/u
end
test "names" do
test "ungreedy" do
assert Regex.run(~r/[\d ]+/, "1 2 3 4 5"), ["1 2 3 4 5"]
assert Regex.run(~r/[\d ]?+/, "1 2 3 4 5"), ["1"]
assert Regex.run(~r/[\d ]+/U, "1 2 3 4 5"), ["1"]
end
test "regex?/1" do
assert Regex.regex?(~r/foo/)
refute Regex.regex?(0)
end
test "compile/1" do
{:ok, regex} = Regex.compile("foo")
assert Regex.regex?(regex)
assert {:error, _} = Regex.compile("*foo")
assert {:error, _} = Regex.compile("foo", "y")
assert {:error, _} = Regex.compile("foo", "uy")
end
test "compile/1 with Erlang options" do
{:ok, regex} = Regex.compile("foo\\sbar", [:dotall, {:newline, :anycrlf}])
assert "foo\nbar" =~ regex
end
test "compile!/1" do
assert Regex.regex?(Regex.compile!("foo"))
assert_raise Regex.CompileError, ~r/position 0$/, fn ->
Regex.compile!("*foo")
end
end
test "recompile/1" do
new_regex = ~r/foo/
{:ok, regex} = Regex.recompile(new_regex)
assert Regex.regex?(regex)
assert Regex.regex?(Regex.recompile!(new_regex))
old_regex = Map.delete(~r/foo/, :re_version)
{:ok, regex} = Regex.recompile(old_regex)
assert Regex.regex?(regex)
assert Regex.regex?(Regex.recompile!(old_regex))
end
test "opts/1" do
assert Regex.opts(Regex.compile!("foo", "i")) == "i"
end
test "names/1" do
assert Regex.names(~r/(?<FOO>foo)/) == ["FOO"]
end
@@ -219,12 +267,6 @@ defmodule RegexTest do
assert Regex.replace(~r[a(b)c], "abcabc", fn "abc", "b" -> "ac" end, global: false) == "acabc"
end
test "ungreedy" do
assert Regex.run(~r/[\d ]+/, "1 2 3 4 5"), ["1 2 3 4 5"]
assert Regex.run(~r/[\d ]?+/, "1 2 3 4 5"), ["1"]
assert Regex.run(~r/[\d ]+/U, "1 2 3 4 5"), ["1"]
end
test "escape" do
assert matches_escaped?(".")
refute matches_escaped?(".", "x")
@@ -247,6 +289,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("[]") == "\\[\\]"
+8
View File
@@ -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]
+2 -1
View File
@@ -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
+1
View File
@@ -238,6 +238,7 @@ defmodule URITest do
end
assert URI.merge("http://google.com/foo", "http://example.com/baz") |> to_string == "http://example.com/baz"
assert URI.merge("http://google.com/foo", "//example.com/baz") |> to_string == "http://example.com/baz"
assert URI.merge("http://example.com", URI.parse("/foo")) |> to_string == "http://example.com/foo"
+9 -1
View File
@@ -66,9 +66,17 @@ defmodule VersionTest do
assert {:ok, %V{major: 1, minor: 4, patch: 5, pre: ["6-g3318bd5"]}} = V.parse("1.4.5-6-g3318bd5+ignore")
assert :error = V.parse("foobar")
assert :error = V.parse("2.3")
assert :error = V.parse("2")
assert :error = V.parse("2.")
assert :error = V.parse("2.3")
assert :error = V.parse("2.3.")
assert :error = V.parse("2.3.0-")
assert :error = V.parse("2.3.0+")
assert :error = V.parse("2.3.0-01")
assert :error = V.parse("2.3.00-1")
assert :error = V.parse("2.3.00")
assert :error = V.parse("2.03.0")
assert :error = V.parse("02.3.0")
end
test "to_string" do
+140 -63
View File
@@ -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,82 +112,139 @@ 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
next_prepend_size(rest, size + unquote(byte_size(codepoint)))
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
+4
View File
@@ -436,6 +436,10 @@ defmodule ExUnit.Diff do
inspect(key) <> " => "
end
defp format_key(key, true) when is_nil(key) or is_boolean(key) do
inspect(key) <> ": "
end
defp format_key(key, true) do
":" <> result = inspect(key)
result <> ": "
+20 -2
View File
@@ -426,7 +426,7 @@ defmodule ExUnit.DocTest do
end
defp extract_tests(line_no, doc, module) do
all_lines = String.split(doc, ~r/\n/, trim: false)
all_lines = String.split(doc, "\n", trim: false)
lines = adjust_indent(all_lines, line_no + 1, module)
extract_tests(lines, "", "", [], true, module)
end
@@ -626,11 +626,29 @@ defmodule ExUnit.DocTest do
[mod, message] = :binary.split(error, ")")
{:error, Module.concat([mod]), String.trim_leading(message)}
_ ->
if string =~ ~r/\A#[A-Z][\w\.]*</mu do
if is_inspected?(string) do
{:inspect, inspect(string)}
else
{:test, string}
end
end
end
defp is_inspected?(<<?#, char, rest::binary>>) when char in ?A..?Z,
do: is_inspected_end?(rest)
defp is_inspected?(_),
do: false
defp is_inspected_end?(<<?., char, rest::binary>>) when char in ?A..?Z,
do: is_inspected_end?(rest)
defp is_inspected_end?(<<char, rest::binary>>)
when char in ?A..?Z
when char in ?a..?z
when char in ?0..?9
when char == ?_,
do: is_inspected_end?(rest)
defp is_inspected_end?(<<?<, _::binary>>),
do: true
defp is_inspected_end?(_),
do: false
end
+5 -4
View File
@@ -14,10 +14,11 @@ defmodule ExUnit.Filters do
"""
@spec parse_path(String.t) :: {String.t, any}
def parse_path(file) do
case Regex.run(~r/^(.+):(\d+)$/, file, capture: :all_but_first) do
[file, line_number] ->
{file, exclude: [:test], include: [line: line_number]}
nil ->
{paths, [line]} = file |> String.split(":") |> Enum.split(-1)
case Integer.parse(line) do
{_, ""} ->
{Enum.join(paths, ":"), exclude: [:test], include: [line: line]}
_ ->
{file, []}
end
end
+4
View File
@@ -289,6 +289,10 @@ defmodule ExUnit.DiffTest do
assert script(%{"foo-bar": 1}, %{}) == [{:eq, "%{"}, [[del: "\"foo-bar\": 1"]], {:eq, "}"}]
assert script(%{}, %{}) == [eq: "%{}"]
assert script(%{nil: 42}, %{}) == [{:eq, "%{"}, [[del: "nil: 42"]], {:eq, "}"}]
assert script(%{true: 42}, %{}) == [{:eq, "%{"}, [[del: "true: 42"]], {:eq, "}"}]
assert script(%{false: 42}, %{}) == [{:eq, "%{"}, [[del: "false: 42"]], {:eq, "}"}]
end
test "structs" do
+16 -25
View File
@@ -115,9 +115,10 @@ defmodule IEx.Autocomplete do
# Elixir.fun
defp expand_call({:__aliases__, _, list}, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_require(hint)
case expand_alias(list, server) do
{:ok, alias} -> expand_require(alias, hint)
:error -> no()
end
end
# variable.fun_or_key
@@ -171,9 +172,10 @@ defmodule IEx.Autocomplete do
end
defp expand_elixir_modules(list, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_elixir_modules_from_aliases(hint, [])
case expand_alias(list, server) do
{:ok, alias} -> expand_elixir_modules_from_aliases(alias, hint, [])
:error -> no()
end
end
defp expand_elixir_modules_from_aliases(mod, hint, aliases) do
@@ -183,19 +185,16 @@ defmodule IEx.Autocomplete do
|> format_expansion(hint)
end
defp expand_alias([name | rest] = list, server) do
module = Module.concat(Elixir, name)
Enum.find_value aliases_from_env(server), 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 expand_alias([_ | _], _) do
:error
end
defp match_aliases(hint, server) do
for {alias, _mod} <- aliases_from_env(server),
@@ -220,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()
+8 -2
View File
@@ -158,7 +158,7 @@ defmodule IEx.Helpers do
{erls, exs} = Enum.split_with(found, &String.ends_with?(&1, ".erl"))
modules = Enum.map(erls, fn(source) ->
erl_modules = Enum.map(erls, fn(source) ->
{module, binary} = compile_erlang(source)
unless path == :in_memory do
base = source |> Path.basename |> Path.rootname
@@ -167,7 +167,13 @@ defmodule IEx.Helpers do
module
end)
modules ++ compile_elixir(exs, path)
ex_modules = try do
compile_elixir(exs, path)
catch
_, _ -> raise CompileError
end
erl_modules ++ ex_modules
end
@doc """
+5 -1
View File
@@ -269,7 +269,11 @@ defmodule IEx.Server do
defp allow_take?(identifier) do
message = IEx.color(:eval_interrupt, "#{identifier}\nAllow? [Yn] ")
IO.gets(:stdio, message) =~ ~r/^(Y(es)?)?$/i
yes?(IO.gets(:stdio, message))
end
defp yes?(string) do
is_binary(string) and String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
end
## State
+1
View File
@@ -90,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
+19 -27
View File
@@ -296,19 +296,12 @@ defmodule IEx.HelpersTest do
cleanup_modules([Sample])
end
test "c/2 helper" do
assert_raise UndefinedFunctionError, ~r"function Sample\.run/0 is undefined", fn ->
Sample.run
test "c helper with error" do
ExUnit.CaptureIO.capture_io fn ->
with_file "sample.ex", "raise \"oops\"", fn ->
assert_raise CompileError, fn -> c("sample.ex") end
end
end
filename = "sample.ex"
with_file filename, test_module_code(), fn ->
assert c(filename, ".") == [Sample]
assert File.exists?("Elixir.Sample.beam")
assert Sample.run == :run
end
after
cleanup_modules([Sample])
end
test "c helper with full path" do
@@ -366,21 +359,6 @@ defmodule IEx.HelpersTest do
cleanup_modules([:sample])
end
test "c/2 helper erlang" do
assert_raise UndefinedFunctionError, ~r"function :sample.hello/0 is undefined", fn ->
:sample.hello
end
filename = "sample.erl"
with_file filename, erlang_module_code(), fn ->
assert c(filename, ".") == [:sample]
assert :sample.hello == :world
assert File.exists?("sample.beam")
end
after
cleanup_modules([:sample])
end
test "c helper skips unknown files" do
assert_raise UndefinedFunctionError, ~r"function :sample.hello/0 is undefined", fn ->
:sample.hello
@@ -396,6 +374,20 @@ defmodule IEx.HelpersTest do
cleanup_modules([:sample, Sample2])
end
test "c helper with path" do
assert_raise UndefinedFunctionError, ~r"function Sample\.run/0 is undefined", fn ->
Sample.run
end
filename = "sample.ex"
with_file filename, test_module_code(), fn ->
assert c(filename, ".") == [Sample]
assert File.exists?("Elixir.Sample.beam")
assert Sample.run == :run
end
after
cleanup_modules([Sample])
end
test "l helper" do
assert_raise UndefinedFunctionError, ~r"function Sample.run/0 is undefined", fn ->
+3 -1
View File
@@ -84,7 +84,9 @@ defmodule Logger.Formatter do
def compile({mod, fun}) when is_atom(mod) and is_atom(fun), do: {mod, fun}
def compile(str) do
for part <- Regex.split(~r/(?<head>)\$[a-z]+(?<tail>)/, str, on: [:head, :tail], trim: true) do
regex = Regex.recompile!(~r/(?<head>)\$[a-z]+(?<tail>)/)
for part <- Regex.split(regex, str, on: [:head, :tail], trim: true) do
case part do
"$" <> code -> compile_code(String.to_atom(code))
_ -> part
+24 -3
View File
@@ -36,11 +36,12 @@ defmodule Logger.Translator do
opts = Application.get_env(:logger, :translator_inspect_opts)
case message do
{'** Generic server ' ++ _, [name, last, state, reason]} ->
{'** Generic server ' ++ _, [name, last, state, reason | client]} ->
msg = ["GenServer #{inspect name} terminating", format_stop(reason),
"\nLast message: #{inspect last, opts}"]
"\nLast message#{format_from(client)}: #{inspect last, opts}"]
if min_level == :debug do
{:ok, [msg | "\nState: #{inspect state, opts}"]}
{:ok, [msg, "\nState: #{inspect state, opts}" |
format_client(client)]}
else
{:ok, msg}
end
@@ -375,4 +376,24 @@ defmodule Logger.Translator do
defp format_mfa(mod, fun, :undefined), do: [inspect(mod), ?., to_string(fun) | "/?"]
defp format_mfa(mod, fun, args), do: Exception.format_mfa(mod, fun, args)
defp format_from([]),
do: ""
defp format_from([from]),
do: " (from #{inspect(from)})"
defp format_from([from, stacktrace]) when is_list(stacktrace),
do: " (from #{inspect(from)})"
defp format_from([from, node_name]) when is_atom(node_name),
do: " (from #{inspect(from)} on #{inspect(node_name)})"
defp format_client([from]) do
"\nClient #{inspect(from)} is dead"
end
defp format_client([from, stacktrace]) when is_list(stacktrace) do
["\nClient #{inspect(from)} is alive\n" |
Exception.format_stacktrace(stacktrace)]
end
defp format_client(_) do
[]
end
end
+93 -3
View File
@@ -5,9 +5,18 @@ defmodule Logger.TranslatorTest do
defmodule MyGenServer do
use GenServer
def handle_cast(:error, _) do
raise "oops"
end
def handle_call(:error, _, _) do
raise "oops"
end
def handle_call(:error_on_down, {pid, _}, _) do
mon = Process.monitor(pid)
assert_receive {:DOWN, ^mon, _, _, _}
raise "oops"
end
end
defmodule MyGenEvent do
@@ -93,20 +102,101 @@ defmodule Logger.TranslatorTest do
Application.put_env(:logger, :translator_inspect_opts, [])
end
test "translates GenServer crashes on debug" do
# TODO: Remove this check once we depend only on 20
if :erlang.system_info(:otp_release) >= '20' do
test "translates GenServer crashes on debug" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
assert capture_log(:debug, fn ->
catch_exit(GenServer.call(pid, :error))
end) =~ ~r"""
\[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
\*\* \(RuntimeError\) oops
.*
Last message \(from #PID<\d+\.\d+\.\d+>\): :error
State: :ok
Client #PID<\d+\.\d+\.\d+> is alive
.*
"""s
end
test "translates GenServer crashes with named client on debug" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
assert capture_log(:debug, fn ->
Process.register(self(), :named_client)
catch_exit(GenServer.call(pid, :error))
end) =~ ~r"""
\[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
\*\* \(RuntimeError\) oops
.*
Last message \(from :named_client\): :error
State: :ok
Client :named_client is alive
.*
"""s
end
test "translates GenServer crashes with dead client on debug" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
assert capture_log(:debug, fn ->
mon = Process.monitor(pid)
spawn_link(fn() ->
catch_exit(GenServer.call(pid, :error_on_down, 0))
end)
assert_receive {:DOWN, ^mon, _, _, _}
end) =~ ~r"""
\[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
\*\* \(RuntimeError\) oops
.*
Last message \(from #PID<\d+\.\d+\.\d+>\): :error_on_down
State: :ok
Client #PID<\d+\.\d+\.\d+> is dead
"""s
end
else
test "translates GenServer crashes on debug" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
assert capture_log(:debug, fn ->
catch_exit(GenServer.call(pid, :error))
end) =~ ~r"""
\[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
\*\* \(RuntimeError\) oops
.*
Last message: :error
State: :ok
"""s
end
end
test "translates GenServer crashes with no client" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
assert capture_log(:debug, fn ->
catch_exit(GenServer.call(pid, :error))
mon = Process.monitor(pid)
GenServer.cast(pid, :error)
assert_receive {:DOWN, ^mon, _, _, _}
end) =~ ~r"""
\[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
\*\* \(RuntimeError\) oops
.*
Last message: :error
Last message: {:"\$gen_cast", :error}
State: :ok
"""s
end
test "translates GenServer crashes with no client on debug" do
{:ok, pid} = GenServer.start(MyGenServer, :ok)
refute capture_log(:debug, fn ->
mon = Process.monitor(pid)
GenServer.cast(pid, :error)
assert_receive {:DOWN, ^mon, _, _, _}
end) =~ "Client"
end
test "translates :gen_event crashes" do
{:ok, pid} = :gen_event.start()
:ok = :gen_event.add_handler(pid, MyGenEvent, :ok)
+1 -1
View File
@@ -297,7 +297,7 @@ defmodule Mix.Dep do
end
defp check_manifest(%{scm: scm} = dep, build_path) do
vsn = System.version
vsn = {System.version, :erlang.system_info(:otp_release)}
case Mix.Dep.ElixirSCM.read(build_path) do
{:ok, old_vsn, _} when old_vsn != vsn ->
+5 -10
View File
@@ -2,7 +2,7 @@
defmodule Mix.Dep.ElixirSCM do
@moduledoc false
@manifest ".compile.elixir_scm"
@manifest_vsn :v1
@manifest_vsn :v2
def manifest(manifest_path \\ Mix.Project.manifest_path) do
Path.join(manifest_path, @manifest)
@@ -13,7 +13,7 @@ defmodule Mix.Dep.ElixirSCM do
File.mkdir_p!(manifest_path)
manifest_data =
{@manifest_vsn, System.version, config[:build_scm]}
{@manifest_vsn, {System.version, :erlang.system_info(:otp_release)}, config[:build_scm]}
|> :erlang.term_to_binary(compressed: 9)
File.write!(manifest(manifest_path), manifest_data)
@@ -23,15 +23,10 @@ defmodule Mix.Dep.ElixirSCM do
case File.read(manifest(manifest_path)) do
{:ok, contents} ->
try do
:erlang.binary_to_term(contents)
else
{@manifest_vsn, vsn, scm} ->
{:ok, vsn, scm}
_ ->
{:ok, "1.0.0", nil} # Force old version if file exists but old format
{@manifest_vsn, vsn, scm} = :erlang.binary_to_term(contents)
{:ok, vsn, scm}
rescue
_ ->
{:ok, "1.0.0", nil} # Force old version if file exists but old format
_ -> {:ok, {"1.0.0", '17'}, nil}
end
_ ->
:error
+18 -14
View File
@@ -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]
+1 -1
View File
@@ -99,7 +99,7 @@ defmodule Mix.Local.Installer do
defp do_install({module, name}, src, opts) do
src_basename = Path.basename(URI.parse(src).path)
dst = Path.join(Mix.Local.path_for(name), src_basename)
previous_files = module.find_previous_versions(src, dst)
previous_files = module.find_previous_versions(src_basename, dst)
if opts[:force] || should_install?(name, src, previous_files) do
case Mix.Utils.read_path(src, opts) do
+5 -4
View File
@@ -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},
+1 -1
View File
@@ -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
View File
@@ -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
+9 -13
View File
@@ -46,31 +46,27 @@ defmodule Mix.Shell.IO do
end
@doc """
Prints a message and prompts the user for
input. Input will be consumed until Enter is pressed.
Prints a message and prompts the user for input.
Input will be consumed until Enter is pressed.
"""
def prompt(message) do
print_app()
IO.gets message <> " "
IO.gets(message <> " ")
end
@doc """
Prints a message and asks the user if they want to proceed.
The user must press Enter or type anything that matches the "yes"
regex `~r/^Y(es)?$/i`.
The user must press Enter or type one of "y", "yes", "Y", "YES" or
"Yes".
"""
def yes?(message) do
print_app()
got_yes? IO.gets(message <> " [Yn] ")
answer = IO.gets(message <> " [Yn] ")
is_binary(answer) and String.trim(answer) in ["", "y", "Y", "yes", "YES", "Yes"]
end
defp got_yes?(answer) when is_binary(answer) do
answer =~ ~r/^(Y(es)?)?$/i
end
# The IO server may return :eof or :error
defp got_yes?(_), do: false
defp red(message) do
[:red, :bright, message]
end
+7 -5
View File
@@ -32,15 +32,17 @@ defmodule Mix.Shell.Quiet do
defdelegate error(message), to: Mix.Shell.IO
@doc """
Prints a message and prompts the user for
input. Input will be consumed until Enter is pressed.
Prints a message and prompts the user for input.
Input will be consumed until Enter is pressed.
"""
defdelegate prompt(message), to: Mix.Shell.IO
@doc """
Receives a message and asks the user if they want to proceed.
The user must press enter or type anything that matches the "yes"
regex `~r/^Y(es)?$/i`.
Prints a message and asks the user if they want to proceed.
The user must press Enter or type one of "y", "yes", "Y", "YES" or
"Yes".
"""
defdelegate yes?(message), to: Mix.Shell.IO
end
+8 -2
View File
@@ -68,13 +68,19 @@ defmodule Mix.Task do
# entire load path so make sure we only return unique modules.
for(dir <- dirs,
{:ok, files} = :erl_prim_loader.list_dir(to_charlist(dir)),
file <- files,
file <- safe_list_dir(to_charlist(dir)),
mod = task_from_path(file),
do: mod)
|> Enum.uniq
end
defp safe_list_dir(path) do
case :erl_prim_loader.list_dir(path) do
{:ok, paths} -> paths
{:error, _} -> []
end
end
@prefix_size byte_size("Elixir.Mix.Tasks.")
@suffix_size byte_size(".beam")
+1 -1
View File
@@ -106,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
+25 -7
View File
@@ -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
+1 -1
View File
@@ -58,7 +58,7 @@ defmodule Mix.Tasks.Loadpaths do
end
defp load_project(config, _args) do
vsn = System.version
vsn = {System.version, :erlang.system_info(:otp_release)}
scm = config[:build_scm]
# Erase the app build if we have lock mismatch.
+2 -2
View File
@@ -158,7 +158,7 @@ defmodule Mix.Tasks.New do
end
defp check_application_name!(name, inferred?) do
unless name =~ ~r/^[a-z][\w_]*$/ do
unless name =~ Regex.recompile!(~r/^[a-z][a-z0-9_]*$/) do
Mix.raise "Application name must start with a letter and have only lowercase " <>
"letters, numbers and underscore, got: #{inspect name}" <>
(if inferred? do
@@ -171,7 +171,7 @@ defmodule Mix.Tasks.New do
end
defp check_mod_name_validity!(name) do
unless name =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/ do
unless name =~ Regex.recompile!(~r/^[A-Z]\w*(\.[A-Z]\w*)*$/) do
Mix.raise "Module name must be a valid Elixir alias (for example: Foo.Bar), got: #{inspect name}"
end
end
+213 -194
View File
@@ -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
+5
View File
@@ -52,6 +52,11 @@ defmodule Mix.Tasks.ArchiveTest do
archive = tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin")
assert to_charlist(archive) in :code.get_path
# Try to override it with URL
send self(), {:mix_shell_input, :yes?, false}
Mix.Tasks.Archive.Install.run ["https://example.com/archive-0.1.0?hello.ez"]
assert_received {:mix_shell, :yes?, ["Found existing archive: " <> _]}
# Loading the archive should emit warning again
Mix.Local.append_archives
assert_received {:mix_shell, :error, [^version_error]}
@@ -8,6 +8,8 @@ defmodule Mix.Tasks.Compile.ElixirTest do
:ok
end
@elixir_otp_version {System.version, :erlang.system_info(:otp_release)}
test "compiles a project without per environment build" do
Mix.Project.pop
Mix.ProjectStack.post_config [build_per_environment: false]
@@ -43,7 +45,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
assert File.exists?("_build/dev/lib/sample")
assert File.exists?("_build/dev/consolidated")
assert Mix.Dep.ElixirSCM.read == {:ok, System.version, Mix.SCM.Path}
assert Mix.Dep.ElixirSCM.read == {:ok, @elixir_otp_version, Mix.SCM.Path}
Mix.Task.clear
File.write!("_build/dev/consolidated/.to_be_removed", "")
@@ -52,7 +54,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
File.touch!("_build/dev/lib/sample/.compile.elixir_scm", {{2010, 1, 1}, {0, 0, 0}})
Mix.Tasks.Compile.run []
assert Mix.Dep.ElixirSCM.read == {:ok, System.version, Mix.SCM.Path}
assert Mix.Dep.ElixirSCM.read == {:ok, @elixir_otp_version, Mix.SCM.Path}
assert File.stat!("_build/dev/lib/sample/.compile.elixir_scm").mtime > {{2010, 1, 1}, {0, 0, 0}}
refute File.exists?("_build/dev/consolidated/.to_be_removed")
end
@@ -64,15 +66,15 @@ defmodule Mix.Tasks.Compile.ElixirTest do
purge [A, B]
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
assert Mix.Dep.ElixirSCM.read == {:ok, System.version, Mix.SCM.Path}
assert Mix.Dep.ElixirSCM.read == {:ok, @elixir_otp_version, Mix.SCM.Path}
Mix.Task.clear
manifest_data = :erlang.term_to_binary({:v1, System.version, :another})
manifest_data = :erlang.term_to_binary({:v2, @elixir_otp_version, :another})
File.write!("_build/dev/lib/sample/.compile.elixir_scm", manifest_data)
File.touch!("_build/dev/lib/sample/.compile.elixir_scm", {{2010, 1, 1}, {0, 0, 0}})
Mix.Tasks.Compile.run []
assert Mix.Dep.ElixirSCM.read == {:ok, System.version, Mix.SCM.Path}
assert Mix.Dep.ElixirSCM.read == {:ok, @elixir_otp_version, Mix.SCM.Path}
assert File.stat!("_build/dev/lib/sample/.compile.elixir_scm").mtime > {{2010, 1, 1}, {0, 0, 0}}
end
end
+1 -1
View File
@@ -571,7 +571,7 @@ defmodule Mix.Tasks.DepsTest do
Mix.Tasks.Deps.Loadpaths.run []
File.mkdir_p!("_build/dev/lib/ok/ebin")
manifest_data = :erlang.term_to_binary({:v1, System.version, :scm})
manifest_data = :erlang.term_to_binary({:v2, {System.version, :erlang.system_info(:otp_release)}, :scm})
File.write!("_build/dev/lib/ok/.compile.elixir_scm", manifest_data)
Mix.Task.clear
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.4.0"},
{vsn, "1.4.3"},
{modules, [
elixir
]},