Compare commits

...
43 Commits
Author SHA1 Message Date
José Valim 06e5ec2d4c Release v1.7.0 2018-07-25 22:36:59 +02:00
José Valim b6d3f29b69 Allow to prune metadata while escaping (#7949)
We use this option in ExUnit as we are escaping
the code to eventually convert it to a string
representation and therefore the metadata is not
relevant.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-25 00:56:53 +02:00
Boyd Multerer bfdaf3f8d1 Update Process.info spec to reflect the possible nil return value (#7977)
The @spec for it did not reflect that possible return value - which causes dialyzer to throw errors if you check for that condition. Now the spec allows for a return value of nil.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-25 00:56:28 +02:00
José Valim bc26f10040 Mark contextual_vars as private
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-25 00:56:07 +02:00
Tobiasz Małecki 52868e556d Added missing info about new StringIO.open/3 function (#7970) 2018-07-22 19:56:17 +02:00
José Valim 04c45b031a Use quote fragments to remove duplication and optimize calendar parsing (#7951)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-21 10:48:38 +02:00
José Valim d410466ce4 Format lib/elixir/lib/code.ex 2018-07-20 16:36:11 +02:00
José Valim fe3c263599 Remove other hard deprecations 2018-07-20 16:26:14 +02:00
José Valim 7558c9a023 Remove warning on since module attribute 2018-07-20 09:50:31 +02:00
José Valim 0d30dbfd24 Properly annotate key errors for structs, closes #7935 2018-07-19 22:41:45 +02:00
José Valim 793bbe6e3c Move some runtime deprecations to compile time 2018-07-19 22:36:21 +02:00
Jean-Philippe Cugnet 3318d33d2e Fix a typo in the unnecessary quotes warning (#7934)
[ci skip]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-19 20:12:30 +02:00
José Valim b6d7769674 Release v1.7.0-rc.1 2018-07-19 12:09:05 +02:00
José Valim dc3a56c44f Remove leftover spaces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:45 +02:00
Callum Dempsey Leach ea9cb84eb1 Clarify Application.get_env by illustrating a use case (#7929)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:41 +02:00
José Valim b7b7282a22 Optimize metadata merging and ensure no nil leakage (#7927)
Closes #7926

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:32 +02:00
José Valim d3d1833d47 Properly populate top_level field of deps (#7931)
Closes #7930

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:26 +02:00
Gary Rennie 7d427719fe Fix nested use of Mix.Config.eval!/2 (#7925)
Previously, if a config file uses Mix.Config.eval!/2 then it would fail with the
following error:

 > ** (RuntimeError) could not set configuration via Mix.Config. This usually
 >  means you are trying to execute a configuration file directly instead of
 > using the proper command, such as mix loadconfig
 > code: assert Mix.Config.eval!(fixture_path("configs/nested_import.exs")) ==
 > stacktrace:
 >   (mix) lib/mix/config.ex:71: Mix.Config.raise_improper_use!/0
 >   (mix) lib/mix/config.ex:222: Mix.Config.eval!/2
 >   lib/mix/test/mix/config_test.exs:110: (test)

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:17 +02:00
Tobiasz Małecki 9812b74a2c Added missing @doc :since metadata (#7922)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:40:01 +02:00
Tobiasz Małecki aebc7e2fe1 Fixed indentation in StringIO.open/3 examples (#7920)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:57 +02:00
Fernando Tapia Rico f0aeb7136f Use "time zone" instead of "timezone" everywhere (#7917)
There is a mix of "timezone" and "time zone" in the codebase. For what I read, "time zone" is usually more correct.

In addition, there are some variables and types named `time_zone`, so I think the best option is to go with "time zone".

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:50 +02:00
Tobiasz Małecki d92a450637 fixed typo in float moduledoc (#7915)
[ci skip]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:44 +02:00
Andrea Leopardi afb4667cd0 Add an additional example to the docs for String.jaro_distance/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:39 +02:00
Fernando Tapia Rico eb50a09679 Clarify documentation of @compile :inline flag (#7909)
[ci skip]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:30 +02:00
Henrik Larsson 2f8ecec32c Clarify how to assert on exits from linked processes (#7904)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:17 +02:00
Fernando Tapia Rico 4354938168 Remove duplicated sentence from Map's doc (#7907)
[ci skip]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 19:39:13 +02:00
José Valim b06d82c93e Allow all args on Mix.Dep.loaded
Even though the API was private, developers were abusing it.
So we allow it to work for now until we remove it in future
versions.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-18 10:36:59 +02:00
Myron Marston 3dbe8a6661 Changelog: explain mix test --failed more accurately. (#7911)
`mix test --failed` runs all tests that failed the last time they ran,
even if they were not included in the last run of the test suite. This
is important, because it means that you can run individual failed tests
to focus on fixing them without worrying about mix losing track of the
other failed tests.
2018-07-16 09:21:54 +02:00
José Valim 6932e25159 Doc fixes 2018-07-15 22:24:56 +02:00
José Valim 22abd6a48e Fix CHANGELOG typo 2018-07-14 14:21:39 +02:00
José Valim 05f5073cd1 Clarify that any metadata is accepted, only some are shown 2018-07-14 11:51:39 +02:00
Tobiasz Małecki 18d96c2fd5 Add Function docs to "Basic Types" group (#7900)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-13 23:45:26 +02:00
Wojtek Mach ea04ec8f81 Clarify how to configure :test_coverage (#7896)
[ci skip]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-13 23:45:18 +02:00
Wojtek Mach 0a5d633a5e Update --cover docs (#7897)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-13 23:45:05 +02:00
José Valim 7392d736c4 Print checksums headings 2018-07-13 15:17:14 +02:00
José Valim 1164784b8e Release v1.7.0-rc.0 2018-07-13 14:43:27 +02:00
José Valim 6694ddbd2f Properly add parens around when with keywords as argument 2018-07-13 00:07:26 +02:00
José Valim 85a4d55efb Update CHANGELOG 2018-07-12 20:28:19 +02:00
Arkadiusz Gil 9b238e0316 Ignore unknown child info in Logger.Translator (#7892)
This prevents Logger application from crashing when supervisors report
children progress with fields unknown to Logger.Translator.

Closes #7889

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 20:15:08 +02:00
László Bácsi ca82388792 Show documentation metadata in IEx (only since and deprecated for now) (#7886) 2018-07-12 20:07:45 +02:00
Lukasz Samson b33dd12d86 Fix error when :trim_bom is used with :encoding
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 14:37:14 +02:00
Dimitrios Zorbas c36b2c9070 Fix syntax highlighting of Calendar.ISO docs (#7880)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 12:44:41 +02:00
José Valim ac75cdbe05 Prepare for v1.7 release 2018-07-12 11:57:29 +02:00
58 changed files with 716 additions and 309 deletions
+22 -2
View File
@@ -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,7 @@ Percentage | Module
77.19% | Total
```
## v1.7.0-dev
## v1.7.0 (2018-07-25)
### 1. Enhancements
@@ -119,6 +134,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 +148,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 +163,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 +198,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
+9 -1
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
1.7.0-dev
1.7.0
+1
View File
@@ -14,6 +14,7 @@
DateTime,
Exception,
Float,
Function,
Integer,
NaiveDateTime,
Record,
+35
View File
@@ -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
+7 -7
View File
@@ -263,13 +263,13 @@ 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}
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
def from_iso8601(unquote(match_date), calendar) when unquote(guard_date) do
{year, month, day} = unquote(read_date)
with {:ok, date} <- new(year, month, day, Calendar.ISO) do
convert(date, calendar)
end
end
+41 -41
View File
@@ -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,48 @@ 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>>
when unquote(guard_date) and sep in @sep and unquote(guard_time) <- string,
{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}
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
+65 -32
View File
@@ -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"
+13 -12
View File
@@ -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.
@@ -476,20 +476,21 @@ defmodule NaiveDateTime do
end
end
@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) 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),
with <<unquote(match_date), sep, unquote(match_time), rest::binary>>
when unquote(guard_date) and sep in @sep and unquote(guard_time) <- string,
{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
+10 -11
View File
@@ -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
@@ -221,23 +221,22 @@ defmodule Time do
from_iso8601(<<h, rest::binary>>, 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),
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
def from_iso8601(string, calendar) do
with <<unquote(match_time), rest::binary>> when unquote(guard_time) <- string,
{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).
+2 -1
View File
@@ -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
+15 -8
View File
@@ -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)
+1 -1
View File
@@ -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)
+5 -1
View File
@@ -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
+15 -3
View File
@@ -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
+1 -1
View File
@@ -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/)
+36 -1
View File
@@ -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.
+7 -7
View File
@@ -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
View File
@@ -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 """
+1
View File
@@ -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:
-1
View File
@@ -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)
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
-1
View File
@@ -24,7 +24,6 @@
imports_hygiene=true,
unquote=true,
unquoted=false,
escape=false,
generated=false
}).
+3 -3
View File
@@ -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}.
+2 -1
View File
@@ -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
+1 -3
View File
@@ -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}) ->
+76 -64
View File
@@ -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}.
+2 -2
View File
@@ -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)"
+44 -4
View File
@@ -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"
+19 -13
View File
@@ -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
+12 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+25 -21
View File
@@ -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
+80
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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)]
+3 -3
View File
@@ -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
View File
@@ -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
+8 -5
View File
@@ -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 """
+8 -2
View File
@@ -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.
+2 -2
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
use Mix.Config
Mix.Config.eval!(Path.join([__DIR__, "good_config.exs"]))
:done
+3
View File
@@ -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
+21
View File
@@ -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 = [