Compare commits

...
40 Commits
Author SHA1 Message Date
Eksperimental 5131c6be9d Missing deprecation notices in CHANGELOG v1.4 (#7614) 2018-04-28 12:07:33 +08:00
Wojtek Mach e3f3dce208 Fix links in documentation guideline on v1.4 (#6282) 2017-07-01 21:45:53 +02:00
José Valim 5ebd0d2b0b Fix misplaced backtick on CHANGELOG 2017-06-22 10:44:40 +02:00
José Valim e25c37108a Release v1.4.5 2017-06-22 10:30:39 +02:00
James Fish 0b30f6495b Handle changes to crash reports in OTP 20 (#6185)
* :message_queue_len and :current_stacktrace added
* :messages and :dictionary not always present

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-06-11 17:35:32 +02:00
Andrea Leopardi f126e9f6f6 Fix indentation in Logger.Translator
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-06-11 17:33:32 +02:00
Steven Blowers ca06fbf6e0 Fix whitespace inconsistencies all around (#5562)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-06-11 17:33:28 +02:00
José Valim 20621d41c6 Do not leak EXIT messages on Registry dispatch
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-06-11 17:30:13 +02:00
Lukas Dolezal 600ca02749 Fix DateTime.from_iso8601/2 when offset has no colon
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-06-02 20:00:11 +02:00
Josh Austin 241e13da98 Add note to CHANGELOG.md about Version parser updates (#6157) 2017-05-27 10:31:55 +02:00
Eksperimental 9c8250a092 Replace no-break space in CHANGELOG (#6140)
This was introduced in
https://github.com/elixir-lang/elixir/pull/5804
due to creating a patch via web interface
2017-05-23 15:22:20 +02:00
José Valim 867da6f876 Release v1.4.4 2017-05-15 16:53:22 +02:00
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
53 changed files with 842 additions and 340 deletions
+78 -6
View File
@@ -109,6 +109,75 @@ 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.5 (2017-06-22)
This version includes changes that make Elixir fully compatible with Erlang/OTP 20.
### 1. Enhancements
#### Logger
* [Logger] Handle changes to crash reports in OTP 20
### 2. Bug fixes
#### Elixir
* [DateTime] Fix `DateTime.from_iso8601/2` when offset has no colon
* [Registry] Do not leak EXIT messages on `Registry.dispatch/3`
## v1.4.4 (2017-05-15)
This version includes changes that make Elixir fully compatible with Erlang/OTP 20-rc.1.
### 1. Bug fixes
#### Elixir
* [Map] Fix regression on struct update syntax
## 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`
* [Version] Reject leading zeros according to the SemVer spec
#### 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
@@ -121,7 +190,7 @@ It is also possible to install escripts and archives by providing a Git/GitHub r
* [Calendar] Correct typo on Calendar types
* [Kernel] Ensure redefined functions point to the proper source
* [OptionParser] Add `:allow_inexistent_atoms` to support unsafe behaviour prior to v1.4
* [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
@@ -269,14 +338,17 @@ 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)
* [Kernel] Deprecate support for making private functions overridable. Overridable functions must always be public as they must be contracts
* [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)
 * [HashDict] The `HashDict` module is deprecated, in favour of the `Map`
 * [HashSet] The `HashDict` module is deprecated, in favour of the `MapSet`
 * [Kernel] Deprecate support for making private functions overridable. Overridable functions must always be public as they must be contracts
* [Kernel] Anonymous functions with no expression after `->`, in favor of using an expression or returning `nil`
* [Kernel] Warn if variable is used as a function call
* [OptionParser] Deprecate aliases with multiple letters, such as `-abc`
* [Set] Deprecate the `Set` module
* [Set] Deprecation of the `Set` module in favor of `MapSet`
* [Stream] Deprecate `Stream.uniq/2` in favor of `Stream.uniq_by/2`
#### IEx
@@ -285,7 +357,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
@@ -1 +1 @@
1.4.2
1.4.5
+8
View File
@@ -240,6 +240,14 @@ defmodule Calendar.ISO do
do: parse_offset(1, hour, min, rest)
def parse_offset(<<?-, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
do: parse_offset(-1, hour, min, rest)
def parse_offset(<<?+, hour::2-bytes, min::2-bytes, rest::binary>>),
do: parse_offset(1, hour, min, rest)
def parse_offset(<<?-, hour::2-bytes, min::2-bytes, rest::binary>>),
do: parse_offset(-1, hour, min, rest)
def parse_offset(<<?+, hour::2-bytes, rest::binary>>),
do: parse_offset(1, hour, "00", rest)
def parse_offset(<<?-, hour::2-bytes, rest::binary>>),
do: parse_offset(-1, hour, "00", rest)
def parse_offset(_),
do: :error
+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])
+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
+2
View File
@@ -359,11 +359,13 @@ defmodule Registry do
end
defp dispatch_task(registry, key, mfa_or_fun, partition) do
partition = partition - 1
parent = self()
task = Task.async(fn ->
registry
|> key_ets!(partition)
|> safe_lookup_second(key)
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
Process.unlink(parent)
:ok
end)
[task | dispatch_task(registry, key, mfa_or_fun, partition)]
+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/
* [Basic writing and formatting syntax](https://help.github.com/articles/basic-writing-and-formatting-syntax/)
* [Mastering Markdown](https://guides.github.com/features/mastering-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(),
+4 -4
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 ->
+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
+24 -13
View File
@@ -12,8 +12,9 @@ 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 = clean_struct_key_from_map_args(Meta, MapArgs, E),
{[ELeft, ERight], EE} = elixir_exp:expand_args([Left, {'%{}', MapMeta, CleanArgs}], E),
case validate_struct(ELeft, Context) of
true when is_atom(ELeft) ->
@@ -38,14 +39,24 @@ 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};
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)]).
{{'%', EMeta, [ELeft, ERight]}, EE}.
clean_struct_key_from_map_args(Meta, [{'|', PipeMeta, [Left, MapAssocs]}], E) ->
[{'|', PipeMeta, [Left, clean_struct_key_from_map_assocs(Meta, MapAssocs, E)]}];
clean_struct_key_from_map_args(Meta, MapAssocs, E) ->
clean_struct_key_from_map_assocs(Meta, MapAssocs, E).
clean_struct_key_from_map_assocs(Meta, Assocs, E) ->
case lists:keytake('__struct__', 1, Assocs) of
{value, _, CleanAssocs} ->
elixir_errors:warn(?line(Meta), ?m(E, file), "key :__struct__ is ignored when using structs"),
CleanAssocs;
false ->
Assocs
end.
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 +77,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 +109,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
+1 -1
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}}),
+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) ->
+32
View File
@@ -163,4 +163,36 @@ defmodule DateTimeTest do
assert DateTime.compare(datetime1, datetime2) == :lt
assert DateTime.compare(datetime2, datetime1) == :gt
end
test "from_iso8601/1 with tz offsets" do
assert DateTime.from_iso8601("2017-06-02T14:00:00+01:00") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 13, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
assert DateTime.from_iso8601("2017-06-02T14:00:00-04:00") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 18, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
assert DateTime.from_iso8601("2017-06-02T14:00:00+0100") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 13, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
assert DateTime.from_iso8601("2017-06-02T14:00:00-0400") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 18, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
assert DateTime.from_iso8601("2017-06-02T14:00:00+01") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 13, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
assert DateTime.from_iso8601("2017-06-02T14:00:00-04") |> elem(1) ==
%DateTime{year: 2017, month: 6, day: 2, zone_abbr: "UTC",
hour: 18, minute: 0, second: 0, microsecond: {0, 0},
utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"}
end
end
-19
View File
@@ -263,25 +263,6 @@ defmodule ExceptionTest do
assert Exception.format_exit(reason) |> String.starts_with?(expected_to_start_with)
end
test "format_exit with call with exception" do
Process.flag(:trap_exit, true)
# Fake reason to prevent error_logger printing to stdout
exit_reason = {%ArgumentError{}, [{:not_a_real_module, :function, 0, []}]}
exit_fun = fn() -> receive do: (_ -> exit(exit_reason)) end
reason =
try do
:gen_fsm.sync_send_event(spawn_link(exit_fun), :hello)
catch
:exit, reason -> reason
end
formatted = Exception.format_exit(reason)
assert formatted =~ ~r"exited in: :gen_fsm\.sync_send_event\(#PID<\d+\.\d+\.\d+>, :hello\)"
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
@@ -373,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 """
@@ -768,6 +781,27 @@ defmodule Kernel.WarningTest do
purge Sample
end
defmodule User do
defstruct [:name]
end
test ":__struct__ is ignored when using structs" do
assert capture_err(fn ->
Code.eval_string """
assert %Kernel.WarningTest.User{__struct__: Ignored, name: "joe"} ==
%Kernel.WarningTest.User{name: "joe"}
""", [], __ENV__
end) =~ "key :__struct__ is ignored when using structs"
assert capture_err(fn ->
Code.eval_string """
user = %Kernel.WarningTest.User{name: "meg"}
assert %Kernel.WarningTest.User{user | __struct__: Ignored, name: "joe"} ==
%Kernel.WarningTest.User{__struct__: Kernel.WarningTest.User, name: "joe"}
""", [], __ENV__
end) =~ "key :__struct__ is ignored when using structs"
end
defp purge(list) when is_list(list) do
Enum.each list, &purge/1
end
+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}
+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
+49 -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")
+4
View File
@@ -207,6 +207,8 @@ defmodule RegistryTest do
end
test "dispatches to multiple keys", %{registry: registry} do
Process.flag(:trap_exit, true)
assert Registry.dispatch(registry, "hello", fn _ ->
raise "will never be invoked"
end) == :ok
@@ -230,6 +232,8 @@ defmodule RegistryTest do
refute_received {:dispatch, :value1}
refute_received {:dispatch, :value2}
assert_received {:dispatch, :value3}
refute_received {:EXIT, _, _}
end
test "allows process unregistering", %{registry: registry} do
+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
+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
+1 -1
View File
@@ -11,7 +11,7 @@ defmodule ExUnit.OnExitHandler do
Agent.update(@name, &Map.put(&1, pid, []))
end
@spec add(pid, term, (()-> term)) :: :ok | :error
@spec add(pid, term, (() -> term)) :: :ok | :error
def add(pid, name_or_ref, callback) when is_pid(pid) and is_function(callback, 0) do
Agent.get_and_update(@name, fn map ->
if entries = Map.get(map, pid) do
+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
+2 -2
View File
@@ -102,7 +102,7 @@ defmodule IEx.Autocomplete do
if prefix in [0, length] do
yes("", Enum.flat_map(entries, &to_entries/1))
else
yes(:binary.part(first.name, prefix, length-prefix), [])
yes(:binary.part(first.name, prefix, length - prefix), [])
end
end
@@ -212,7 +212,7 @@ defmodule IEx.Autocomplete do
for mod <- match_modules(base, module === Elixir),
parts = String.split(mod, "."),
depth <= length(parts) do
%{kind: :module, type: :elixir, name: Enum.at(parts, depth-1)}
%{kind: :module, type: :elixir, name: Enum.at(parts, depth - 1)}
end
|> Enum.uniq
end
+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
+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
+101 -62
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
@@ -96,7 +97,7 @@ defmodule Logger.Translator do
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:pid, pid}, {name_or_id, name} | offender]])
when is_pid(pid) and context !== :shutdown and name_or_id in [:name, :id] do
when is_pid(pid) and context !== :shutdown and name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context),
@@ -105,37 +106,37 @@ defmodule Logger.Translator do
end
defp translate_supervisor(min_level,
[supervisor: sup, errorContext: context,
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:pid, _pid},
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context) |
child_info(min_level, offender)]}
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context) |
child_info(min_level, offender)]}
end
defp translate_supervisor(min_level,
[supervisor: sup, errorContext: context,
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:pid, pid} | offender]]) do
{:ok, ["Child of Supervisor ",
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context),
"\nPid: ", inspect(pid) |
child_info(min_level, offender)]}
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context),
"\nPid: ", inspect(pid) |
child_info(min_level, offender)]}
end
defp translate_supervisor(min_level,
[supervisor: sup, errorContext: context,
[supervisor: sup, errorContext: context,
reason: reason,
offender: [{:nb_children, n},
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
{:ok, ["Children ", inspect(name), " of Supervisor ",
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context),
"\nNumber: ", inspect(n) |
child_info(min_level, offender)]}
sup_name(sup), ?\s, sup_context(context),
"\n** (exit) ", offender_reason(reason, context),
"\nNumber: ", inspect(n) |
child_info(min_level, offender)]}
end
defp translate_supervisor(_min_level, _other), do: :none
@@ -147,19 +148,19 @@ defmodule Logger.Translator do
defp translate_progress(min_level,
[supervisor: sup,
started: [{:pid, pid}, {name_or_id, name} | started]]) when name_or_id in [:name, :id] do
started: [{:pid, pid}, {name_or_id, name} | started]]) when name_or_id in [:name, :id] do
{:ok, ["Child ", inspect(name), " of Supervisor ",
sup_name(sup), " started",
"\nPid: ", inspect(pid) |
child_info(min_level, started)]}
sup_name(sup), " started",
"\nPid: ", inspect(pid) |
child_info(min_level, started)]}
end
defp translate_progress(min_level,
[supervisor: sup,
started: [{:pid, pid} | started]]) do
started: [{:pid, pid} | started]]) do
{:ok, ["Child of Supervisor ", sup_name(sup), " started",
"\nPid: ", inspect(pid) |
child_info(min_level, started)]}
"\nPid: ", inspect(pid) |
child_info(min_level, started)]}
end
defp translate_progress(_min_level, _other), do: :none
@@ -176,17 +177,17 @@ defmodule Logger.Translator do
defp child_info(min_level, [{:mfargs, {mod, fun, args}} | debug]) do
["\nStart Call: ", format_mfa(mod, fun, args) |
child_debug(min_level, debug)]
child_debug(min_level, debug)]
end
defp child_info(min_level, [{:mfa, {mod, fun, args}} | debug]) do
["\nStart Call: ", format_mfa(mod, fun, args) |
child_debug(min_level, debug)]
child_debug(min_level, debug)]
end
defp child_info(min_level, [{:mod, mod} | debug]) do
["\nStart Module: ", inspect(mod) |
child_debug(min_level, debug)]
child_debug(min_level, debug)]
end
defp child_debug(:debug,
@@ -216,9 +217,9 @@ defmodule Logger.Translator do
{:error_info, {kind, exception, stack}} | crashed],
linked]) do
{:ok, ["Process ", crash_name(pid, name), " terminating",
format(kind, exception, stack),
crash_info(min_level, [initial_call | crashed]) |
crash_linked(min_level, linked)]}
format(kind, exception, stack),
crash_info(min_level, [initial_call | crashed]) |
crash_linked(min_level, linked)]}
end
defp translate_crash(min_level,
@@ -227,9 +228,9 @@ defmodule Logger.Translator do
{:error_info, {kind, exception, stack}} | crashed],
linked]) do
{:ok, ["Process ", crash_name(pid, name), " terminating",
format(kind, exception, stack),
crash_info(min_level, crashed),
crash_linked(min_level, linked)]}
format(kind, exception, stack),
crash_info(min_level, crashed),
crash_linked(min_level, linked)]}
end
defp crash_name(pid, []), do: inspect(pid)
@@ -240,13 +241,13 @@ defmodule Logger.Translator do
defp crash_info(min_level,
[{:initial_call, {mod, fun, args}} | info], prefix) do
[prefix, "Initial Call: ", crash_call(mod, fun, args) |
crash_info(min_level, info, prefix)]
crash_info(min_level, info, prefix)]
end
defp crash_info(min_level,
[{:current_function, {mod, fun, args}} | info], prefix) do
[prefix, "Current Call: ", crash_call(mod, fun, args) |
crash_info(min_level, info, prefix)]
crash_info(min_level, info, prefix)]
end
defp crash_info(min_level, [{:current_function, []} | info], prefix) do
@@ -256,7 +257,17 @@ defmodule Logger.Translator do
defp crash_info(min_level,
[{:ancestors, ancestors} | debug], prefix) do
[prefix, "Ancestors: ", inspect(ancestors) |
crash_debug(min_level, debug, prefix)]
crash_info(min_level, debug, prefix)]
end
defp crash_info(:debug, debug, prefix) do
for {key, value} <- debug do
crash_debug(key, value, prefix)
end
end
defp crash_info(_, _, _) do
[]
end
defp crash_call(mod, fun, arity) when is_integer(arity) do
@@ -267,22 +278,28 @@ defmodule Logger.Translator do
format_mfa(mod, fun, length(args))
end
defp crash_debug(:debug,
[messages: msgs, links: links, dictionary: dict,
trap_exit: trap, status: status, heap_size: heap_size,
stack_size: stack_size, reductions: reductions], prefix) do
[prefix, "Messages: ", inspect(msgs),
prefix, "Links: ", inspect(links),
prefix, "Dictionary: ", inspect(dict),
prefix, "Trapping Exits: ", inspect(trap),
prefix, "Status: ", inspect(status),
prefix, "Heap Size: ", inspect(heap_size),
prefix, "Stack Size: ", inspect(stack_size),
prefix, "Reductions: ", inspect(reductions)]
defp crash_debug(:current_stacktrace, stack, prefix) do
stack_prefix = [prefix | " "]
[prefix, "Current Stacktrace:" |
Enum.map(stack, &[stack_prefix | Exception.format_stacktrace_entry(&1)])]
end
defp crash_debug(_min_level, _info, _prefix) do
[]
defp crash_debug(key, value, prefix) do
[prefix, crash_debug_key(key), ?:, ?\s, inspect(value)]
end
defp crash_debug_key(key) do
case key do
:message_queue_len -> "Message Queue Length"
:messages -> "Messages"
:links -> "Links"
:dictionary -> "Dictionary"
:trap_exit -> "Trapping Exits"
:status -> "Status"
:heap_size -> "Heap Size"
:stack_size -> "Stack Size"
:reductions -> "Reductions"
end
end
defp crash_linked(_min_level, []), do: []
@@ -297,17 +314,17 @@ defmodule Logger.Translator do
[{:pid, pid}, {:registered_name, []} | info]) do
indent = " "
[?\n, indent, inspect(pid) |
crash_info(min_level, info, [?\n, indent | indent])]
crash_info(min_level, info, [?\n, indent | indent])]
end
defp crash_neighbour(min_level,
[{:pid, pid}, {:registered_name, name} | info]) do
indent = " "
[?\n, indent, inspect(name), " (", inspect(pid), ")" |
crash_info(min_level, info, [?\n, indent | indent])]
crash_info(min_level, info, [?\n, indent | indent])]
end
defp format_stop({maybe_exception, [_ | _ ] = maybe_stacktrace} = reason) do
defp format_stop({maybe_exception, [_ | _] = maybe_stacktrace} = reason) do
try do
format_stacktrace(maybe_stacktrace)
catch
@@ -315,8 +332,7 @@ defmodule Logger.Translator do
format_stop_banner(reason)
else
formatted_stacktrace ->
[format_stop_banner(maybe_exception, maybe_stacktrace) |
formatted_stacktrace]
[format_stop_banner(maybe_exception, maybe_stacktrace) | formatted_stacktrace]
end
end
@@ -331,8 +347,8 @@ defmodule Logger.Translator do
# OTP processes rewrite the :undef error to these reasons when logging
@gen_undef [:"module could not be loaded", :"function not exported"]
defp format_stop_banner(undef, [{mod, fun, args, _info} | _ ] = stacktrace)
when undef in @gen_undef and is_atom(mod) and is_atom(fun) do
defp format_stop_banner(undef, [{mod, fun, args, _info} | _] = stacktrace)
when undef in @gen_undef and is_atom(mod) and is_atom(fun) do
cond do
is_list(args) ->
format_undef(mod, fun, length(args), undef, stacktrace)
@@ -344,8 +360,11 @@ defmodule Logger.Translator do
end
defp format_stop_banner(reason, stacktrace) do
# If this is already an exception (even an ErlangError), we format it as an
# exception. Otherwise, we try to normalize it, and if it's normalized as an
# ErlangError we instead format it as an exit.
if Exception.exception?(reason) do
[?\n | Exception.format_banner(:error, reason, stacktrace)]
[?\n | Exception.format_banner(:error, reason, stacktrace)]
else
case Exception.normalize(:error, reason, stacktrace) do
%ErlangError{} ->
@@ -368,11 +387,31 @@ defmodule Logger.Translator do
end
defp format_stacktrace(stacktrace) do
for entry <- stacktrace do
[<<"\n ">> | Exception.format_stacktrace_entry(entry)]
end
for entry <- stacktrace do
[<<"\n ">> | Exception.format_stacktrace_entry(entry)]
end
end
defp format_mfa(mod, fun, :undefined), do: [inspect(mod), ?., to_string(fun) | "/?"]
defp format_mfa(mod, fun, args), do: Exception.format_mfa(mod, fun, args)
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
+4 -4
View File
@@ -32,10 +32,10 @@ defmodule Logger.Utils do
end
end
defp truncate_n(int, n) when int in 0..127, do: {int, n-1}
defp truncate_n(int, n) when int in 127..0x07FF, do: {int, n-2}
defp truncate_n(int, n) when int in 0x800..0xFFFF, do: {int, n-3}
defp truncate_n(int, n) when int >= 0x10000 and is_integer(int), do: {int, n-4}
defp truncate_n(int, n) when int in 0..127, do: {int, n - 1}
defp truncate_n(int, n) when int in 127..0x07FF, do: {int, n - 2}
defp truncate_n(int, n) when int in 0x800..0xFFFF, do: {int, n - 3}
defp truncate_n(int, n) when int >= 0x10000 and is_integer(int), do: {int, n - 4}
defp truncate_n(list, n) when is_list(list) do
truncate_n_list(list, n, [])
+103 -9
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)
@@ -382,7 +472,8 @@ defmodule Logger.TranslatorTest do
send(pid, :go)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
Ancestors: \[#PID<\d+\.\d+\.\d+>\]
Ancestors: \[#PID<\d+\.\d+\.\d+>\](?:
Message Queue Length: 1(?#TODO: Require once depend on 20)|)
Messages: \[:message\]
Links: \[\]
Dictionary: \[\]
@@ -403,15 +494,18 @@ defmodule Logger.TranslatorTest do
send(pid, :go)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
Messages: \[\]
Links: \[#PID<\d+\.\d+\.\d+>\]
Dictionary: \[\]
Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\](?:
Message Queue Length: 0|
Messages: \[\](?# TODO: Remove once depend on 20))
Links: \[#PID<\d+\.\d+\.\d+>\](?:|
Dictionary: \[\](?# TODO: Remove once depend on 20))
Trapping Exits: false
Status: :waiting
Heap Size: \d+
Stack Size: \d+
Reductions: \d+
Reductions: \d+(?:
Current Stacktrace:
test/logger/translator_test.exs:\d+: Logger.TranslatorTest.sleep/1(?#TODO: Require once depend on 20)|)
"""
end
+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
+1 -1
View File
@@ -78,7 +78,7 @@ defmodule Mix.Generator do
@file file
File.read!(file)
c when is_binary(c) ->
@file {__ENV__.file, __ENV__.line+1}
@file {__ENV__.file, __ENV__.line + 1}
c
_ ->
raise ArgumentError, "expected string or from_file: file"
+2 -2
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
@@ -255,7 +255,7 @@ defmodule Mix.Local.Installer do
path = Path.join(root, name)
cond do
not File.exists?(path) ->
Mix.shell.error "Could not find a local #{item_name} named #{inspect name}. "<>
Mix.shell.error "Could not find a local #{item_name} named #{inspect name}. " <>
"Existing #{item_plural} are:"
Mix.Task.run item_name
nil
+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
@@ -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.
+4 -4
View File
@@ -119,11 +119,11 @@ defmodule Mix.Tasks.New do
defp otp_app(mod, true) do
" [extra_applications: [:logger],\n mod: {#{mod}.Application, []}]"
end
defp cd_path(".") do
""
end
defp cd_path(path) do
"cd #{path}\n "
end
@@ -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
+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.2"},
{vsn, "1.4.5"},
{modules, [
elixir
]},