Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6d7769674 | ||
|
|
dc3a56c44f | ||
|
|
ea9cb84eb1 | ||
|
|
b7b7282a22 | ||
|
|
d3d1833d47 | ||
|
|
7d427719fe | ||
|
|
9812b74a2c | ||
|
|
aebc7e2fe1 | ||
|
|
f0aeb7136f | ||
|
|
d92a450637 | ||
|
|
afb4667cd0 | ||
|
|
eb50a09679 | ||
|
|
2f8ecec32c | ||
|
|
4354938168 | ||
|
|
b06d82c93e | ||
|
|
3dbe8a6661 | ||
|
|
6932e25159 | ||
|
|
22abd6a48e | ||
|
|
05f5073cd1 | ||
|
|
18d96c2fd5 | ||
|
|
ea04ec8f81 | ||
|
|
0a5d633a5e | ||
|
|
7392d736c4 | ||
|
|
1164784b8e | ||
|
|
6694ddbd2f | ||
|
|
85a4d55efb | ||
|
|
9b238e0316 | ||
|
|
ca82388792 | ||
|
|
b33dd12d86 | ||
|
|
c36b2c9070 | ||
|
|
ac75cdbe05 |
+33
-2
@@ -2,6 +2,21 @@
|
||||
|
||||
Elixir v1.7 is the last release to support Erlang/OTP 19. We recommend everyone to migrate to Erlang/OTP 20+.
|
||||
|
||||
## Documentation metadata
|
||||
|
||||
Elixir v1.7 implements [EEP 48](http://erlang.org/eep/eeps/eep-0048.html). EEP 48 aims to bring documentation interoperability across all languages running on the Erlang VM. The documentation format proposed by EEP 48 also supports metadata, which is now fully exposed to Elixir developers:
|
||||
|
||||
```elixir
|
||||
@moduledoc "A brand new module"
|
||||
@moduledoc authors: ["Jane", "Mary"], since: "1.4.0"
|
||||
```
|
||||
|
||||
Passing metadata is supported on `@doc`, `@moduledoc` and `@typedoc`.
|
||||
|
||||
To access the new documentation format, developers should use `Code.fetch_docs/1`. The old documentation format is no longer available and the old `Code.get_docs/2` function will return `nil` accordingly.
|
||||
|
||||
Tools like IEx and ExDoc have been updated to leverage the new format and show relevant metadata to users. While Elixir allows any metadata to be given, those tools currently exhibit only `:deprecated` and `:since`. Other keys may be shown in the future.
|
||||
|
||||
## The `__STACKTRACE__` construct
|
||||
|
||||
Erlang/OTP 21.0 introduces a new way to retrieve the stacktrace that is lexically scoped and no longer relies on side-effects like `System.stacktrace/0` does. Before one would write:
|
||||
@@ -81,7 +96,7 @@ ExUnit has also seen its own share of improvements. Assertions such as `assert s
|
||||
|
||||
Furthermore, failures in doctests are now colored and diffed.
|
||||
|
||||
On the `mix test` side of things, there is a new `--failed` flag that runs all tests that failed in the previous run. Finally, coverage reports generated with `mix test --cover` include a summary out of the box:
|
||||
On the `mix test` side of things, there is a new `--failed` flag that runs all tests that failed the last time they ran. Finally, coverage reports generated with `mix test --cover` include a summary out of the box:
|
||||
|
||||
```
|
||||
Generating cover results ...
|
||||
@@ -101,7 +116,20 @@ Percentage | Module
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.0-dev
|
||||
## v1.7.0-rc.1 (2018-07-19)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Ensure nil metadata is always pruned
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Properly tag which dependencies are `top_level` when processing child dependencies (regression)
|
||||
* [Mix] Allow calls to `Mix.Config.eval!/2` to be nested (regression)
|
||||
|
||||
## v1.7.0-rc.0 (2018-07-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -119,6 +147,7 @@ Percentage | Module
|
||||
* [Function] Add `Function` module with `capture/3`, `info/1` and `info/2` functions
|
||||
* [GenServer] Support the new `handle_continue/2` callback on Erlang/OTP 21+
|
||||
* [IO.ANSI] Add cursor movement to `IO.ANSI`
|
||||
* [Kernel] Support adding arbitrary documentation metadata by passing a keyword list to `@doc`, `@moduledoc` and `@typedoc`
|
||||
* [Kernel] Introduce `__STACKTRACE__` to retrieve the current stacktrace inside `catch`/`rescue` (this will be a requirement for Erlang/OTP 21+)
|
||||
* [Kernel] Raise on unsafe variables in order to allow us to better track unused variables
|
||||
* [Kernel] Warn when using `length` to check if a list is not empty on guards
|
||||
@@ -146,6 +175,7 @@ Percentage | Module
|
||||
* [IEx.Helpers] Add `use_if_available/2`
|
||||
* [IEx.Helpers] Allow `force: true` option in `recompile/1`
|
||||
* [IEx.Helpers] Add `:allocators` pane to `runtime_info/1`
|
||||
* [IEx.Helpers] Show documentation metadata in `h/1` helpers
|
||||
|
||||
#### Logger
|
||||
|
||||
@@ -179,6 +209,7 @@ Percentage | Module
|
||||
* [Code] Ensure `string_to_quoted` returns error tuples instead of raising in certain constructs
|
||||
* [Code.Formatter] Consistently format keyword lists in function calls with and without parens
|
||||
* [Code.Formatter] Do not break after `->` when there are only comments and one-line clauses
|
||||
* [File] Allow the `:trim_bom` option to be used with `:encoding`
|
||||
* [Kernel] Raise on unsafe variables as some of the code emitted with unsafe variables would not correctly propagate variables or would disable tail call optimization semantics
|
||||
* [Kernel] Do not crash on dynamic sizes in binary generators with collectable into in comprehensions
|
||||
* [Kernel] Do not crash on literals with non-unary size in binary generators with collectable into in comprehensions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
CANONICAL := master/
|
||||
CANONICAL := v1.7/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -192,6 +192,14 @@ Precompiled.zip: build_man compile
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
@ echo ""
|
||||
@ echo "## Checksums"
|
||||
@ echo ""
|
||||
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ echo ""
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
DateTime,
|
||||
Exception,
|
||||
Float,
|
||||
Function,
|
||||
Integer,
|
||||
NaiveDateTime,
|
||||
Record,
|
||||
|
||||
@@ -404,6 +404,41 @@ defmodule Application do
|
||||
|
||||
If the configuration parameter does not exist, the function returns the
|
||||
`default` value.
|
||||
|
||||
## Examples
|
||||
|
||||
`get_env/3` is commonly used to read the configuration of your OTP applications.
|
||||
Since Mix configurations are commonly used to configure applications, we will use
|
||||
this as a point of illustration.
|
||||
|
||||
Consider a new application `:my_app`. `:my_app` contains a database engine which
|
||||
supports a pool of databases. The database engine needs to know the configuration for
|
||||
each of those databases, and that configuration is supplied by key-value pairs in
|
||||
environment of `:my_app`.
|
||||
|
||||
config :my_app, Databases.RepoOne,
|
||||
# A database configuration
|
||||
ip: "localhost"
|
||||
port: 5433
|
||||
|
||||
config :my_app, Databases.RepoTwo,
|
||||
# Another database configuration (for the same OTP app)
|
||||
ip: "localhost"
|
||||
port: 20717
|
||||
|
||||
config :my_app, my_app_databases: [Databases.RepoOne, Databases.RepoTwo]
|
||||
|
||||
Our database engine used by `:my_app` needs to know what databases exist, and
|
||||
what the database configurations are. The database engine can make a call to
|
||||
`get_env(:my_app, :my_app_databases)` to retrieve the list of databases (specified
|
||||
by module names). Our database engine can then traverse each repository in the
|
||||
list and then call `get_env(:my_app, Databases.RepoOne)` and so forth to retrieve
|
||||
the configuration of each one.
|
||||
|
||||
**Important:** if you are writing a library to be used by other developers,
|
||||
it is generally recommended to avoid the application environment, as the
|
||||
application environment is effectively a global storage. For more information,
|
||||
read our [library guidelines](/library-guidelines.html).
|
||||
"""
|
||||
@spec get_env(app, key, value) :: value
|
||||
def get_env(app, key, default \\ nil) do
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule DateTime do
|
||||
Such functions expect `t:Calendar.datetime/0` in their typespecs
|
||||
(instead of `t:t/0`).
|
||||
|
||||
Developers should avoid creating the DateTime struct directly
|
||||
Developers should avoid creating the `DateTime` struct directly
|
||||
and instead rely on the functions provided by this module as
|
||||
well as the ones in 3rd party calendar libraries.
|
||||
|
||||
@@ -25,12 +25,12 @@ defmodule DateTime do
|
||||
|
||||
You will notice this module only contains conversion
|
||||
functions as well as functions that work on UTC. This
|
||||
is because a proper DateTime implementation requires a
|
||||
TimeZone database which currently is not provided as part
|
||||
is because a proper `DateTime` implementation requires a
|
||||
time zone database which currently is not provided as part
|
||||
of Elixir.
|
||||
|
||||
Such may be addressed in upcoming versions, meanwhile,
|
||||
use 3rd party packages to provide DateTime building and
|
||||
use 3rd party packages to provide `DateTime` building and
|
||||
similar functionality with time zone backing.
|
||||
"""
|
||||
|
||||
|
||||
@@ -351,12 +351,12 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
"2015-02-28"
|
||||
iex> Calendar.ISO.date_to_string(2017, 8, 1)
|
||||
"2017-08-01"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31)
|
||||
"-0099-01-31"
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
"2015-02-28"
|
||||
iex> Calendar.ISO.date_to_string(2017, 8, 1)
|
||||
"2017-08-01"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31)
|
||||
"-0099-01-31"
|
||||
|
||||
"""
|
||||
@spec date_to_string(year, month, day) :: String.t()
|
||||
@@ -378,10 +378,10 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
"2015-02-28 01:02:03.000004"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
"2015-02-28 01:02:03.000004"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@@ -403,14 +403,14 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
|
||||
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
|
||||
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
|
||||
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
|
||||
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
|
||||
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
|
||||
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@@ -452,14 +452,14 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 28)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 30)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 31)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 32)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 28)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 30)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 31)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 32)
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@@ -476,12 +476,12 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
|
||||
false
|
||||
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
|
||||
@@ -412,7 +412,7 @@ defmodule NaiveDateTime do
|
||||
Parses the extended "Date and time of day" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Timezone offset may be included in the string but they will be
|
||||
Time zone offset may be included in the string but they will be
|
||||
simply discarded as such information is not included in naive date
|
||||
times.
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ defmodule Time do
|
||||
Parses the extended "Local time" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Timezone offset may be included in the string but they will be
|
||||
Time zone offset may be included in the string but they will be
|
||||
simply discarded as such information is not included in times.
|
||||
|
||||
As specified in the standard, the separator "T" may be omitted if
|
||||
|
||||
@@ -716,14 +716,21 @@ defmodule Code.Formatter do
|
||||
{concat(op_string, doc), @empty, newlines, state}
|
||||
end
|
||||
|
||||
operand_to_algebra_with_comments(
|
||||
operands,
|
||||
meta,
|
||||
min_line,
|
||||
max_line,
|
||||
state,
|
||||
operand_to_algebra
|
||||
)
|
||||
{doc, state} =
|
||||
operand_to_algebra_with_comments(
|
||||
operands,
|
||||
meta,
|
||||
min_line,
|
||||
max_line,
|
||||
state,
|
||||
operand_to_algebra
|
||||
)
|
||||
|
||||
if keyword?(right_arg) and context in [:parens_arg, :no_parens_arg] do
|
||||
{wrap_in_parens(doc), state}
|
||||
else
|
||||
{doc, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp binary_op_to_algebra(op, _, meta, left_arg, right_arg, context, state, _nesting)
|
||||
|
||||
@@ -1512,7 +1512,8 @@ defmodule File do
|
||||
in raw mode for performance reasons. Therefore, Elixir **will** open
|
||||
streams in `:raw` mode with the `:read_ahead` option unless an encoding
|
||||
is specified. This means any data streamed into the file must be
|
||||
converted to `t:iodata/0` type. If you pass `[:utf8]` in the modes parameter,
|
||||
converted to `t:iodata/0` type. If you pass e.g. `[encoding: :utf8]`
|
||||
or `[encoding: {:utf16, :little}]` in the modes parameter,
|
||||
the underlying stream will use `IO.write/2` and the `String.Chars` protocol
|
||||
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
|
||||
|
||||
@@ -1524,6 +1525,9 @@ defmodule File do
|
||||
If you pass `:trim_bom` in the modes parameter, the stream will
|
||||
trim UTF-8, UTF-16 and UTF-32 byte order marks when reading from file.
|
||||
|
||||
Note that this function does not try to discover the file encoding basing
|
||||
on BOM.
|
||||
|
||||
## Examples
|
||||
|
||||
# Read in 2048 byte chunks rather than lines
|
||||
|
||||
@@ -140,12 +140,24 @@ defmodule File.Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_bom(device, raw) do
|
||||
header = if raw, do: IO.binread(device, 4), else: IO.read(device, 1)
|
||||
{:ok, new_pos} = :file.position(device, bom_length(header))
|
||||
defp trim_bom(device, true) do
|
||||
bom_length = device |> IO.binread(4) |> bom_length()
|
||||
{:ok, new_pos} = :file.position(device, bom_length)
|
||||
{device, new_pos}
|
||||
end
|
||||
|
||||
defp trim_bom(device, false) do
|
||||
# Or we read the bom in the correct amount or it isn't there
|
||||
case bom_length(IO.read(device, 1)) do
|
||||
0 ->
|
||||
{:ok, _} = :file.position(device, 0)
|
||||
{device, 0}
|
||||
|
||||
_ ->
|
||||
{device, 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp bom_length(<<239, 187, 191, _rest::binary>>), do: 3
|
||||
defp bom_length(<<254, 255, _rest::binary>>), do: 2
|
||||
defp bom_length(<<255, 254, _rest::binary>>), do: 2
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Float do
|
||||
There are also other known problems like flooring or rounding numbers. See
|
||||
`round/2` and `floor/2` for more details about them.
|
||||
|
||||
To learn more about floting-point arithmetic visit:
|
||||
To learn more about floating-point arithmetic visit:
|
||||
|
||||
* [0.30000000000000004.com](http://0.30000000000000004.com/)
|
||||
* [What Every Programmer Should Know About Floating-Point Arithmetic](http://floating-point-gui.de/)
|
||||
|
||||
@@ -7,12 +7,13 @@ defmodule IO.ANSI.Docs do
|
||||
@doc """
|
||||
The default options used by this module.
|
||||
|
||||
The supported values are:
|
||||
The supported keys are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan)
|
||||
* `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow)
|
||||
* `:doc_metadata` - documentation metadata keys (yellow)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - the style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow)
|
||||
@@ -29,6 +30,7 @@ defmodule IO.ANSI.Docs do
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan],
|
||||
doc_headings: [:yellow],
|
||||
doc_metadata: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
@@ -53,6 +55,39 @@ defmodule IO.ANSI.Docs do
|
||||
newline_after_block()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints documentation metadata (only `since` and `deprecated` for now).
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
@spec print_metadata(map, keyword) :: :ok
|
||||
def print_metadata(metadata, options \\ []) when is_map(metadata) do
|
||||
options = Keyword.merge(default_options(), options)
|
||||
print_each_metadata(metadata, options) && IO.write("\n")
|
||||
end
|
||||
|
||||
@metadata_filter [:deprecated, :since]
|
||||
|
||||
defp print_each_metadata(metadata, options) do
|
||||
Enum.reduce(metadata, false, fn
|
||||
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
|
||||
label = metadata_label(key, options)
|
||||
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
|
||||
write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true)
|
||||
|
||||
_metadata, printed ->
|
||||
printed
|
||||
end)
|
||||
end
|
||||
|
||||
defp metadata_label(key, options) do
|
||||
if options[:enabled] do
|
||||
"#{color(:doc_metadata, options)}#{key}:#{IO.ANSI.reset()}"
|
||||
else
|
||||
"#{key}:"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule Kernel do
|
||||
If you don't want to import a function or macro from `Kernel`, use the `:except`
|
||||
option and then list the function/macro by arity:
|
||||
|
||||
import Kernel, except: [if: 2, unless: 2]
|
||||
import Kernel, except: [if: 2, unless: 2]
|
||||
|
||||
See `Kernel.SpecialForms.import/2` for more information on importing.
|
||||
|
||||
@@ -70,10 +70,10 @@ defmodule Kernel do
|
||||
listed above. Some of them are:
|
||||
|
||||
* `Date` - `year-month-day` structs in a given calendar
|
||||
* `DateTime` - date and time with timezone in a given calendar
|
||||
* `DateTime` - date and time with time zone in a given calendar
|
||||
* `Exception` - data raised from errors and unexpected scenarios
|
||||
* `MapSet` - unordered collections of unique elements
|
||||
* `NaiveDateTime` - date and time without timezone in a given calendar
|
||||
* `NaiveDateTime` - date and time without time zone in a given calendar
|
||||
* `Keyword` - lists of two-element tuples, often representing optional values
|
||||
* `Range` - inclusive ranges between two integers
|
||||
* `Regex` - regular expressions
|
||||
|
||||
@@ -506,7 +506,6 @@ defmodule Map do
|
||||
iex> Map.delete(%{b: 2}, :a)
|
||||
%{b: 2}
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec delete(map, key) :: map
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
@@ -466,7 +466,8 @@ defmodule Module do
|
||||
of the corresponding setting in `Code.compiler_options/1`
|
||||
|
||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||
name/arity pairs
|
||||
name/arity pairs. Inlining is applied locally, calls from another
|
||||
module are not affected by this option
|
||||
|
||||
* `@compile {:autoload, false}` - disables automatic loading of
|
||||
modules after compilation. Instead, the module will be loaded after
|
||||
|
||||
@@ -2240,6 +2240,8 @@ defmodule String do
|
||||
0.8222222222222223
|
||||
iex> String.jaro_distance("even", "odd")
|
||||
0.0
|
||||
iex> String.jaro_distance("same", "same")
|
||||
1.0
|
||||
|
||||
"""
|
||||
@spec jaro_distance(t, t) :: float
|
||||
|
||||
+13
-12
@@ -31,21 +31,22 @@ defmodule StringIO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> StringIO.open("foo", [], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", "The input was foo"}}
|
||||
iex> StringIO.open("foo", [], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", "The input was foo"}}
|
||||
|
||||
iex> StringIO.open("foo", [capture_prompt: true], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", ">The input was foo"}}
|
||||
iex> StringIO.open("foo", [capture_prompt: true], fn(pid) ->
|
||||
...> input = IO.gets(pid, ">")
|
||||
...> IO.write(pid, "The input was #{input}")
|
||||
...> StringIO.contents(pid)
|
||||
...> end)
|
||||
{:ok, {"", ">The input was foo"}}
|
||||
|
||||
"""
|
||||
@doc since: "1.7.0"
|
||||
@spec open(binary, keyword, (pid -> res)) :: {:ok, res} when res: var
|
||||
def open(string, options, function)
|
||||
when is_binary(string) and is_list(options) and is_function(function, 1) do
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule Tuple do
|
||||
|
||||
* `elem/2` - access a tuple by index
|
||||
* `put_elem/3` - insert a value into a tuple by index
|
||||
* `tuple_size/3` - get the number of elements in a tuple
|
||||
* `tuple_size/1` - get the number of elements in a tuple
|
||||
|
||||
Tuples are intended as fixed-size containers for multiple elements.
|
||||
To manipulate a collection of elements, use a list instead. `Enum`
|
||||
|
||||
@@ -136,4 +136,4 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
|
||||
[v1.4]: https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md#4-deprecations
|
||||
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
|
||||
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
@@ -447,6 +447,20 @@ defmodule Code.Formatter.IntegrationTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "when with keywords inside call" do
|
||||
assert_same """
|
||||
quote((bar(foo(1)) when bat: foo(1)), [])
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
quote(do: (bar(foo(1)) when bat: foo(1)), line: 1)
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
typespec(quote(do: (bar(foo(1)) when bat: foo(1))), [foo: 1], [])
|
||||
"""
|
||||
end
|
||||
|
||||
test "capture with operators" do
|
||||
assert_same """
|
||||
"this works" |> (&String.upcase/1) |> (&String.downcase/1)
|
||||
|
||||
@@ -1569,11 +1569,11 @@ defmodule FileTest do
|
||||
src = fixture_path("utf8_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8])
|
||||
|> File.stream!([{:encoding, :utf8}])
|
||||
|> Enum.take(1) == [<<239, 187, 191>> <> "Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8], 1)
|
||||
|> File.stream!([{:encoding, :utf8}], 1)
|
||||
|> Enum.take(9) == ["\uFEFF", "Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
@@ -1581,11 +1581,51 @@ defmodule FileTest do
|
||||
src = fixture_path("utf8_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8, :trim_bom])
|
||||
|> File.stream!([{:encoding, :utf8}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8, :trim_bom], 1)
|
||||
|> File.stream!([{:encoding, :utf8}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
test "keeps BOM with UTF16 BE" do
|
||||
src = fixture_path("utf16_be_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}])
|
||||
|> Enum.take(1) == ["\uFEFFРусский\n"]
|
||||
end
|
||||
|
||||
test "keeps BOM with UTF16 LE" do
|
||||
src = fixture_path("utf16_le_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}])
|
||||
|> Enum.take(1) == ["\uFEFFРусский\n"]
|
||||
end
|
||||
|
||||
test "trims BOM via option with utf16 BE encoding" do
|
||||
src = fixture_path("utf16_be_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
test "trims BOM via option with utf16 LE encoding" do
|
||||
src = fixture_path("utf16_le_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,10 @@ defmodule IO.ANSI.DocsTest do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.trim_trailing()
|
||||
end
|
||||
|
||||
def format_metadata(map) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_metadata(map, []) end)
|
||||
end
|
||||
|
||||
def format(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.trim_trailing()
|
||||
end
|
||||
@@ -19,6 +23,12 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert String.contains?(result, " wibble ")
|
||||
end
|
||||
|
||||
test "metadata is formatted" do
|
||||
result = format_metadata(%{since: "1.2.3", deprecated: "Use that other one", author: "Alice"})
|
||||
assert result == "\e[33mdeprecated:\e[0m Use that other one\n\e[33msince:\e[0m 1.2.3\n\n"
|
||||
assert format_metadata(%{author: "Alice"}) == ""
|
||||
end
|
||||
|
||||
test "first level heading is converted" do
|
||||
result = format("# wibble\n\ntext\n")
|
||||
assert result == "\e[33m# wibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
|
||||
@@ -745,6 +745,7 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
@doc """
|
||||
Asserts `expression` will throw a value.
|
||||
|
||||
Returns the thrown value or fails otherwise.
|
||||
|
||||
## Examples
|
||||
@@ -758,11 +759,19 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
@doc """
|
||||
Asserts `expression` will exit.
|
||||
Returns the exit status/message or fails otherwise.
|
||||
|
||||
Returns the exit status/message of the current process or fails otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
assert catch_exit(exit 1) == 1
|
||||
|
||||
To assert exits from linked processes started from the test, trap exits
|
||||
with `Process.flag/2` and assert the exit message with `assert_received/2`.
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
pid = spawn_link(fn -> Process.exit(self(), :normal) end)
|
||||
assert_receive {:EXIT, ^pid, :normal}
|
||||
|
||||
"""
|
||||
defmacro catch_exit(expression) do
|
||||
@@ -771,6 +780,7 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
@doc """
|
||||
Asserts `expression` will cause an error.
|
||||
|
||||
Returns the error or fails otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -338,7 +338,7 @@ defimpl IEx.Info, for: [Date, Time, NaiveDateTime] do
|
||||
case @for do
|
||||
Date -> {"D", "date"}
|
||||
Time -> {"T", "time"}
|
||||
NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a timezone)}}
|
||||
NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a time zone)}}
|
||||
end
|
||||
|
||||
def info(value) do
|
||||
|
||||
@@ -250,8 +250,8 @@ defmodule IEx.Introspection do
|
||||
case Code.ensure_loaded(module) do
|
||||
{:module, _} ->
|
||||
case Code.fetch_docs(module) do
|
||||
{:docs_v1, _, _, _, %{} = doc, _, _} ->
|
||||
print_doc(inspect(module), [], doc)
|
||||
{:docs_v1, _, _, _, %{} = doc, metadata, _} ->
|
||||
print_doc(inspect(module), [], doc, metadata)
|
||||
|
||||
{:docs_v1, _, _, _, _, _, _} ->
|
||||
docs_not_found(inspect(module))
|
||||
@@ -376,7 +376,7 @@ defmodule IEx.Introspection do
|
||||
|
||||
is_nil(docs) and spec != [] ->
|
||||
message = %{"en" => "Module was compiled without docs. Showing only specs."}
|
||||
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message)
|
||||
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message, %{})
|
||||
:ok
|
||||
|
||||
is_nil(docs) ->
|
||||
@@ -449,7 +449,7 @@ defmodule IEx.Introspection do
|
||||
defp has_content?({{_, name, _}, _, _, :none, _}), do: hd(Atom.to_charlist(name)) != ?_
|
||||
defp has_content?({_, _, _, _, _}), do: true
|
||||
|
||||
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, _meta}, spec) do
|
||||
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, metadata}, spec) do
|
||||
if callback_module = doc == :none and callback_module(mod, fun, arity) do
|
||||
filter = &match?({_, ^fun, ^arity}, elem(&1, 0))
|
||||
|
||||
@@ -458,7 +458,7 @@ defmodule IEx.Introspection do
|
||||
_ -> nil
|
||||
end
|
||||
else
|
||||
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc)
|
||||
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -513,7 +513,7 @@ defmodule IEx.Introspection do
|
||||
{:ok, docs} ->
|
||||
docs
|
||||
|> add_optional_callback_docs(mod)
|
||||
|> Enum.each(fn {definition, _} -> IO.puts(definition) end)
|
||||
|> Enum.each(fn {definition, _, _} -> IO.puts(definition) end)
|
||||
end
|
||||
|
||||
dont_display_result()
|
||||
@@ -565,12 +565,12 @@ defmodule IEx.Introspection do
|
||||
docs
|
||||
|> Enum.filter(filter)
|
||||
|> Enum.map(fn
|
||||
{{:macrocallback, fun, arity}, _, _, doc, _} ->
|
||||
{{:macrocallback, fun, arity}, _, _, doc, metadata} ->
|
||||
macro = {:"MACRO-#{fun}", arity + 1}
|
||||
{format_callback(:macrocallback, fun, macro, callbacks), doc}
|
||||
{format_callback(:macrocallback, fun, macro, callbacks), doc, metadata}
|
||||
|
||||
{{kind, fun, arity}, _, _, doc, _} ->
|
||||
{format_callback(kind, fun, {fun, arity}, callbacks), doc}
|
||||
{{kind, fun, arity}, _, _, doc, metadata} ->
|
||||
{format_callback(kind, fun, {fun, arity}, callbacks), doc, metadata}
|
||||
end)
|
||||
|
||||
{:ok, docs}
|
||||
@@ -588,7 +588,7 @@ defmodule IEx.Introspection do
|
||||
if optional_callbacks == [] do
|
||||
docs
|
||||
else
|
||||
docs ++ [{format_optional_callbacks(optional_callbacks), ""}]
|
||||
docs ++ [{format_optional_callbacks(optional_callbacks), "", %{}}]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -637,8 +637,8 @@ defmodule IEx.Introspection do
|
||||
{:ok, types} ->
|
||||
printed =
|
||||
for {_, {^type, _, args}} = typespec <- types do
|
||||
doc = {format_type(typespec), type_doc(module, type, length(args))}
|
||||
print_typespec(doc)
|
||||
type_doc(module, type, length(args), typespec)
|
||||
|> print_typespec()
|
||||
end
|
||||
|
||||
if printed == [] do
|
||||
@@ -657,8 +657,8 @@ defmodule IEx.Introspection do
|
||||
{:ok, types} ->
|
||||
printed =
|
||||
for {_, {^type, _, args}} = typespec <- types, length(args) == arity do
|
||||
doc = {format_type(typespec), type_doc(module, type, arity)}
|
||||
print_typespec(doc)
|
||||
type_doc(module, type, arity, typespec)
|
||||
|> print_typespec()
|
||||
end
|
||||
|
||||
if printed == [] do
|
||||
@@ -674,12 +674,12 @@ defmodule IEx.Introspection do
|
||||
dont_display_result()
|
||||
end
|
||||
|
||||
defp type_doc(module, type, arity) do
|
||||
defp type_doc(module, type, arity, typespec) do
|
||||
if docs = get_docs(module, [:type]) do
|
||||
{_, _, _, content, _} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
|
||||
content
|
||||
{_, _, _, content, metadata} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
|
||||
{format_type(typespec), content, metadata}
|
||||
else
|
||||
:none
|
||||
{format_type(typespec), :none, %{}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -717,27 +717,31 @@ defmodule IEx.Introspection do
|
||||
IEx.color(:doc_inline_code, left) <> " " <> right
|
||||
end
|
||||
|
||||
defp print_doc(heading, types, doc) do
|
||||
defp print_doc(heading, types, doc, metadata) do
|
||||
doc = translate_doc(doc) || ""
|
||||
|
||||
if opts = IEx.Config.ansi_docs() do
|
||||
IO.ANSI.Docs.print_heading(heading, opts)
|
||||
IO.write(types)
|
||||
IO.ANSI.Docs.print_metadata(metadata, opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.puts("* #{heading}\n")
|
||||
IO.write(types)
|
||||
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
|
||||
IO.puts(doc)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_typespec({types, doc}) do
|
||||
defp print_typespec({types, doc, metadata}) do
|
||||
IO.puts(types)
|
||||
doc = translate_doc(doc)
|
||||
|
||||
if opts = IEx.Config.ansi_docs() do
|
||||
IO.ANSI.Docs.print_metadata(metadata, opts)
|
||||
doc && IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
|
||||
doc && IO.puts(doc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -403,6 +403,38 @@ defmodule IEx.HelpersTest do
|
||||
"No documentation for Kernel.__info__ was found\n"
|
||||
end
|
||||
|
||||
test "prints documentation metadata" do
|
||||
content = """
|
||||
defmodule Sample do
|
||||
@moduledoc "Sample module"
|
||||
@moduledoc deprecated: "Use OtherSample", since: "1.2.3", authors: ["Alice", "Bob"]
|
||||
@doc "With metadata"
|
||||
@doc since: "1.2.3", author: "Alice"
|
||||
@deprecated "Use OtherSample.with_metadata/0"
|
||||
def with_metadata(), do: 0
|
||||
@doc "Without metadata"
|
||||
def without_metadata(), do: 1
|
||||
end
|
||||
"""
|
||||
|
||||
filename = "sample.ex"
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [Sample]
|
||||
|
||||
assert capture_io(fn -> h(Sample) end) ==
|
||||
"* Sample\n\ndeprecated: Use OtherSample\nsince: 1.2.3\n\nSample module\n"
|
||||
|
||||
assert capture_io(fn -> h(Sample.with_metadata()) end) ==
|
||||
"* def with_metadata()\n\ndeprecated: Use OtherSample.with_metadata/0\nsince: 1.2.3\n\nWith metadata\n"
|
||||
|
||||
assert capture_io(fn -> h(Sample.without_metadata()) end) ==
|
||||
"* def without_metadata()\n\nWithout metadata\n"
|
||||
end)
|
||||
after
|
||||
cleanup_modules([Sample])
|
||||
end
|
||||
|
||||
test "considers underscored functions without docs by default" do
|
||||
content = """
|
||||
defmodule Sample do
|
||||
@@ -593,6 +625,27 @@ defmodule IEx.HelpersTest do
|
||||
"@callback message(t()) :: String.t()\n\n"
|
||||
end
|
||||
|
||||
test "prints callback documentation metadata" do
|
||||
filename = "callback_with_metadata.ex"
|
||||
|
||||
content = """
|
||||
defmodule CallbackWithMetadata do
|
||||
@doc "callback"
|
||||
@doc since: "1.2.3", deprecated: "Use handle_test/1", purpose: :test
|
||||
@callback test(:foo) :: integer
|
||||
end
|
||||
"""
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [CallbackWithMetadata]
|
||||
|
||||
assert capture_io(fn -> b(CallbackWithMetadata.test()) end) ==
|
||||
"@callback test(:foo) :: integer()\n\ndeprecated: Use handle_test/1\nsince: 1.2.3\n\ncallback\n"
|
||||
end)
|
||||
after
|
||||
cleanup_modules([CallbackWithMetadata])
|
||||
end
|
||||
|
||||
test "prints optional callback" do
|
||||
filename = "optional_callbacks.ex"
|
||||
|
||||
@@ -666,6 +719,33 @@ defmodule IEx.HelpersTest do
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
end
|
||||
|
||||
test "prints type documentation metadata" do
|
||||
content = """
|
||||
defmodule TypeSample do
|
||||
@typedoc "An id with description."
|
||||
@typedoc since: "1.2.3", deprecated: "Use t/0", purpose: :test
|
||||
@type id_with_desc :: {number, String.t}
|
||||
end
|
||||
"""
|
||||
|
||||
filename = "typesample.ex"
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [TypeSample]
|
||||
|
||||
assert capture_io(fn -> t(TypeSample.id_with_desc()) end) == """
|
||||
@type id_with_desc() :: {number(), String.t()}
|
||||
|
||||
deprecated: Use t/0
|
||||
since: 1.2.3
|
||||
|
||||
An id with description.
|
||||
"""
|
||||
end)
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
end
|
||||
end
|
||||
|
||||
describe "v" do
|
||||
|
||||
@@ -194,7 +194,7 @@ defmodule IEx.InfoTest do
|
||||
assert get_key(info, "Reference modules") == "NaiveDateTime, Calendar, Map"
|
||||
|
||||
assert get_key(info, "Description") =~
|
||||
~S{a "naive" datetime (that is, a datetime without a timezone)}
|
||||
~S{a "naive" datetime (that is, a datetime without a time zone)}
|
||||
|
||||
assert get_key(info, "Description") =~ "`~N`"
|
||||
end
|
||||
|
||||
+19
-11
@@ -416,7 +416,7 @@ defmodule Logger do
|
||||
@type backend :: :gen_event.handler()
|
||||
@type message :: IO.chardata() | String.Chars.t()
|
||||
@type level :: :error | :info | :warn | :debug
|
||||
@type metadata :: keyword(String.Chars.t())
|
||||
@type metadata :: keyword()
|
||||
@levels [:error, :info, :warn, :debug]
|
||||
|
||||
@metadata :logger_metadata
|
||||
@@ -436,17 +436,25 @@ defmodule Logger do
|
||||
@spec metadata(metadata) :: :ok
|
||||
def metadata(keyword) do
|
||||
{enabled?, metadata} = __metadata__()
|
||||
|
||||
metadata =
|
||||
Enum.reduce(keyword, metadata, fn
|
||||
{key, nil}, acc -> Keyword.delete(acc, key)
|
||||
{key, val}, acc -> Keyword.put(acc, key, val)
|
||||
end)
|
||||
|
||||
Process.put(@metadata, {enabled?, metadata})
|
||||
Process.put(@metadata, {enabled?, into_metadata(keyword, metadata)})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp into_metadata([], metadata), do: metadata
|
||||
defp into_metadata(keyword, metadata), do: into_metadata(keyword, [], metadata)
|
||||
|
||||
defp into_metadata([{key, nil} | keyword], prepend, metadata) do
|
||||
into_metadata(keyword, prepend, :lists.keydelete(key, 1, metadata))
|
||||
end
|
||||
|
||||
defp into_metadata([{key, _} = pair | keyword], prepend, metadata) do
|
||||
into_metadata(keyword, [pair | prepend], :lists.keydelete(key, 1, metadata))
|
||||
end
|
||||
|
||||
defp into_metadata([], prepend, metadata) do
|
||||
prepend ++ metadata
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the current process metadata.
|
||||
"""
|
||||
@@ -671,7 +679,7 @@ defmodule Logger do
|
||||
@doc false
|
||||
def __do_log__({level, config, pdict}, chardata_or_fun, metadata) when is_list(metadata) do
|
||||
%{utc_log: utc_log?, truncate: truncate, mode: mode} = config
|
||||
metadata = [pid: self()] ++ Keyword.merge(pdict, metadata)
|
||||
metadata = [pid: self()] ++ into_metadata(metadata, pdict)
|
||||
|
||||
case normalize_message(chardata_or_fun, metadata) do
|
||||
{message, metadata} ->
|
||||
@@ -851,7 +859,7 @@ defmodule Logger do
|
||||
|
||||
defp normalize_message(fun, metadata) when is_function(fun, 0) do
|
||||
case fun.() do
|
||||
{message, fun_metadata} -> {message, Keyword.merge(metadata, fun_metadata)}
|
||||
{message, fun_metadata} -> {message, into_metadata(fun_metadata, metadata)}
|
||||
:skip -> :skip
|
||||
message -> {message, metadata}
|
||||
end
|
||||
|
||||
@@ -339,6 +339,10 @@ defmodule Logger.Translator do
|
||||
["\nStart Module: ", inspect(mod) | child_debug(min_level, debug)]
|
||||
end
|
||||
|
||||
defp child_info(_min_level, _child) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
|
||||
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
|
||||
["\nType: ", inspect(type)]
|
||||
|
||||
@@ -109,7 +109,7 @@ defmodule LoggerTest do
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert Logger.bare_log(:info, "ok", application: nil, module: LoggerTest) == :ok
|
||||
end) =~ msg("application= module=LoggerTest [info] ok")
|
||||
end) =~ msg("module=LoggerTest [info] ok")
|
||||
end
|
||||
|
||||
test "metadata compile-time merge" do
|
||||
@@ -117,7 +117,7 @@ defmodule LoggerTest do
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert Logger.log(:info, "ok", application: nil, module: CustomTest) == :ok
|
||||
end) =~ msg("application= module=CustomTest [info] ok")
|
||||
end) =~ msg("module=CustomTest [info] ok")
|
||||
end
|
||||
|
||||
test "metadata merge when the argument function returns metadata" do
|
||||
@@ -127,7 +127,7 @@ defmodule LoggerTest do
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert Logger.bare_log(:info, fun, application: nil, module: LoggerTest) == :ok
|
||||
end) =~ msg("application= module=Function [info] ok")
|
||||
end) =~ msg("module=Function [info] ok")
|
||||
end
|
||||
|
||||
test "enable/1 and disable/1" do
|
||||
|
||||
+14
-13
@@ -47,7 +47,6 @@ defmodule Mix.Config do
|
||||
|
||||
defp put_config(value) do
|
||||
Process.put(@config_key, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_config() do
|
||||
@@ -60,7 +59,6 @@ defmodule Mix.Config do
|
||||
|
||||
defp put_files(value) do
|
||||
Process.put(@files_key, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_files() do
|
||||
@@ -215,20 +213,23 @@ defmodule Mix.Config do
|
||||
It returns a tuple with the configuration and the imported paths.
|
||||
"""
|
||||
def eval!(file, imported_paths \\ []) do
|
||||
put_config([])
|
||||
put_files(imported_paths)
|
||||
{eval_config, _} = eval_config!(Path.expand(file))
|
||||
previous_config = put_config([])
|
||||
previous_files = put_files(imported_paths)
|
||||
|
||||
case get_config!() do
|
||||
[] when is_list(eval_config) ->
|
||||
{validate!(eval_config), get_files!()}
|
||||
try do
|
||||
{eval_config, _} = eval_config!(Path.expand(file))
|
||||
|
||||
pdict_config ->
|
||||
{pdict_config, get_files!()}
|
||||
case get_config!() do
|
||||
[] when is_list(eval_config) ->
|
||||
{validate!(eval_config), get_files!()}
|
||||
|
||||
pdict_config ->
|
||||
{pdict_config, get_files!()}
|
||||
end
|
||||
after
|
||||
if previous_config, do: put_config(previous_config), else: delete_config()
|
||||
if previous_files, do: put_files(previous_files), else: delete_files()
|
||||
end
|
||||
after
|
||||
delete_config()
|
||||
delete_files()
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -123,9 +123,12 @@ defmodule Mix.Dep do
|
||||
|
||||
defp load_and_cache(config, _top, bottom, _env) do
|
||||
{_, deps} = Mix.ProjectStack.read_cache({:cached_deps, bottom})
|
||||
apps = [Keyword.fetch!(config, :app)]
|
||||
seen = populate_seen(MapSet.new(), apps)
|
||||
get_deps(deps, tl(Enum.uniq(get_children(deps, seen, apps))))
|
||||
app = Keyword.fetch!(config, :app)
|
||||
top_level = for dep <- deps, dep.app == app, child <- dep.deps, do: child.app
|
||||
|
||||
seen = populate_seen(MapSet.new(), [app])
|
||||
children = get_deps(deps, tl(Enum.uniq(get_children(deps, seen, [app]))))
|
||||
Enum.map(children, &%{&1 | top_level: &1.app in top_level})
|
||||
end
|
||||
|
||||
defp read_cached_deps(project, env) do
|
||||
@@ -153,8 +156,8 @@ defmodule Mix.Dep do
|
||||
# TODO: Remove this on v1.8
|
||||
@doc false
|
||||
@deprecated "Mix.Dep.loaded/1 was private API and you should not use it"
|
||||
def loaded([]) do
|
||||
load_on_environment([])
|
||||
def loaded(opts) do
|
||||
load_on_environment(opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -145,7 +145,7 @@ defmodule Mix.Tasks.Test do
|
||||
## Command line options
|
||||
|
||||
* `--color` - enables color in the output
|
||||
* `--cover` - the directory to include coverage results
|
||||
* `--cover` - runs coverage tool. See "Coverage" section below
|
||||
* `--exclude` - excludes tests that match the filter
|
||||
* `--force` - forces compilation regardless of modification times
|
||||
* `--formatter` - formatter module
|
||||
@@ -256,7 +256,13 @@ defmodule Mix.Tasks.Test do
|
||||
By default, a very simple wrapper around OTP's `cover` is used as a tool,
|
||||
but it can be overridden as follows:
|
||||
|
||||
test_coverage: [tool: CoverModule]
|
||||
def project() do
|
||||
[
|
||||
...
|
||||
test_coverage: [tool: CoverModule]
|
||||
...
|
||||
]
|
||||
end
|
||||
|
||||
`CoverModule` can be any module that exports `start/2`, receiving the
|
||||
compilation path and the `test_coverage` options as arguments.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
use Mix.Config
|
||||
Mix.Config.eval!(Path.join([__DIR__, "good_config.exs"]))
|
||||
:done
|
||||
@@ -107,6 +107,9 @@ defmodule Mix.ConfigTest do
|
||||
{[my_app: [key: :value]],
|
||||
[fixture_path("configs/good_config.exs"), fixture_path("configs/good_import.exs")]}
|
||||
|
||||
assert Mix.Config.eval!(fixture_path("configs/nested_import.exs")) ==
|
||||
{[], [fixture_path("configs/nested_import.exs")]}
|
||||
|
||||
assert_raise ArgumentError,
|
||||
~r"expected runtime config for app :sample to return keyword list",
|
||||
fn -> Mix.Config.eval!(fixture_path("configs/bad_app.exs")) end
|
||||
|
||||
@@ -488,6 +488,27 @@ defmodule Mix.DepTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with no overrides" do
|
||||
# deps_repo brings git_repo but it is overriden
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"}
|
||||
]
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
File.mkdir_p!("custom/deps_repo/lib")
|
||||
|
||||
File.write!("custom/deps_repo/lib/a.ex", """
|
||||
# Check that the child dependency is top_level
|
||||
[%Mix.Dep{app: :git_repo, top_level: true}] = Mix.Dep.cached()
|
||||
""")
|
||||
|
||||
Mix.Tasks.Deps.Get.run([])
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with overrides" do
|
||||
# deps_repo brings git_repo but it is overriden
|
||||
deps = [
|
||||
|
||||
Reference in New Issue
Block a user