Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aab53b941 | ||
|
|
07c276ab5c | ||
|
|
89e3a81adc | ||
|
|
9752f1ce05 | ||
|
|
06e5ec2d4c | ||
|
|
b6d3f29b69 | ||
|
|
bfdaf3f8d1 | ||
|
|
bc26f10040 | ||
|
|
52868e556d | ||
|
|
04c45b031a | ||
|
|
d410466ce4 | ||
|
|
fe3c263599 | ||
|
|
7558c9a023 | ||
|
|
0d30dbfd24 | ||
|
|
793bbe6e3c | ||
|
|
3318d33d2e | ||
|
|
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 |
+30
-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,15 @@ Percentage | Module
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.0-dev
|
||||
## v1.7.1 (2018-07-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Work-around a Dialyzer bug that causes it to loop for a long time, potentially indefinitely
|
||||
|
||||
## v1.7.0 (2018-07-25)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -119,6 +142,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
|
||||
@@ -132,6 +156,7 @@ Percentage | Module
|
||||
* [Regex] Include endianness in the regex version. This allows regexes to be recompiled when an archive is installed in a system with a different endianness
|
||||
* [Registry] Add `Registry.count/1` and `Registry.count_match/4`
|
||||
* [String] Update to Unicode 11
|
||||
* [StringIO] Add `StringIO.open/3`
|
||||
* [System] Use ISO 8601 in `System.build_info/0`
|
||||
|
||||
#### ExUnit
|
||||
@@ -146,9 +171,11 @@ 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
|
||||
|
||||
* [Logger] Ensure nil metadata is always pruned
|
||||
* [Logger] Only evaluate Logger macro arguments when the message will be logged
|
||||
* [Logger] Add `:compile_time_purge_matching` to purge logger calls that match certain compile time metadata, such as module names and application names
|
||||
* [Logger] Log to `:stderr` if a backend fails and there are no other backends
|
||||
@@ -179,6 +206,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
|
||||
|
||||
@@ -263,24 +263,29 @@ defmodule Date do
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes>>, calendar) do
|
||||
with {year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day) do
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO), do: convert(date, calendar)
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<?-, next, rest::binary>>, calendar) when next != ?- do
|
||||
with {:ok, %{year: year} = date} <- from_iso8601(<<next>> <> rest, calendar) do
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
with {:ok, %{year: year} = date} <- raw_from_iso8601(rest, calendar) do
|
||||
{:ok, %{date | year: -year}}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<_::binary>>, _calendar) do
|
||||
{:error, :invalid_format}
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with unquote(match_date) <- string,
|
||||
true <- unquote(guard_date) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO) do
|
||||
convert(date, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -505,48 +505,66 @@ defmodule DateTime do
|
||||
@doc since: "1.4.0"
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) ::
|
||||
{:ok, t, Calendar.utc_offset()} | {:error, atom}
|
||||
|
||||
@sep [?\s, ?T]
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
|
||||
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
|
||||
true <- sep in [?\s, ?T],
|
||||
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
|
||||
{year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
{minute, ""} <- Integer.parse(min),
|
||||
{second, ""} <- Integer.parse(sec),
|
||||
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
|
||||
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{:ok, date} <- Date.new(year, month, day),
|
||||
{:ok, time} <- Time.new(hour, minute, second, microsecond),
|
||||
{:ok, offset} <- parse_offset(rest) do
|
||||
%{year: year, month: month, day: day} = date
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
|
||||
{_, precision} = microsecond
|
||||
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, minute, second} = unquote(read_time)
|
||||
|
||||
datetime =
|
||||
Calendar.ISO.naive_datetime_to_iso_days(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond
|
||||
)
|
||||
|> apply_tz_offset(offset)
|
||||
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
{:error, :invalid_date}
|
||||
|
||||
{:ok, %{datetime | microsecond: microsecond}, offset}
|
||||
not calendar.valid_time?(hour, minute, second, microsecond) ->
|
||||
{:error, :invalid_time}
|
||||
|
||||
offset == 0 ->
|
||||
datetime = %DateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
std_offset: 0,
|
||||
utc_offset: 0,
|
||||
zone_abbr: "UTC",
|
||||
time_zone: "Etc/UTC"
|
||||
}
|
||||
|
||||
{:ok, datetime, 0}
|
||||
|
||||
is_nil(offset) ->
|
||||
{:error, :missing_offset}
|
||||
|
||||
true ->
|
||||
{_, precision} = microsecond
|
||||
|
||||
datetime =
|
||||
Calendar.ISO.naive_datetime_to_iso_days(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond
|
||||
)
|
||||
|> apply_tz_offset(offset)
|
||||
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
|
||||
|
||||
{:ok, %{datetime | microsecond: microsecond}, offset}
|
||||
end
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_offset(rest) do
|
||||
case Calendar.ISO.parse_offset(rest) do
|
||||
{offset, ""} when is_integer(offset) -> {:ok, offset}
|
||||
{nil, ""} -> {:error, :missing_offset}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,6 +36,39 @@ defmodule Calendar.ISO do
|
||||
|
||||
@months_in_year 12
|
||||
|
||||
@doc false
|
||||
def __match_date__ do
|
||||
quote do
|
||||
[
|
||||
<<y1, y2, y3, y4, ?-, m1, m2, ?-, d1, d2>>,
|
||||
y1 >= ?0 and y1 <= ?9 and y2 >= ?0 and y2 <= ?9 and y3 >= ?0 and y3 <= ?9 and y4 >= ?0 and
|
||||
y4 <= ?9 and m1 >= ?0 and m1 <= ?9 and m2 >= ?0 and m2 <= ?9 and d1 >= ?0 and d1 <= ?9 and
|
||||
d2 >= ?0 and d2 <= ?9,
|
||||
{
|
||||
(y1 - ?0) * 1000 + (y2 - ?0) * 100 + (y3 - ?0) * 10 + (y4 - ?0),
|
||||
(m1 - ?0) * 10 + (m2 - ?0),
|
||||
(d1 - ?0) * 10 + (d2 - ?0)
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __match_time__ do
|
||||
quote do
|
||||
[
|
||||
<<h1, h2, ?:, i1, i2, ?:, s1, s2>>,
|
||||
h1 >= ?0 and h1 <= ?9 and h2 >= ?0 and h2 <= ?9 and i1 >= ?0 and i1 <= ?9 and i2 >= ?0 and
|
||||
i2 <= ?9 and s1 >= ?0 and s1 <= ?9 and s2 >= ?0 and s2 <= ?9,
|
||||
{
|
||||
(h1 - ?0) * 10 + (h2 - ?0),
|
||||
(i1 - ?0) * 10 + (i2 - ?0),
|
||||
(s1 - ?0) * 10 + (s2 - ?0)
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the `t:Calendar.iso_days/0` format of the specified date.
|
||||
|
||||
@@ -351,12 +384,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 +411,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 +436,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 +485,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 +509,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"
|
||||
|
||||
@@ -171,10 +171,35 @@ defmodule NaiveDateTime do
|
||||
Calendar.microsecond(),
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
def new(year, month, day, hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO) do
|
||||
with {:ok, date} <- Date.new(year, month, day, calendar),
|
||||
{:ok, time} <- Time.new(hour, minute, second, microsecond, calendar),
|
||||
do: new(date, time)
|
||||
def new(year, month, day, hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
def new(year, month, day, hour, minute, second, microsecond, calendar)
|
||||
when is_integer(microsecond) do
|
||||
new(year, month, day, hour, minute, second, {microsecond, 6}, calendar)
|
||||
end
|
||||
|
||||
def new(year, month, day, hour, minute, second, microsecond, calendar) do
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
{:error, :invalid_date}
|
||||
|
||||
not calendar.valid_time?(hour, minute, second, microsecond) ->
|
||||
{:error, :invalid_time}
|
||||
|
||||
true ->
|
||||
naive_datetime = %NaiveDateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
|
||||
{:ok, naive_datetime}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -412,7 +437,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.
|
||||
|
||||
@@ -470,26 +495,31 @@ defmodule NaiveDateTime do
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<?-, next, rest::binary>>, calendar) when next != ?- do
|
||||
with {:ok, %{year: year} = naive_datetime} <- from_iso8601(<<next>> <> rest, calendar) do
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
with {:ok, %{year: year} = naive_datetime} <- raw_from_iso8601(rest, calendar) do
|
||||
{:ok, %{naive_datetime | year: -year}}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(string, calendar) when is_binary(string) do
|
||||
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
|
||||
true <- sep in [?\s, ?T],
|
||||
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
|
||||
{year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
{min, ""} <- Integer.parse(min),
|
||||
{sec, ""} <- Integer.parse(sec),
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
@sep [?\s, ?T]
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
|
||||
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO),
|
||||
do: convert(utc_date, calendar)
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, min, sec} = unquote(read_time)
|
||||
|
||||
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO) do
|
||||
convert(utc_date, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -217,27 +217,31 @@ defmodule Time do
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<?T, h, rest::binary>>, calendar) when h in ?0..?9 do
|
||||
from_iso8601(<<h, rest::binary>>, calendar)
|
||||
def from_iso8601(<<?T, rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
def from_iso8601(<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>, calendar) do
|
||||
with {hour, ""} <- Integer.parse(hour),
|
||||
{min, ""} <- Integer.parse(min),
|
||||
{sec, ""} <- Integer.parse(sec),
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar)
|
||||
end
|
||||
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
defp raw_from_iso8601(string, calendar) do
|
||||
with <<unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_time),
|
||||
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO),
|
||||
do: convert(utc_time, calendar)
|
||||
{hour, min, sec} = unquote(read_time)
|
||||
|
||||
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO) do
|
||||
convert(utc_time, calendar)
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<_::binary>>, _calendar) do
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the extended "Local time" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
@@ -1121,7 +1121,8 @@ defmodule Code do
|
||||
longer available, and therefore this function always returns `nil`.
|
||||
Use `Code.fetch_docs/1` instead.
|
||||
"""
|
||||
@deprecated "Code.get_docs/2 always returns nil as its outdated documentation is no longer stored on BEAM files. Use Code.fetch_docs/1 instead"
|
||||
@doc deprecated:
|
||||
"Code.get_docs/2 always returns nil as its outdated documentation is no longer stored on BEAM files. Use Code.fetch_docs/1 instead"
|
||||
@spec get_docs(module, :moduledoc | :docs | :callback_docs | :type_docs | :all) :: nil
|
||||
def get_docs(_module, _kind) do
|
||||
nil
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1163,7 +1163,7 @@ defmodule KeyError do
|
||||
end
|
||||
|
||||
defp map_with_atom_keys_only?(term) do
|
||||
is_map(term) and Enum.all?(term, fn {k, _} -> is_atom(k) end)
|
||||
is_map(term) and Enum.all?(Map.to_list(term), fn {k, _} -> is_atom(k) end)
|
||||
end
|
||||
|
||||
defp available_keys(term) when is_map(term), do: Map.keys(term)
|
||||
|
||||
@@ -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
|
||||
@@ -2776,7 +2776,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
try do
|
||||
:elixir_quote.escape(value, false)
|
||||
:elixir_quote.escape(value, :default, false)
|
||||
rescue
|
||||
ex in [ArgumentError] ->
|
||||
raise ArgumentError,
|
||||
@@ -3698,7 +3698,7 @@ defmodule Kernel do
|
||||
quote(do: Kernel.LexicalTracker.read_cache(unquote(pid), unquote(integer)))
|
||||
|
||||
%{} ->
|
||||
{escaped, _} = :elixir_quote.escape(block, false)
|
||||
{escaped, _} = :elixir_quote.escape(block, :default, false)
|
||||
escaped
|
||||
end
|
||||
|
||||
@@ -3972,8 +3972,8 @@ defmodule Kernel do
|
||||
module = assert_module_scope(env, kind, 2)
|
||||
assert_no_function_scope(env, kind, 2)
|
||||
|
||||
{escaped_call, unquoted_call} = :elixir_quote.escape(call, true)
|
||||
{escaped_expr, unquoted_expr} = :elixir_quote.escape(expr, true)
|
||||
{escaped_call, unquoted_call} = :elixir_quote.escape(call, :default, true)
|
||||
{escaped_expr, unquoted_expr} = :elixir_quote.escape(expr, :default, true)
|
||||
|
||||
escaped_expr =
|
||||
case unquoted_expr do
|
||||
|
||||
+24
-7
@@ -356,12 +356,7 @@ defmodule Macro do
|
||||
def decompose_call(_), do: :error
|
||||
|
||||
@doc """
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `escape/2`
|
||||
which leaves `unquote/1` statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
Recursively escapes a value so it can be inserted into a syntax tree.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -374,6 +369,26 @@ defmodule Macro do
|
||||
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
|
||||
1
|
||||
|
||||
## Options
|
||||
|
||||
* `:unquote` - when true, this function leaves `unquote/1` and
|
||||
`unquote_splicing/1` statements unescaped, effectively unquoting
|
||||
the contents on escape. This option is useful only when escaping
|
||||
ASTs which may have quoted fragments in them. Defaults to false.
|
||||
|
||||
* `:prune_metadata` - when true, removes metadata from escaped AST
|
||||
nodes. Note this option changes the semantics of escaped code and
|
||||
it should only be used when escaping ASTs, never values. Defaults
|
||||
to false.
|
||||
|
||||
As an example, `ExUnit` stores the AST of every assertion, so when
|
||||
an assertion fails we can show code snippets to users. Without this
|
||||
option, each time the test module is compiled, we get a different
|
||||
MD5 of the module byte code, because the AST contains metadata,
|
||||
such as counters, specific to the compilation environment. By pruning
|
||||
the metadata, we ensure that the module is deterministic and reduce
|
||||
the amount of data `ExUnit` needs to keep around.
|
||||
|
||||
## Comparison to `Kernel.quote/2`
|
||||
|
||||
The `escape/2` function is sometimes confused with `Kernel.SpecialForms.quote/2`,
|
||||
@@ -402,7 +417,9 @@ defmodule Macro do
|
||||
"""
|
||||
@spec escape(term, keyword) :: Macro.t()
|
||||
def escape(expr, opts \\ []) do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
unquote = Keyword.get(opts, :unquote, false)
|
||||
kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :default
|
||||
elem(:elixir_quote.escape(expr, kind, unquote), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -45,6 +45,7 @@ defmodule Macro.Env do
|
||||
* `current_vars`
|
||||
* `unused_vars`
|
||||
* `prematch_vars`
|
||||
* `contextual_vars`
|
||||
|
||||
The following fields are deprecated and must not be accessed or relied on:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -559,7 +559,7 @@ defmodule OptionParser do
|
||||
|
||||
true ->
|
||||
# TODO: Remove this in Elixir v2.0
|
||||
IO.warn("Not passing the :switches or :strict option to OptionParser is deprecated")
|
||||
IO.warn("not passing the :switches or :strict option to OptionParser is deprecated")
|
||||
{[], false}
|
||||
end
|
||||
|
||||
|
||||
@@ -636,7 +636,7 @@ defmodule Process do
|
||||
|
||||
See `:erlang.process_info/1` for more info.
|
||||
"""
|
||||
@spec info(pid) :: keyword
|
||||
@spec info(pid) :: keyword | nil
|
||||
def info(pid) do
|
||||
nillify(:erlang.process_info(pid))
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
imports_hygiene=true,
|
||||
unquote=true,
|
||||
unquoted=false,
|
||||
escape=false,
|
||||
generated=false
|
||||
}).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do, Block}]) ->
|
||||
{Escaped, _} = elixir_quote:escape(Block, false),
|
||||
{Escaped, _} = elixir_quote:escape(Block, default, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
{{'.', [], [elixir_module, compile]}, [], Args}.
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
{defp, 2}].
|
||||
|
||||
define({Line, E}, Kind, Call, Expr) ->
|
||||
{EscapedCall, UC} = elixir_quote:escape(Call, true),
|
||||
{EscapedExpr, UE} = elixir_quote:escape(Expr, true),
|
||||
{EscapedCall, UC} = elixir_quote:escape(Call, default, true),
|
||||
{EscapedExpr, UE} = elixir_quote:escape(Expr, default, true),
|
||||
Args = [Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E#{line := Line})],
|
||||
{{'.', [], [elixir_def, store_definition]}, [], Args}.
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, #{context := Context} = E)
|
||||
Struct = load_struct(Meta, ELeft, [Assocs], InContext, EE),
|
||||
assert_struct_keys(Meta, ELeft, Struct, Assocs, EE),
|
||||
Keys = ['__struct__'] ++ [K || {K, _} <- Assocs],
|
||||
{StructAssocs, _} = elixir_quote:escape(maps:to_list(maps:without(Keys, Struct)), false),
|
||||
WithoutKeys = maps:to_list(maps:without(Keys, Struct)),
|
||||
{StructAssocs, _} = elixir_quote:escape(WithoutKeys, default, false),
|
||||
{{'%', Meta, [ELeft, {'%{}', MapMeta, StructAssocs ++ Assocs}]}, EE};
|
||||
|
||||
{_, _, Assocs} -> %% Update or match
|
||||
|
||||
@@ -331,7 +331,7 @@ warn_unused_attributes(File, DataSet, DataBag, PersistedAttrs) ->
|
||||
StoredAttrs = bag_lookup_element(DataBag, attributes, 2),
|
||||
%% This is the same list as in Module.put_attribute
|
||||
%% without moduledoc which are never warned on.
|
||||
Attrs = [doc, typedoc, impl, deprecated | StoredAttrs -- PersistedAttrs],
|
||||
Attrs = [doc, typedoc, impl, deprecated | StoredAttrs -- [since | PersistedAttrs]],
|
||||
Query = [{{Attr, '_', '$1'}, [{is_integer, '$1'}], [[Attr, '$1']]} || Attr <- Attrs],
|
||||
[elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_attribute, Key})
|
||||
|| [Key, Line] <- ets:select(DataSet, Query)].
|
||||
@@ -404,8 +404,6 @@ format_error({unused_attribute, impl}) ->
|
||||
"module attribute @impl was set but no definition follows it";
|
||||
format_error({unused_attribute, deprecated}) ->
|
||||
"module attribute @deprecated was set but no definition follows it";
|
||||
format_error({unused_attribute, since}) -> %% TODO: Remove this on v1.8
|
||||
"module attribute @since is deprecated, please use \"@doc since: ...\" instead";
|
||||
format_error({unused_attribute, Attr}) ->
|
||||
io_lib:format("module attribute @~ts was set but never used", [Attr]);
|
||||
format_error({invalid_module, Module}) ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
|
||||
-export([escape/3, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
|
||||
-export([dot/5, tail_list/3, list/2]). %% Quote callbacks
|
||||
|
||||
-include("elixir.hrl").
|
||||
@@ -132,16 +132,15 @@ annotate(Tree, _Context) -> Tree.
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
escape(Expr, Kind, Unquote) ->
|
||||
{Res, Q} = quote(Expr, nil, #elixir_quote{
|
||||
line=true,
|
||||
file=nil,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=Unquote,
|
||||
escape=true
|
||||
}, nil),
|
||||
unquote=Unquote
|
||||
}, Kind),
|
||||
{Res, Q#elixir_quote.unquoted}.
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
@@ -161,7 +160,7 @@ quote(Expr, Binding, Q, E) ->
|
||||
{'{}', [], [K, [], Context]},
|
||||
V
|
||||
] ]
|
||||
} || {K, V} <- Binding],
|
||||
} || {K, V} <- Binding],
|
||||
|
||||
{TExprs, TQ} = do_quote(Expr, Q, E),
|
||||
{{'{}', [], ['__block__', [], Vars ++ [TExprs] ]}, TQ}.
|
||||
@@ -211,11 +210,7 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
|
||||
do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
|
||||
do_quote_import(Name, Meta, ArgsOrAtom, Q, E);
|
||||
|
||||
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
%% Literals
|
||||
%% Two element tuples
|
||||
|
||||
do_quote({Left, Right}, #elixir_quote{unquote=true} = Q, E) when
|
||||
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
|
||||
@@ -227,7 +222,33 @@ do_quote({Left, Right}, Q, E) ->
|
||||
{TRight, RQ} = do_quote(Right, LQ, E),
|
||||
{{TLeft, TRight}, RQ};
|
||||
|
||||
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
|
||||
%% Everything else
|
||||
|
||||
do_quote(Other, Q, E) when is_atom(E) ->
|
||||
do_escape(Other, Q, E);
|
||||
|
||||
do_quote({_, _, _} = Tuple, Q, E) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
do_quote(List, Q, E) when is_list(List) ->
|
||||
do_quote_splice(lists:reverse(List), Q, E);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{Other, Q}.
|
||||
|
||||
%% do_escape
|
||||
|
||||
do_escape({Left, _Meta, Right}, Q, E = prune_metadata) ->
|
||||
{TL, QL} = do_quote(Left, Q, E),
|
||||
{TR, QR} = do_quote(Right, QL, E),
|
||||
{{'{}', [], [TL, [], TR]}, QR};
|
||||
|
||||
do_escape(Tuple, Q, E) when is_tuple(Tuple) ->
|
||||
{TT, TQ} = do_quote(tuple_to_list(Tuple), Q, E),
|
||||
{{'{}', [], TT}, TQ};
|
||||
|
||||
do_escape(BitString, Q, _) when is_bitstring(BitString) ->
|
||||
case bit_size(BitString) rem 8 of
|
||||
0 ->
|
||||
{BitString, Q};
|
||||
@@ -236,51 +257,41 @@ do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitStri
|
||||
{{'<<>>', [], [{'::', [], [Bits, {size, [], [Size]}]}, {'::', [], [Bytes, {binary, [], []}]}]}, Q}
|
||||
end;
|
||||
|
||||
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
|
||||
do_escape(Map, Q, E) when is_map(Map) ->
|
||||
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
|
||||
{{'%{}', [], TT}, TQ};
|
||||
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
|
||||
{TT, TQ} = do_quote(tuple_to_list(Tuple), Q, E),
|
||||
{{'{}', [], TT}, TQ};
|
||||
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
|
||||
do_escape(List, Q, E) when is_list(List) ->
|
||||
%% The improper case is a bit inefficient, but improper lists are rare.
|
||||
case reverse_improper(List) of
|
||||
{L} -> do_splice(L, Q, E);
|
||||
{L, R} ->
|
||||
{TL, QL} = do_splice(L, Q, E, [], []),
|
||||
{L} -> do_quote_splice(L, Q, E);
|
||||
{L, R} ->
|
||||
{TL, QL} = do_quote_splice(L, Q, E, [], []),
|
||||
{TR, QR} = do_quote(R, QL, E),
|
||||
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
|
||||
end;
|
||||
|
||||
do_quote(Other, #elixir_quote{escape=true} = Q, _)
|
||||
do_escape(Other, Q, _)
|
||||
when is_number(Other); is_pid(Other); is_atom(Other) ->
|
||||
{Other, Q};
|
||||
|
||||
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
|
||||
do_escape(Fun, Q, _) when is_function(Fun) ->
|
||||
case (erlang:fun_info(Fun, env) == {env, []}) andalso
|
||||
(erlang:fun_info(Fun, type) == {type, external}) of
|
||||
true -> {Fun, Q};
|
||||
false -> bad_escape(Fun)
|
||||
end;
|
||||
|
||||
do_quote(Other, #elixir_quote{escape=true}, _) ->
|
||||
bad_escape(Other);
|
||||
|
||||
do_quote(List, Q, E) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, E);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{Other, Q}.
|
||||
|
||||
%% Quote helpers
|
||||
do_escape(Other, _, _) ->
|
||||
bad_escape(Other).
|
||||
|
||||
bad_escape(Arg) ->
|
||||
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
|
||||
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>).
|
||||
|
||||
%% do_quote_*
|
||||
|
||||
do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q, E) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
@@ -332,10 +343,41 @@ do_quote_tuple(Left, Meta, [{{unquote, _, _}, _, _}, _] = Right, Q, E) when ?def
|
||||
{{'{}', [], [TLeft, meta(Meta, Q), [NewHead, Body]]}, RQ};
|
||||
|
||||
do_quote_tuple(Left, Meta, Right, Q, E) ->
|
||||
{TLeft, LQ} = do_quote(Left, Q, E),
|
||||
{TLeft, LQ} = do_quote(Left, Q, E),
|
||||
{TRight, RQ} = do_quote(Right, LQ, E),
|
||||
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
|
||||
|
||||
do_quote_splice([{'|', Meta, [{unquote_splicing, _, [Left]}, Right]} | T], #elixir_quote{unquote=true} = Q, E) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg) | tail], this will quote
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{TT, QT} = do_quote_splice(T, Q, E, [], []),
|
||||
{TR, QR} = do_quote(Right, QT, E),
|
||||
{do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true}};
|
||||
|
||||
do_quote_splice(List, Q, E) ->
|
||||
do_quote_splice(List, Q, E, [], []).
|
||||
|
||||
do_quote_splice([{unquote_splicing, Meta, [Expr]} | T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
|
||||
Runtime = do_runtime_list(Meta, list, [Expr, do_list_concat(Buffer, Acc)]),
|
||||
do_quote_splice(T, Q#elixir_quote{unquoted=true}, E, [], Runtime);
|
||||
|
||||
do_quote_splice([H | T], Q, E, Buffer, Acc) ->
|
||||
{TH, TQ} = do_quote(H, Q, E),
|
||||
do_quote_splice(T, TQ, E, [TH | Buffer], Acc);
|
||||
|
||||
do_quote_splice([], Q, _E, Buffer, Acc) ->
|
||||
{do_list_concat(Buffer, Acc), Q}.
|
||||
|
||||
do_list_concat(Left, []) -> Left;
|
||||
do_list_concat([], Right) -> Right;
|
||||
do_list_concat(Left, Right) -> {{'.', [], [erlang, '++']}, [], [Left, Right]}.
|
||||
|
||||
do_runtime_list(Meta, Fun, Args) ->
|
||||
{{'.', Meta, [elixir_quote, Fun]}, Meta, Args}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
meta(Meta, Q) ->
|
||||
generated(keep(Meta, Q), Q).
|
||||
|
||||
@@ -381,33 +423,3 @@ keynew(Key, Meta, Value) ->
|
||||
{Key, _} -> Meta;
|
||||
_ -> keystore(Key, Meta, Value)
|
||||
end.
|
||||
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{'|', Meta, [{unquote_splicing, _, [Left]}, Right]} | T], #elixir_quote{unquote=true} = Q, E) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg) | tail], this will quote
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{TT, QT} = do_splice(T, Q, E, [], []),
|
||||
{TR, QR} = do_quote(Right, QT, E),
|
||||
{do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true}};
|
||||
|
||||
do_splice(List, Q, E) ->
|
||||
do_splice(List, Q, E, [], []).
|
||||
|
||||
do_splice([{unquote_splicing, Meta, [Expr]} | T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, E, [], do_runtime_list(Meta, list, [Expr, do_join(Buffer, Acc)]));
|
||||
|
||||
do_splice([H | T], Q, E, Buffer, Acc) ->
|
||||
{TH, TQ} = do_quote(H, Q, E),
|
||||
do_splice(T, TQ, E, [TH | Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _E, Buffer, Acc) ->
|
||||
{do_join(Buffer, Acc), Q}.
|
||||
|
||||
do_join(Left, []) -> Left;
|
||||
do_join([], Right) -> Right;
|
||||
do_join(Left, Right) -> {{'.', [], [erlang, '++']}, [], [Left, Right]}.
|
||||
|
||||
do_runtime_list(Meta, Fun, Args) ->
|
||||
{{'.', Meta, [elixir_quote, Fun]}, Meta, Args}.
|
||||
|
||||
@@ -425,7 +425,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H)
|
||||
true ->
|
||||
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, io_lib:format(
|
||||
"found quoted atom \"~ts\" but the quotes are not required. "
|
||||
"Quotes should only be used to introduced atoms with foreign characters in them",
|
||||
"Quotes should only be used to introduce atoms with foreign characters in them",
|
||||
[hd(Parts)]
|
||||
));
|
||||
|
||||
@@ -646,7 +646,7 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
|
||||
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, io_lib:format(
|
||||
"found quoted keyword \"~ts\" but the quotes are not required. "
|
||||
"Note that keywords are always atoms, even when quoted, and quotes "
|
||||
"should only be used to introduced keywords with foreign characters in them",
|
||||
"should only be used to introduce keywords with foreign characters in them",
|
||||
[hd(Parts)]
|
||||
));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -510,6 +510,13 @@ defmodule ExceptionTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotates key error with suggestions for structs" do
|
||||
message = blame_message(%URI{}, fn map -> map.schema end)
|
||||
assert message =~ "key :schema not found in: %URI{"
|
||||
assert message =~ "Did you mean one of:"
|
||||
assert message =~ "* :scheme"
|
||||
end
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '21' do
|
||||
test "annotates +/1 arithmetic errors" do
|
||||
assert blame_message(:foo, &(+&1)) == "bad argument in arithmetic expression: +(:foo)"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -24,38 +24,44 @@ defmodule MacroTest do
|
||||
import Macro.ExternalTest
|
||||
|
||||
describe "escape/2" do
|
||||
test "handles tuples with size different than two" do
|
||||
test "returns tuples with size equal to two" do
|
||||
assert Macro.escape({:a, :b}) == {:a, :b}
|
||||
end
|
||||
|
||||
test "returns lists" do
|
||||
assert Macro.escape([1, 2, 3]) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test "escapes tuples with size different than two" do
|
||||
assert Macro.escape({:a}) == {:{}, [], [:a]}
|
||||
assert Macro.escape({:a, :b, :c}) == {:{}, [], [:a, :b, :c]}
|
||||
assert Macro.escape({:a, {1, 2, 3}, :c}) == {:{}, [], [:a, {:{}, [], [1, 2, 3]}, :c]}
|
||||
end
|
||||
|
||||
test "simply returns tuples with size equal to two" do
|
||||
assert Macro.escape({:a, :b}) == {:a, :b}
|
||||
end
|
||||
|
||||
test "simply returns any other structure" do
|
||||
assert Macro.escape([1, 2, 3]) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test "handles maps" do
|
||||
test "escapes maps" do
|
||||
assert Macro.escape(%{a: 1}) == {:%{}, [], [a: 1]}
|
||||
end
|
||||
|
||||
test "handles bitstring" do
|
||||
test "escapes bitstring" do
|
||||
assert {:<<>>, [], args} = Macro.escape(<<300::12>>)
|
||||
assert [{:::, [], [1, {:size, [], [4]}]}, {:::, [], [",", {:binary, [], []}]}] = args
|
||||
end
|
||||
|
||||
test "works recursively" do
|
||||
test "escapes recursively" do
|
||||
assert Macro.escape([1, {:a, :b, :c}, 3]) == [1, {:{}, [], [:a, :b, :c]}, 3]
|
||||
end
|
||||
|
||||
test "with improper lists" do
|
||||
test "escapes improper lists" do
|
||||
assert Macro.escape([1 | 2]) == [{:|, [], [1, 2]}]
|
||||
assert Macro.escape([1, 2 | 3]) == [1, {:|, [], [2, 3]}]
|
||||
end
|
||||
|
||||
test "prunes metadata" do
|
||||
meta = [nothing: :important, counter: 1]
|
||||
assert Macro.escape({:foo, meta, []}) == {:{}, [], [:foo, meta, []]}
|
||||
assert Macro.escape({:foo, meta, []}, prune_metadata: true) == {:{}, [], [:foo, [], []]}
|
||||
end
|
||||
|
||||
test "with unquote" do
|
||||
contents = quote(unquote: false, do: unquote(1))
|
||||
assert Macro.escape(contents, unquote: true) == 1
|
||||
|
||||
@@ -376,7 +376,7 @@ end
|
||||
defmodule OptionsParserDeprecationsTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@warning ~r[Not passing the :switches or :strict option to OptionParser is deprecated]
|
||||
@warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]
|
||||
|
||||
def assert_deprecated(fun) do
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
|
||||
|
||||
@@ -317,7 +317,7 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
|
||||
defp escape_quoted(kind, expr) do
|
||||
Macro.escape({kind, [], [expr]})
|
||||
Macro.escape({kind, [], [expr]}, prune_metadata: true)
|
||||
end
|
||||
|
||||
defp extract_args({root, meta, [_ | _] = args} = expr, env) do
|
||||
@@ -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
|
||||
|
||||
@@ -1095,8 +1095,8 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use import_file_if_available/1 instead"
|
||||
defmacro import_file(path, opts) when is_binary(path) and is_list(opts) do
|
||||
IO.warn("import_file/2 is deprecated, please use import_file_if_available/1 instead")
|
||||
import_file_if_available(path, Keyword.get(opts, :optional, false))
|
||||
end
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -350,14 +350,14 @@ defmodule Mix.Utils do
|
||||
defp quoted(data), do: [?", to_string(data), ?"]
|
||||
|
||||
@doc false
|
||||
@deprecated "Use Macro.underscore/1 instead"
|
||||
def underscore(value) do
|
||||
IO.warn("Mix.Utils.underscore/1 is deprecated, use Macro.underscore/1 instead")
|
||||
Macro.underscore(value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use Macro.camelize/1 instead"
|
||||
def camelize(value) do
|
||||
IO.warn("Mix.Utils.camelize/1 is deprecated, use Macro.camelize/1 instead")
|
||||
Macro.camelize(value)
|
||||
end
|
||||
|
||||
|
||||
@@ -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