Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fb6c0d0e | ||
|
|
4f4aad2cd4 | ||
|
|
d0f5236c8e | ||
|
|
2b95813d98 | ||
|
|
eb5679bfcc | ||
|
|
e38ae98c3a | ||
|
|
71a537b950 | ||
|
|
7087149bfa | ||
|
|
b4b1947a22 | ||
|
|
a33d9116ab | ||
|
|
7d744ebc84 | ||
|
|
3e6c6986f2 | ||
|
|
f167716f53 | ||
|
|
0cdf6d28b1 | ||
|
|
e4c1e3d262 | ||
|
|
3f6313906d | ||
|
|
0089a40a33 | ||
|
|
549745fa5e | ||
|
|
ac41358b5a | ||
|
|
0db2ad6953 | ||
|
|
8ca85ed2a8 | ||
|
|
af64fe7e98 | ||
|
|
28cf40fe03 | ||
|
|
d38a189907 | ||
|
|
2b338092b6 | ||
|
|
557b779bd4 | ||
|
|
ece40480f6 | ||
|
|
a77445c46b | ||
|
|
4d55fa6814 | ||
|
|
b9d6f4f9c1 | ||
|
|
b0cf76c74c | ||
|
|
7841fbb8e0 | ||
|
|
f5773fed42 | ||
|
|
e2b8ab9a0d | ||
|
|
1dc6d10f30 | ||
|
|
e7138fb2c6 | ||
|
|
04ecc05adc | ||
|
|
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 |
+84
-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,69 @@ Percentage | Module
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.0-dev
|
||||
## v1.7.4 (2018-10-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Expand `left..right` at compile time in more cases, which leads to improved performance under different scenarios, especially on `x in left..right` expressions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.loadpaths] Add `--no-load-deps` flag. This is useful for Rebar 3 compatibility
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Fix for converting from negative iso days on New Year in a leap year
|
||||
* [Kernel] Ensure `@spec`, `@callback`, `@type` and friends can be read accordingly
|
||||
* [Module] Avoid warnings when using Module.eval_quoted in the middle of existing definitions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.build] Unload previous archive versions before building
|
||||
* [mix format] Expand paths so mix format `path\for\windows.ex` works
|
||||
* [mix test] Ensure that `--cover` displays correct coverage in an umbrella app
|
||||
|
||||
## v1.7.3 (2018-08-24)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Do not attempt to expand `try/1` as it is a special form
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.app] Do not include applications with `runtime: false` as a runtime dependency for applications coming from Hex
|
||||
|
||||
## v1.7.2 (2018-08-05)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [DateTime] Take negative years into account in `DateTime.from_iso8601/1`
|
||||
* [Kernel] Do not emit warnings for repeated docs over different clauses due to false positives
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Properly mark top-level dependencies as optional and as runtime. This fixes a bug where Mix attempted to start optional dependencies of a package when those optional dependencies were not available
|
||||
* [mix compile] Avoid deadlock when a config has a timestamp later than current time
|
||||
* [mix help] Show task and alias help when both are available
|
||||
* [mix test] Do not fail suite if there are no test files
|
||||
|
||||
## 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 +196,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 +210,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 +225,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 +260,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
|
||||
|
||||
@@ -170,7 +170,7 @@ defmodule Calendar do
|
||||
@callback time_to_string(hour, minute, second, microsecond) :: String.t()
|
||||
|
||||
@doc """
|
||||
Converts the given datetime (with time zone) into the `t:iso_days/0` format.
|
||||
Converts the given datetime (without time zone) into the `t:iso_days/0` format.
|
||||
"""
|
||||
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) ::
|
||||
iso_days
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -485,6 +485,14 @@ defmodule DateTime do
|
||||
iex> datetime
|
||||
#DateTime<2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> {:ok, datetime, 0} = DateTime.from_iso8601("-2015-01-23T23:50:07Z")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 23:50:07Z>
|
||||
|
||||
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("-2015-01-23T23:50:07,123+02:30")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
|
||||
@@ -505,48 +513,76 @@ defmodule DateTime do
|
||||
@doc since: "1.4.0"
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) ::
|
||||
{:ok, t, Calendar.utc_offset()} | {:error, atom}
|
||||
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),
|
||||
{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
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
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}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, true)
|
||||
end
|
||||
|
||||
defp parse_offset(rest) do
|
||||
case Calendar.ISO.parse_offset(rest) do
|
||||
{offset, ""} when is_integer(offset) -> {:ok, offset}
|
||||
{nil, ""} -> {:error, :missing_offset}
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, false)
|
||||
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, is_negative_datetime) do
|
||||
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),
|
||||
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, minute, second} = unquote(read_time)
|
||||
year = if is_negative_datetime, do: -year, else: year
|
||||
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
{:error, :invalid_date}
|
||||
|
||||
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, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,7 @@ defmodule Calendar.ISO do
|
||||
@seconds_per_hour 60 * 60
|
||||
# Note that this does _not_ handle leap seconds.
|
||||
@seconds_per_day 24 * 60 * 60
|
||||
@last_second_of_the_day @seconds_per_day - 1
|
||||
@microseconds_per_second 1_000_000
|
||||
@parts_per_day @seconds_per_day * @microseconds_per_second
|
||||
|
||||
@@ -36,6 +37,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.
|
||||
|
||||
@@ -174,23 +208,13 @@ defmodule Calendar.ISO do
|
||||
|
||||
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def date_from_iso_days(days) when days in 0..3_652_424 do
|
||||
def date_from_iso_days(days) when days in -3_652_059..3_652_424 do
|
||||
{year, day_of_year} = days_to_year(days)
|
||||
extra_day = if leap_year?(year), do: 1, else: 0
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{year, month, day_in_month + 1}
|
||||
end
|
||||
|
||||
def date_from_iso_days(days) when days in -3_652_059..-1 do
|
||||
{year, day_of_year} = days_to_year(-days)
|
||||
previous_extra_day = if leap_year?(year), do: 1, else: 0
|
||||
extra_day = if leap_year?(year + 1), do: 1, else: 0
|
||||
day_of_year = @days_per_nonleap_year + extra_day - day_of_year
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{-year - 1, month, day_in_month + previous_extra_day}
|
||||
end
|
||||
|
||||
defp div_mod(int1, int2) do
|
||||
div = div(int1, int2)
|
||||
rem = int1 - div * int2
|
||||
@@ -351,12 +375,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 +402,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 +427,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,21 +476,22 @@ 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"
|
||||
@impl true
|
||||
@spec valid_date?(year, month, day) :: boolean
|
||||
def valid_date?(year, month, day) do
|
||||
month in 1..12 and year in -9999..9999 and day in 1..days_in_month(year, month)
|
||||
month in 1..12 and year in -9999..9999 and
|
||||
(is_integer(day) and day >= 1 and day <= days_in_month(year, month))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -476,12 +501,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"
|
||||
@@ -726,13 +751,30 @@ defmodule Calendar.ISO do
|
||||
if leap_year?(year), do: 1, else: 0
|
||||
end
|
||||
|
||||
defp days_to_year(days) when days < 0 do
|
||||
year_estimate = -div(-days, @days_per_nonleap_year) - 1
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_to_end_of_epoch(year_estimate))
|
||||
|
||||
leap_year_pad = if leap_year?(year), do: 1, else: 0
|
||||
{year, leap_year_pad + @days_per_nonleap_year + days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(days) do
|
||||
year = Integer.floor_div(days, @days_per_nonleap_year)
|
||||
{year, days_before_year} = days_to_year(year, days, days_in_previous_years(year))
|
||||
year_estimate = div(days, @days_per_nonleap_year)
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_in_previous_years(year_estimate))
|
||||
|
||||
{year, days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when days1 < days2 do
|
||||
defp days_to_year(year, days1, days2) when year < 0 and days1 >= days2 do
|
||||
days_to_year(year + 1, days1, days_to_end_of_epoch(year + 1))
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when year >= 0 and days1 < days2 do
|
||||
days_to_year(year - 1, days1, days_in_previous_years(year - 1))
|
||||
end
|
||||
|
||||
@@ -740,6 +782,13 @@ defmodule Calendar.ISO do
|
||||
{year, days2}
|
||||
end
|
||||
|
||||
defp days_to_end_of_epoch(year) when year < 0 do
|
||||
previous_year = year + 1
|
||||
|
||||
div(previous_year, 4) - div(previous_year, 100) + div(previous_year, 400) +
|
||||
previous_year * @days_per_nonleap_year
|
||||
end
|
||||
|
||||
defp days_in_previous_years(0), do: 0
|
||||
|
||||
defp days_in_previous_years(year) do
|
||||
@@ -807,7 +856,7 @@ defmodule Calendar.ISO do
|
||||
{date, time}
|
||||
end
|
||||
|
||||
defp seconds_to_time(seconds) when seconds in 0..(@seconds_per_day - 1) do
|
||||
defp seconds_to_time(seconds) when seconds in 0..@last_second_of_the_day do
|
||||
{hour, rest_seconds} = div_mod(seconds, @seconds_per_hour)
|
||||
{minute, second} = div_mod(rest_seconds, @seconds_per_minute)
|
||||
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -538,7 +538,7 @@ defmodule Inspect.Algebra do
|
||||
["hello", "\n ", "world"]
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@spec nest(t, non_neg_integer | :cursor | :reset, :always | :break) :: doc_nest
|
||||
def nest(doc, level, mode \\ :always)
|
||||
|
||||
def nest(doc, :cursor, mode) when is_doc(doc) and mode in [:always, :break] do
|
||||
@@ -640,7 +640,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec next_break_fits(t) :: doc_fits
|
||||
@spec next_break_fits(t, :enabled | :disabled) :: doc_fits
|
||||
def next_break_fits(doc, mode \\ @next_break_fits)
|
||||
when is_doc(doc) and mode in [:enabled, :disabled] do
|
||||
doc_fits(doc, mode)
|
||||
@@ -757,7 +757,7 @@ defmodule Inspect.Algebra do
|
||||
["Hello,", "\n", "A", "\n", "B"]
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
@spec group(t, :self | :inherit) :: doc_group
|
||||
def group(doc, mode \\ :self) when is_doc(doc) do
|
||||
doc_group(doc, mode)
|
||||
end
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+28
-18
@@ -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,
|
||||
@@ -3027,26 +3027,36 @@ defmodule Kernel do
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro first..last when is_integer(first) and is_integer(last) do
|
||||
defmacro first..last do
|
||||
case bootstrapped?(Macro) do
|
||||
true ->
|
||||
first = Macro.expand(first, __CALLER__)
|
||||
last = Macro.expand(last, __CALLER__)
|
||||
range(__CALLER__.context, first, last)
|
||||
|
||||
false ->
|
||||
range(__CALLER__.context, first, last)
|
||||
end
|
||||
end
|
||||
|
||||
defp range(_context, first, last) when is_integer(first) and is_integer(last) do
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
|
||||
defmacro first..last
|
||||
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
|
||||
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
|
||||
defp range(_context, first, last)
|
||||
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
|
||||
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
|
||||
raise ArgumentError,
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{Macro.to_string({:.., [], [first, last]})}"
|
||||
end
|
||||
|
||||
defmacro first..last do
|
||||
case __CALLER__.context do
|
||||
nil ->
|
||||
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
|
||||
defp range(nil, first, last) do
|
||||
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
defp range(_, first, last) do
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -3698,7 +3708,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 +3982,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
|
||||
|
||||
@@ -71,24 +71,24 @@ defmodule Kernel.Typespec do
|
||||
type_to_signature(expr) == signature
|
||||
end
|
||||
|
||||
:lists.any(finder, get_typespec(bag, :type))
|
||||
:lists.any(finder, get_typespecs(bag, [:type, :opaque, :typep]))
|
||||
end
|
||||
|
||||
def spec_to_callback(module, {name, arity} = signature)
|
||||
when is_atom(module) and is_atom(name) and arity in 0..255 do
|
||||
{_set, bag} = :elixir_module.data_tables(module)
|
||||
|
||||
filter = fn {expr, _} = object ->
|
||||
filter = fn {:spec, expr, pos} ->
|
||||
if spec_to_signature(expr) == signature do
|
||||
:ets.delete_object(bag, {:spec, object})
|
||||
store_typespec(bag, :callback, object)
|
||||
delete_typespec(bag, :spec, expr, pos)
|
||||
store_typespec(bag, :callback, expr, pos)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
:lists.filter(filter, get_typespec(bag, :spec)) != []
|
||||
:lists.filter(filter, get_typespecs(bag, :spec)) != []
|
||||
end
|
||||
|
||||
## Typespec definition and storage
|
||||
@@ -100,7 +100,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def deftypespec(:spec, expr, _line, _file, module, pos) do
|
||||
{_set, bag} = :elixir_module.data_tables(module)
|
||||
store_typespec(bag, :spec, {expr, pos})
|
||||
store_typespec(bag, :spec, expr, pos)
|
||||
end
|
||||
|
||||
def deftypespec(kind, expr, line, _file, module, pos)
|
||||
@@ -116,7 +116,7 @@ defmodule Kernel.Typespec do
|
||||
:error
|
||||
end
|
||||
|
||||
store_typespec(bag, kind, {expr, pos})
|
||||
store_typespec(bag, kind, expr, pos)
|
||||
end
|
||||
|
||||
def deftypespec(kind, expr, line, file, module, pos)
|
||||
@@ -143,17 +143,34 @@ defmodule Kernel.Typespec do
|
||||
:error
|
||||
end
|
||||
|
||||
store_typespec(bag, :type, {kind, expr, pos})
|
||||
store_typespec(bag, kind, expr, pos)
|
||||
end
|
||||
|
||||
defp get_typespec(bag, key) do
|
||||
:ets.lookup_element(bag, key, 2)
|
||||
defp get_typespecs(bag, keys) when is_list(keys) do
|
||||
:lists.flatmap(&get_typespecs(bag, &1), keys)
|
||||
end
|
||||
|
||||
defp get_typespecs(bag, key) do
|
||||
:ets.lookup_element(bag, {:accumulate, key}, 2)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
end
|
||||
|
||||
defp store_typespec(bag, key, value) do
|
||||
:ets.insert(bag, {key, value})
|
||||
defp take_typespecs(bag, keys) when is_list(keys) do
|
||||
:lists.flatmap(&take_typespecs(bag, &1), keys)
|
||||
end
|
||||
|
||||
defp take_typespecs(bag, key) do
|
||||
:lists.map(&elem(&1, 1), :ets.take(bag, {:accumulate, key}))
|
||||
end
|
||||
|
||||
defp store_typespec(bag, key, expr, pos) do
|
||||
:ets.insert(bag, {{:accumulate, key}, {key, expr, pos}})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_typespec(bag, key, expr, pos) do
|
||||
:ets.delete_object(bag, {{:accumulate, key}, {key, expr, pos}})
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -192,19 +209,15 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc false
|
||||
def translate_typespecs_for_module(_set, bag) do
|
||||
types = Enum.map(take_typespec(bag, :type), &translate_type/1)
|
||||
specs = Enum.map(take_typespec(bag, :spec), &translate_spec/1)
|
||||
callbacks = Enum.map(take_typespec(bag, :callback), &translate_spec/1)
|
||||
macrocallbacks = Enum.map(take_typespec(bag, :macrocallback), &translate_spec/1)
|
||||
optional_callbacks = List.flatten(get_typespec(bag, {:accumulate, :optional_callbacks}))
|
||||
types = Enum.map(take_typespecs(bag, [:type, :opaque, :typep]), &translate_type/1)
|
||||
specs = Enum.map(take_typespecs(bag, :spec), &translate_spec/1)
|
||||
callbacks = Enum.map(take_typespecs(bag, :callback), &translate_spec/1)
|
||||
macrocallbacks = Enum.map(take_typespecs(bag, :macrocallback), &translate_spec/1)
|
||||
optional_callbacks = List.flatten(get_typespecs(bag, :optional_callbacks))
|
||||
{types, specs, callbacks, macrocallbacks, optional_callbacks}
|
||||
end
|
||||
|
||||
defp take_typespec(bag, key) do
|
||||
:ets.take(bag, key)
|
||||
end
|
||||
|
||||
defp translate_type({_, {kind, {:::, _, [{name, _, args}, definition]}, pos}})
|
||||
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos})
|
||||
when is_atom(name) and name != ::: do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
|
||||
@@ -247,7 +260,7 @@ defmodule Kernel.Typespec do
|
||||
{kind, {name, arity}, caller.line, type, export}
|
||||
end
|
||||
|
||||
defp translate_type({_, {_kind, other, pos}}) do
|
||||
defp translate_type({_kind, other, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error(caller, "invalid type specification: #{type_spec}")
|
||||
@@ -259,12 +272,12 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp valid_variable_ast?(_), do: false
|
||||
|
||||
defp translate_spec({kind, {{:when, _meta, [spec, guard]}, pos}}) do
|
||||
defp translate_spec({kind, {:when, _meta, [spec, guard]}, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
defp translate_spec({kind, {spec, pos}}) do
|
||||
defp translate_spec({kind, spec, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
+30
-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 """
|
||||
@@ -1238,6 +1255,12 @@ defmodule Macro do
|
||||
next = :erlang.unique_integer()
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
|
||||
{:ok, Kernel, op, [arg]} when op in [:+, :-] ->
|
||||
case expand_once(arg, env) do
|
||||
integer when is_integer(integer) -> {apply(Kernel, op, [integer]), true}
|
||||
_ -> {original, false}
|
||||
end
|
||||
|
||||
{:ok, _receiver, _name, _args} ->
|
||||
{original, false}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1319,7 +1320,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_doc(table, line, kind, name, arity, args, body, doc, doc_meta, env, impl) do
|
||||
defp compile_doc(table, line, kind, name, arity, args, _body, doc, doc_meta, env, impl) do
|
||||
key = {doc_key(kind), name, arity}
|
||||
signature = build_signature(args, env)
|
||||
|
||||
@@ -1330,16 +1331,6 @@ defmodule Module do
|
||||
|
||||
[{_, current_line, current_sign, current_doc, current_doc_meta}] ->
|
||||
signature = merge_signatures(current_sign, signature, 1)
|
||||
|
||||
if is_binary(doc) and is_binary(current_doc) and not is_nil(body) do
|
||||
message =
|
||||
"redefining @doc attribute previously set at line #{current_line}. " <>
|
||||
"If you want to redefine a previously specified doc, " <>
|
||||
"use a definition without a body after the original documentation"
|
||||
|
||||
:elixir_errors.warn(line, env.file, message)
|
||||
end
|
||||
|
||||
doc = if is_nil(doc), do: current_doc, else: doc
|
||||
doc = if is_nil(doc) && impl, do: false, else: doc
|
||||
doc_meta = Map.merge(current_doc_meta, doc_meta)
|
||||
@@ -1873,9 +1864,9 @@ defmodule Module do
|
||||
do: {atom, :__on_definition__}
|
||||
|
||||
defp preprocess_attribute(key, _value)
|
||||
when key in [:type, :typep, :export_type, :opaque, :callback, :macrocallback] do
|
||||
when key in [:type, :typep, :export_type, :opaque, :spec, :callback, :macrocallback] do
|
||||
raise ArgumentError,
|
||||
"attributes type, typep, export_type, opaque, callback, and macrocallback" <>
|
||||
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
|
||||
"must be set directly via the @ notation"
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -389,13 +389,13 @@ defmodule String do
|
||||
|
||||
Note this function can split within or across grapheme boundaries.
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns `true`:
|
||||
"e" and the acute accent. The following returns the acute accent separately:
|
||||
|
||||
iex> String.split(String.normalize("é", :nfd), "e")
|
||||
["", "́"]
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return `false`:
|
||||
accent, then it will return "e with acute":
|
||||
|
||||
iex> String.split(String.normalize("é", :nfc), "e")
|
||||
["é"]
|
||||
@@ -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
|
||||
|
||||
@@ -564,10 +564,10 @@ defmodule System do
|
||||
iex> System.cmd("echo", ["hello"])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd("echo", ["hello"]), env: [{"MIX_ENV", "test"}]
|
||||
iex> System.cmd("echo", ["hello"], env: [{"MIX_ENV", "test"}])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd("echo", ["hello"]), into: IO.stream(:stdio, :line)
|
||||
iex> System.cmd("echo", ["hello"], into: IO.stream(:stdio, :line))
|
||||
hello
|
||||
{%IO.Stream{}, 0}
|
||||
|
||||
|
||||
@@ -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`
|
||||
@@ -36,7 +36,7 @@ defmodule Tuple do
|
||||
tuple = {:ok, :example}
|
||||
|
||||
# Avoid
|
||||
Tuple.insert_at(tuple, 2, %{}}
|
||||
Tuple.insert_at(tuple, 2, %{})
|
||||
|
||||
# Prefer
|
||||
{:ok, atom} = tuple
|
||||
|
||||
@@ -94,7 +94,9 @@ defmodule Version do
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t() | non_neg_integer]
|
||||
@type build :: String.t() | nil
|
||||
@type matchable :: {major :: major, minor :: minor, patch :: patch, pre :: pre}
|
||||
@type matchable ::
|
||||
{major :: major, minor :: minor, patch :: patch, pre :: pre,
|
||||
build_parts :: [String.t()]}
|
||||
@type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
|
||||
|
||||
defmodule Requirement do
|
||||
|
||||
@@ -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}.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ setup(DataTables) ->
|
||||
ok.
|
||||
|
||||
reset_last({DataSet, _DataBag}) ->
|
||||
ets:insert(DataSet, {?last_def, []});
|
||||
ets:insert(DataSet, {?last_def, none});
|
||||
|
||||
reset_last(Module) when is_atom(Module) ->
|
||||
reset_last(elixir_module:data_tables(Module)).
|
||||
|
||||
@@ -239,6 +239,6 @@ special_form('for', _) -> true;
|
||||
special_form('with', _) -> true;
|
||||
special_form('cond', 1) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('try', 1) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -211,7 +211,7 @@ build(Line, File, Module, Lexical) ->
|
||||
|
||||
%% In the bag table we store:
|
||||
%%
|
||||
%% * {{accumulate, Attribute}, ...}
|
||||
%% * {{accumulate, Attribute}, ...} (includes typespecs)
|
||||
%% * {attributes, ...}
|
||||
%% * {impls, ...}
|
||||
%% * {deprecated, ...}
|
||||
@@ -221,7 +221,6 @@ build(Line, File, Module, Lexical) ->
|
||||
%% * {{clauses, Tuple}, ...} (from elixir_def)
|
||||
%% * {reattach, ...} (from elixir_local)
|
||||
%% * {{local, Tuple}, ...} (from elixir_local)
|
||||
%% * {spec, ...}, {type, ...}, {callback, ...}, {macrocallback, ...}
|
||||
%%
|
||||
DataBag = ets:new(Module, [duplicate_bag, public]),
|
||||
|
||||
@@ -238,6 +237,12 @@ build(Line, File, Module, Lexical) ->
|
||||
{dialyzer, [], accumulate},
|
||||
{external_resource, [], accumulate},
|
||||
{on_definition, [], accumulate},
|
||||
{type, [], accumulate},
|
||||
{opaque, [], accumulate},
|
||||
{typep, [], accumulate},
|
||||
{spec, [], accumulate},
|
||||
{callback, [], accumulate},
|
||||
{macrocallback, [], accumulate},
|
||||
{on_load, [], accumulate},
|
||||
{optional_callbacks, [], accumulate},
|
||||
|
||||
@@ -331,7 +336,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 +409,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)]
|
||||
));
|
||||
|
||||
|
||||
@@ -3,4 +3,40 @@ Code.require_file("../test_helper.exs", __DIR__)
|
||||
defmodule Calendar.ISOTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest Calendar.ISO
|
||||
|
||||
describe "date_from_iso_days" do
|
||||
test "with positive dates" do
|
||||
assert {0, 1, 1} == iso_day_roundtrip(0, 1, 1)
|
||||
assert {0, 12, 31} == iso_day_roundtrip(0, 12, 31)
|
||||
assert {1, 12, 31} == iso_day_roundtrip(1, 12, 31)
|
||||
assert {4, 1, 1} == iso_day_roundtrip(4, 1, 1)
|
||||
assert {4, 12, 31} == iso_day_roundtrip(4, 12, 31)
|
||||
assert {9999, 12, 31} == iso_day_roundtrip(9999, 12, 31)
|
||||
assert {9999, 1, 1} == iso_day_roundtrip(9999, 1, 1)
|
||||
assert {9996, 12, 31} == iso_day_roundtrip(9996, 12, 31)
|
||||
assert {9996, 1, 1} == iso_day_roundtrip(9996, 1, 1)
|
||||
end
|
||||
|
||||
test "with negative dates" do
|
||||
assert {-1, 1, 1} == iso_day_roundtrip(-1, 1, 1)
|
||||
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
|
||||
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
|
||||
assert {-2, 1, 1} == iso_day_roundtrip(-2, 1, 1)
|
||||
assert {-5, 12, 31} == iso_day_roundtrip(-5, 12, 31)
|
||||
|
||||
assert {-4, 1, 1} == iso_day_roundtrip(-4, 1, 1)
|
||||
assert {-4, 12, 31} == iso_day_roundtrip(-4, 12, 31)
|
||||
|
||||
assert {-9999, 12, 31} == iso_day_roundtrip(-9999, 12, 31)
|
||||
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
|
||||
|
||||
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
|
||||
assert {-9996, 1, 1} == iso_day_roundtrip(-9996, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp iso_day_roundtrip(year, month, day) do
|
||||
iso_days = Calendar.ISO.date_to_iso_days(year, month, day)
|
||||
Calendar.ISO.date_from_iso_days(iso_days)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -293,8 +293,7 @@ defmodule DateTimeTest do
|
||||
end
|
||||
|
||||
test "from_iso8601/1 handles positive and negative offsets" do
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00")
|
||||
|> elem(1) ==
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
@@ -309,8 +308,7 @@ defmodule DateTimeTest do
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00")
|
||||
|> elem(1) ==
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
@@ -324,6 +322,98 @@ defmodule DateTimeTest do
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("0000-01-01T01:22:07+10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 12,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -1,
|
||||
zone_abbr: "UTC",
|
||||
day: 31,
|
||||
hour: 14,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
end
|
||||
|
||||
test "from_iso8601/1 handles negative dates" do
|
||||
assert DateTime.from_iso8601("-2015-01-24T09:50:07-10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2015,
|
||||
zone_abbr: "UTC",
|
||||
day: 24,
|
||||
hour: 19,
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-2015-01-24T09:50:07+10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2015,
|
||||
zone_abbr: "UTC",
|
||||
day: 23,
|
||||
hour: 23,
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-01-01T01:22:07+10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 12,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2,
|
||||
zone_abbr: "UTC",
|
||||
day: 31,
|
||||
hour: 14,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-01-01T01:22:07-10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -1,
|
||||
zone_abbr: "UTC",
|
||||
day: 1,
|
||||
hour: 11,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-12-31T23:22:07-10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: 0,
|
||||
zone_abbr: "UTC",
|
||||
day: 1,
|
||||
hour: 9,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
end
|
||||
|
||||
test "from_unix/2" do
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -503,6 +503,22 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "typespec attributes set via Module.put_attribute/4" do
|
||||
message =
|
||||
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
|
||||
"must be set directly via the @ notation"
|
||||
|
||||
for kind <- [:type, :typep, :opaque, :spec, :callback, :macrocallback] do
|
||||
assert_eval_raise ArgumentError,
|
||||
message,
|
||||
"""
|
||||
defmodule PutTypespecAttribute do
|
||||
Module.put_attribute(__MODULE__, #{inspect(kind)}, {})
|
||||
end
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid struct field value" do
|
||||
msg = ~r"invalid value for struct field baz, cannot escape "
|
||||
|
||||
|
||||
@@ -1109,34 +1109,6 @@ defmodule Kernel.WarningTest do
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "duplicated docs across multiple clauses" do
|
||||
output =
|
||||
capture_err(fn ->
|
||||
Code.eval_string("""
|
||||
defmodule Sample do
|
||||
@doc "Something"
|
||||
def foo(a)
|
||||
|
||||
@doc "Something"
|
||||
def foo(1), do: :ok
|
||||
|
||||
@doc "Something"
|
||||
def foo(2), do: :ok
|
||||
|
||||
@doc "Something"
|
||||
def foo(a)
|
||||
end
|
||||
""")
|
||||
end)
|
||||
|
||||
assert output =~ "redefining @doc attribute previously set at line 2"
|
||||
assert output =~ "nofile:5"
|
||||
assert output =~ "nofile:8"
|
||||
refute output =~ "nofile:11"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "reserved doc metadata keys" do
|
||||
output =
|
||||
capture_err(fn ->
|
||||
|
||||
@@ -440,9 +440,7 @@ defmodule KernelTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "is optimized" do
|
||||
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
|
||||
|
||||
test "hoists variables" do
|
||||
result = expand_to_string(quote(do: rand() in 1..2))
|
||||
assert result =~ "var = rand()"
|
||||
|
||||
@@ -463,6 +461,16 @@ defmodule KernelTest do
|
||||
assert result =~ ":erlang.orelse(:erlang.\"=:=\"(var, 1), :lists.member(var, var0))"
|
||||
end
|
||||
|
||||
test "is optimized" do
|
||||
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
|
||||
|
||||
assert expand_to_string(quote(do: foo in 0..1)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1)))"
|
||||
|
||||
assert expand_to_string(quote(do: foo in -1..0)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0)))"
|
||||
end
|
||||
|
||||
defp expand_to_string(ast) do
|
||||
ast
|
||||
|> Macro.prewalk(&Macro.expand(&1, __ENV__))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -85,7 +85,23 @@ defmodule ModuleTest do
|
||||
assert eval_quoted_info() == {ModuleTest, "sample.ex", 13}
|
||||
end
|
||||
|
||||
test "retrieves line from macros" do
|
||||
test "resets last definition information on eval" do
|
||||
# This should not emit any warning
|
||||
defmodule LastDefinition do
|
||||
def foo(0), do: 0
|
||||
|
||||
Module.eval_quoted(
|
||||
__ENV__,
|
||||
quote do
|
||||
def bar, do: :ok
|
||||
end
|
||||
)
|
||||
|
||||
def foo(1), do: 1
|
||||
end
|
||||
end
|
||||
|
||||
test "retrieves line from use callsite" do
|
||||
assert ModuleTest.ToUse.line() == 40
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -747,6 +747,136 @@ defmodule TypespecTest do
|
||||
assert [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]] = constraint_type
|
||||
end
|
||||
|
||||
test "@type, @opaque, and @typep as module attributes" do
|
||||
defmodule TypeModuleAttributes do
|
||||
@type type1 :: boolean
|
||||
@opaque opaque1 :: boolean
|
||||
@typep typep1 :: boolean
|
||||
|
||||
def type1, do: @type
|
||||
def opaque1, do: @opaque
|
||||
def typep1, do: @typep
|
||||
|
||||
@type type2 :: atom
|
||||
@type type3 :: pid
|
||||
@opaque opaque2 :: atom
|
||||
@opaque opaque3 :: pid
|
||||
@typep typep2 :: atom
|
||||
|
||||
def type2, do: @type
|
||||
def opaque2, do: @opaque
|
||||
def typep2, do: @typep
|
||||
|
||||
# Avoid unused warnings
|
||||
@spec foo(typep1) :: typep2
|
||||
def foo(_x), do: :ok
|
||||
end
|
||||
|
||||
assert [
|
||||
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.type1()
|
||||
|
||||
assert [
|
||||
{:type, {:::, _, [{:type3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:type, {:::, _, [{:type2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.type2()
|
||||
|
||||
assert [
|
||||
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.opaque1()
|
||||
|
||||
assert [
|
||||
{:opaque, {:::, _, [{:opaque3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:opaque, {:::, _, [{:opaque2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.opaque2()
|
||||
|
||||
assert [
|
||||
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.typep1()
|
||||
|
||||
assert [
|
||||
{:typep, {:::, _, [{:typep2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.typep2()
|
||||
after
|
||||
:code.delete(TypeModuleAttributes)
|
||||
:code.purge(TypeModuleAttributes)
|
||||
end
|
||||
|
||||
test "@spec, @callback, and @macrocallback as module attributes" do
|
||||
defmodule SpecModuleAttributes do
|
||||
@callback callback1 :: integer
|
||||
@macrocallback macrocallback1 :: integer
|
||||
|
||||
@spec spec1 :: boolean
|
||||
def spec1, do: @spec
|
||||
|
||||
@callback callback2 :: boolean
|
||||
@macrocallback macrocallback2 :: boolean
|
||||
|
||||
@spec spec2 :: atom
|
||||
def spec2, do: @spec
|
||||
|
||||
@spec spec3 :: pid
|
||||
def spec3, do: :ok
|
||||
def spec4, do: @spec
|
||||
|
||||
def callback, do: @callback
|
||||
def macrocallback, do: @macrocallback
|
||||
end
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec1()
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec2()
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec4()
|
||||
|
||||
assert [
|
||||
{:callback, {:::, _, [{:callback2, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:callback, {:::, _, [{:callback1, _, _}, {:integer, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.callback()
|
||||
|
||||
assert [
|
||||
{:macrocallback, {:::, _, [{:macrocallback2, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:macrocallback, {:::, _, [{:macrocallback1, _, _}, {:integer, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.macrocallback()
|
||||
after
|
||||
:code.delete(SpecModuleAttributes)
|
||||
:code.purge(SpecModuleAttributes)
|
||||
end
|
||||
|
||||
test "@callback(callback)" do
|
||||
bytecode =
|
||||
test_module do
|
||||
|
||||
@@ -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
|
||||
|
||||
+20
-12
@@ -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} ->
|
||||
@@ -781,7 +789,7 @@ defmodule Logger do
|
||||
metadata = Keyword.merge(caller, metadata)
|
||||
{metadata, metadata}
|
||||
else
|
||||
{metadata,
|
||||
{[],
|
||||
quote do
|
||||
Keyword.merge(unquote(caller), unquote(metadata))
|
||||
end}
|
||||
@@ -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
|
||||
@@ -252,11 +252,13 @@ defmodule LoggerTest do
|
||||
|
||||
test "remove unused calls at compile time based on matching metadata" do
|
||||
Logger.configure(
|
||||
compile_time_application: :sample_app,
|
||||
compile_time_purge_matching: [
|
||||
[module: LoggerTest.PurgeMatching, function: "two_filters/0"],
|
||||
[function: "one_filter/0"],
|
||||
[custom: true],
|
||||
[function: "level_filter/0", level_lower_than: :info]
|
||||
[function: "level_filter/0", level_lower_than: :warn],
|
||||
[application: :sample_app, level_lower_than: :info]
|
||||
]
|
||||
)
|
||||
|
||||
@@ -274,12 +276,16 @@ defmodule LoggerTest do
|
||||
end
|
||||
|
||||
def level_filter do
|
||||
Logger.debug("debug_filter")
|
||||
Logger.info("info_filter")
|
||||
Logger.warn("warn_filter")
|
||||
end
|
||||
|
||||
def works do
|
||||
Logger.debug("works")
|
||||
Logger.info("works")
|
||||
end
|
||||
|
||||
def log(level, metadata \\ []) do
|
||||
Logger.log(level, "ok", metadata)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -287,9 +293,13 @@ defmodule LoggerTest do
|
||||
assert capture_log(fn -> assert PurgeMatching.one_filter() == :ok end) == ""
|
||||
assert capture_log(fn -> assert PurgeMatching.two_filters() == :ok end) == ""
|
||||
assert capture_log(fn -> assert PurgeMatching.custom_filters() == :ok end) == ""
|
||||
assert capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "info_filter"
|
||||
refute capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "debug_filter"
|
||||
assert capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "warn_filter"
|
||||
refute capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "info_filter"
|
||||
|
||||
capture_log(fn -> assert PurgeMatching.log(:info) == :ok end)
|
||||
capture_log(fn -> assert PurgeMatching.log(:debug) == :ok end)
|
||||
after
|
||||
Logger.configure(compile_time_application: nil)
|
||||
Logger.configure(compile_time_purge_matching: [])
|
||||
end
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+24
-5
@@ -123,9 +123,28 @@ 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)
|
||||
seen = populate_seen(MapSet.new(), [app])
|
||||
children = get_deps(deps, tl(Enum.uniq(get_children(deps, seen, [app]))))
|
||||
|
||||
top_level =
|
||||
for dep <- deps,
|
||||
dep.app == app,
|
||||
child <- dep.deps,
|
||||
do: {child.app, Keyword.get(child.opts, :optional, false)},
|
||||
into: %{}
|
||||
|
||||
Enum.map(children, fn %{app: app, opts: opts} = dep ->
|
||||
# optional only matters at the top level. Any non-top level dependency
|
||||
# that is optional and is still available means it has been fulfilled.
|
||||
case top_level do
|
||||
%{^app => optional} ->
|
||||
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
|
||||
|
||||
%{} ->
|
||||
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp read_cached_deps(project, env) do
|
||||
@@ -153,8 +172,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 """
|
||||
|
||||
@@ -99,19 +99,25 @@ defmodule Mix.ProjectStack do
|
||||
|
||||
@spec config_mtime() :: [integer]
|
||||
def config_mtime() do
|
||||
get_and_update(fn state ->
|
||||
get_and_update_in(state.stack, fn
|
||||
[%{config_mtime: nil, config_files: files} = h | t] ->
|
||||
mtime = files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
|
||||
{mtime, [%{h | config_mtime: mtime} | t]}
|
||||
|
||||
[%{config_mtime: mtime} | _] = stack ->
|
||||
{mtime, stack}
|
||||
|
||||
[] ->
|
||||
{0, []}
|
||||
mtime_or_files =
|
||||
get(fn
|
||||
%{stack: [%{config_mtime: nil, config_files: files} | _]} -> files
|
||||
%{stack: [%{config_mtime: mtime} | _]} -> mtime
|
||||
%{stack: []} -> 0
|
||||
end)
|
||||
end)
|
||||
|
||||
if is_list(mtime_or_files) do
|
||||
mtime = mtime_or_files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
|
||||
|
||||
cast(fn state ->
|
||||
update_in(state.stack, fn [h | t] -> [%{h | config_mtime: mtime} | t] end)
|
||||
end)
|
||||
|
||||
mtime
|
||||
else
|
||||
mtime = mtime_or_files
|
||||
mtime
|
||||
end
|
||||
end
|
||||
|
||||
@spec config_apps() :: [atom]
|
||||
|
||||
@@ -113,8 +113,15 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
end
|
||||
|
||||
def build(_install_spec, _opts) do
|
||||
src = Mix.Local.name_for(:archive, Mix.Project.config())
|
||||
previous = find_previous_versions(src)
|
||||
|
||||
Enum.each(previous, fn path ->
|
||||
Code.delete_path(Mix.Local.archive_ebin(path))
|
||||
end)
|
||||
|
||||
Mix.Task.run("archive.build", [])
|
||||
Mix.Local.name_for(:archive, Mix.Project.config())
|
||||
src
|
||||
end
|
||||
|
||||
### Private helpers
|
||||
|
||||
@@ -208,7 +208,7 @@ defmodule Mix.Tasks.Compile.App do
|
||||
apps =
|
||||
properties
|
||||
|> Keyword.get(:applications)
|
||||
|> Kernel.||(apps_from_prod_non_optional_deps(properties))
|
||||
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|
||||
|> normalize_apps(extra, config)
|
||||
|
||||
Keyword.put(properties, :applications, apps)
|
||||
@@ -313,17 +313,32 @@ defmodule Mix.Tasks.Compile.App do
|
||||
end)
|
||||
end
|
||||
|
||||
defp apps_from_prod_non_optional_deps(properties) do
|
||||
defp apps_from_prod_non_optional_deps(properties, config) do
|
||||
included_applications = Keyword.get(properties, :included_applications, [])
|
||||
non_runtime_deps = non_runtime_deps(config)
|
||||
|
||||
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
|
||||
Keyword.get(opts, :app, true),
|
||||
Keyword.get(opts, :runtime, true),
|
||||
not Keyword.get(opts, :optional, false),
|
||||
not Map.has_key?(non_runtime_deps, app),
|
||||
app not in included_applications,
|
||||
do: app
|
||||
end
|
||||
|
||||
defp non_runtime_deps(config) do
|
||||
for config_dep <- Keyword.get(config, :deps, []),
|
||||
not runtime_dep?(config_dep),
|
||||
do: {elem(config_dep, 0), true},
|
||||
into: %{}
|
||||
end
|
||||
|
||||
defp runtime_dep?({_app, opts}) when is_list(opts), do: runtime_opts?(opts)
|
||||
defp runtime_dep?({_app, _req, opts}) when is_list(opts), do: runtime_opts?(opts)
|
||||
defp runtime_dep?(_), do: true
|
||||
|
||||
defp runtime_opts?(opts) do
|
||||
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true)
|
||||
end
|
||||
|
||||
defp normalize_apps(apps, extra, config) do
|
||||
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
|
||||
end
|
||||
|
||||
@@ -145,10 +145,10 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
|
||||
case Protocol.consolidate(protocol, impls) do
|
||||
{:ok, binary} ->
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
File.write!(Path.join(output, "#{Atom.to_string(protocol)}.beam"), binary)
|
||||
|
||||
if opts[:verbose] do
|
||||
Mix.shell().info("Consolidated #{inspect(protocol)}")
|
||||
Mix.shell().info("Consolidated #{inspect_protocol(protocol)}")
|
||||
end
|
||||
|
||||
# If we remove a dependency and we have implemented one of its
|
||||
@@ -161,11 +161,17 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
remove_consolidated(protocol, output)
|
||||
|
||||
if opts[:verbose] do
|
||||
Mix.shell().info("Unavailable #{inspect(protocol)}")
|
||||
Mix.shell().info("Unavailable #{inspect_protocol(protocol)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We cannot use the inspect protocol while consolidating
|
||||
# since inspect may not be available.
|
||||
defp inspect_protocol(protocol) do
|
||||
Code.Identifier.inspect_as_atom(protocol)
|
||||
end
|
||||
|
||||
defp reload(module) do
|
||||
:code.purge(module)
|
||||
:code.delete(module)
|
||||
@@ -227,6 +233,6 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
end
|
||||
|
||||
defp remove_consolidated(protocol, output) do
|
||||
File.rm(Path.join(output, "#{protocol}.beam"))
|
||||
File.rm(Path.join(output, "#{Atom.to_string(protocol)}.beam"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,8 +93,12 @@ defmodule Mix.Tasks.Deps do
|
||||
and override it by setting the `:override` option in a top-level project.
|
||||
|
||||
* `:runtime` - whether the dependency is part of runtime applications.
|
||||
Defaults to `true` which automatically adds the application to the list
|
||||
of apps that are started automatically and included in releases
|
||||
If the `:applications` key is not provided in `def application` in your
|
||||
mix.exs file, Mix will automatically included all dependencies as a runtime
|
||||
application, except if `runtime: false` is given. Defaults to true.
|
||||
|
||||
* `:system_env` - an enumerable of key-value tuples of binaries to be set
|
||||
as environment variables when loading or compiling the dependency
|
||||
|
||||
### Git options (`:git`)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
|
||||
* `--no-deps-check` - does not check or compile deps, only load available ones
|
||||
* `--no-compile` - does not compile dependencies
|
||||
* `--no-load-deps` - do not load deps from the code path
|
||||
|
||||
"""
|
||||
|
||||
@@ -26,14 +27,16 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
deps_check(all, "--no-compile" in args)
|
||||
end
|
||||
|
||||
load_paths =
|
||||
for dep <- all,
|
||||
path <- Mix.Dep.load_paths(dep) do
|
||||
_ = Code.prepend_path(path)
|
||||
path
|
||||
end
|
||||
unless "--no-load-deps" in args do
|
||||
load_paths =
|
||||
for dep <- all,
|
||||
path <- Mix.Dep.load_paths(dep) do
|
||||
_ = Code.prepend_path(path)
|
||||
path
|
||||
end
|
||||
|
||||
prune_deps(load_paths, "--no-deps-check" in args)
|
||||
prune_deps(load_paths, "--no-deps-check" in args)
|
||||
end
|
||||
end
|
||||
|
||||
# If the build is per environment, we should be able to look
|
||||
|
||||
@@ -382,7 +382,7 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp stdin_or_wildcard("-"), do: [:stdin]
|
||||
defp stdin_or_wildcard(path), do: Path.wildcard(path, match_dot: true)
|
||||
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
|
||||
|
||||
defp read_file(:stdin) do
|
||||
{IO.stream(:stdio, :line) |> Enum.to_list() |> IO.iodata_to_binary(), file: "stdin"}
|
||||
@@ -447,7 +447,7 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp check!({[{:exit, file, exception, stacktrace} | _], _not_equivalent, _not_formatted}) do
|
||||
Mix.shell().error("mix format failed for file: #{file}")
|
||||
Mix.shell().error("mix format failed for file: #{Path.relative_to_cwd(file)}")
|
||||
reraise exception, stacktrace
|
||||
end
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ defmodule Mix.Tasks.Help do
|
||||
aliases = Enum.filter(load_aliases(), fn {name, _} -> String.contains?(name, pattern) end)
|
||||
|
||||
{docs, max} = build_doc_list(modules, aliases)
|
||||
|
||||
display_doc_list(docs, max)
|
||||
end
|
||||
|
||||
@@ -86,25 +85,39 @@ defmodule Mix.Tasks.Help do
|
||||
def run([task]) do
|
||||
loadpaths!()
|
||||
|
||||
{doc, location} = verbose_doc(task)
|
||||
opts = Application.get_env(:mix, :colors)
|
||||
|
||||
if ansi_docs?(opts) do
|
||||
opts = [width: width()] ++ opts
|
||||
IO.ANSI.Docs.print_heading("mix #{task}", opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.puts("# mix #{task}\n")
|
||||
IO.puts(doc)
|
||||
end
|
||||
opts =
|
||||
if ansi_docs?(opts) do
|
||||
[width: width()] ++ opts
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
IO.puts("Location: #{location}")
|
||||
for doc <- verbose_doc(task) do
|
||||
print_doc(task, doc, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("Unexpected arguments, expected \"mix help\" or \"mix help TASK\"")
|
||||
end
|
||||
|
||||
defp print_doc(task, {doc, location, note}, opts) do
|
||||
if ansi_docs?(opts) do
|
||||
opts = [width: width()] ++ opts
|
||||
IO.ANSI.Docs.print_heading("mix #{task}", opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
IO.puts("Location: #{location}")
|
||||
note && IO.puts("") && IO.ANSI.Docs.print(note, opts)
|
||||
else
|
||||
IO.puts("# mix #{task}\n")
|
||||
IO.puts(doc)
|
||||
IO.puts("\nLocation: #{location}")
|
||||
note && IO.puts([?\n, note, ?\n, ?\n])
|
||||
end
|
||||
end
|
||||
|
||||
# Loadpaths without checks because tasks may be defined in deps.
|
||||
defp loadpaths! do
|
||||
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
@@ -194,14 +207,29 @@ defmodule Mix.Tasks.Help do
|
||||
defp verbose_doc(task) do
|
||||
aliases = load_aliases()
|
||||
|
||||
if Map.has_key?(aliases, task) do
|
||||
task_name = aliases[task]
|
||||
doc = "Alias for " <> inspect(task_name)
|
||||
{doc, "mix.exs"}
|
||||
else
|
||||
module = Mix.Task.get!(task)
|
||||
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
|
||||
{doc, where_is_file(module)}
|
||||
has_alias? = Map.has_key?(aliases, task)
|
||||
has_task? = Mix.Task.get(task)
|
||||
|
||||
cond do
|
||||
has_alias? and has_task? ->
|
||||
note = "There is also a task named \"#{task}\". The documentation is shown next."
|
||||
[alias_doc(aliases[task], note), task_doc(task)]
|
||||
|
||||
has_alias? ->
|
||||
[alias_doc(aliases[task], nil)]
|
||||
|
||||
true ->
|
||||
[task_doc(task)]
|
||||
end
|
||||
end
|
||||
|
||||
defp alias_doc(task_name, note) do
|
||||
{"Alias for " <> inspect(task_name), "mix.exs", note}
|
||||
end
|
||||
|
||||
defp task_doc(task) do
|
||||
module = Mix.Task.get!(task)
|
||||
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
|
||||
{doc, where_is_file(module), nil}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule Mix.Tasks.Test do
|
||||
|
||||
def start(compile_path, opts) do
|
||||
Mix.shell().info("Cover compiling modules ...")
|
||||
_ = :cover.stop()
|
||||
_ = :cover.start()
|
||||
|
||||
case :cover.compile_beam_directory(compile_path |> to_charlist) do
|
||||
@@ -145,7 +146,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 +257,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.
|
||||
@@ -401,7 +408,7 @@ defmodule Mix.Tasks.Test do
|
||||
Mix.shell().info("No stale tests")
|
||||
|
||||
files == [] ->
|
||||
raise_or_error_at_exit("There are no tests to run", opts)
|
||||
Mix.shell().info("There are no tests to run")
|
||||
|
||||
true ->
|
||||
message = "Paths given to `mix test` did not match any directory/file: "
|
||||
|
||||
@@ -176,7 +176,7 @@ defmodule Mix.Utils do
|
||||
(1970-01-01 00:00:00).
|
||||
"""
|
||||
def last_modified_and_size(path) do
|
||||
now = System.system_time(:second)
|
||||
now = System.os_time(:second)
|
||||
|
||||
case :elixir_utils.read_posix_mtime_and_size(path) do
|
||||
{:ok, mtime, size} when mtime > now ->
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
defmodule Bar do
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Bar.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :bar,
|
||||
version: "0.1.0",
|
||||
# Choose something besides *_test.exs so that these test files don't
|
||||
# get accidentally swept up into the actual Mix test suite.
|
||||
test_pattern: "*_tests.exs"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule BarTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "greets the world" do
|
||||
assert Foo.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
@@ -0,0 +1,5 @@
|
||||
defmodule Foo do
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Foo.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :foo,
|
||||
version: "0.1.0",
|
||||
# Choose something besides *_test.exs so that these test files don't
|
||||
# get accidentally swept up into the actual Mix test suite.
|
||||
test_pattern: "*_tests.exs"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule FooTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "greets the world" do
|
||||
assert Foo.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
@@ -0,0 +1,9 @@
|
||||
defmodule UmbrellaCover.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
apps_path: "apps"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -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,72 @@ 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 optional matching" do
|
||||
Process.put(:custom_deps_git_repo_opts, optional: true)
|
||||
|
||||
# deps_repo brings git_repo but it is optional
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_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 and optional
|
||||
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
|
||||
true = Keyword.fetch!(opts, :optional)
|
||||
""")
|
||||
|
||||
Mix.Tasks.Deps.Get.run([])
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps considers runtime from current app" do
|
||||
Process.put(:custom_deps_git_repo_opts, runtime: false)
|
||||
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
|
||||
]
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
|
||||
{:ok, [{:application, :deps_repo, opts}]} =
|
||||
:file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app")
|
||||
|
||||
assert :git_repo not in Keyword.get(opts, :applications)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with overrides" do
|
||||
# deps_repo brings git_repo but it is overriden
|
||||
deps = [
|
||||
|
||||
@@ -183,6 +183,10 @@ defmodule Mix.Tasks.ArchiveTest do
|
||||
send(self(), {:mix_shell_input, :yes?, true})
|
||||
Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
|
||||
refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
|
||||
|
||||
# Check old paths are unloaded
|
||||
paths = Enum.map(:code.get_path(), &List.to_string/1)
|
||||
refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -242,6 +242,36 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "does not load or prune builds with --no-load-deps" do
|
||||
Mix.Project.push(SuccessfulDepsApp)
|
||||
|
||||
in_fixture("deps_status", fn ->
|
||||
# Start from scratch!
|
||||
File.rm_rf("_build")
|
||||
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/ok/priv/sample")
|
||||
|
||||
Mix.Tasks.Compile.run([])
|
||||
assert to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
|
||||
# Remove the deps without build_path
|
||||
Mix.ProjectStack.post_config(deps: [])
|
||||
Mix.ProjectStack.clear_cache()
|
||||
Mix.Project.pop()
|
||||
Mix.Project.push(SuccessfulDepsApp)
|
||||
Code.delete_path("_build/dev/lib/ok/ebin")
|
||||
|
||||
Mix.Tasks.Deps.Loadpaths.run(["--no-load-deps"])
|
||||
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
end)
|
||||
end
|
||||
|
||||
## deps.unlock
|
||||
|
||||
test "unlocks all deps", context do
|
||||
|
||||
@@ -136,6 +136,33 @@ defmodule Mix.Tasks.HelpTest do
|
||||
end)
|
||||
end
|
||||
|
||||
defmodule ShadowedAliases do
|
||||
def project do
|
||||
[aliases: [compile: "compile"]]
|
||||
end
|
||||
end
|
||||
|
||||
test "help TASK && ALIAS", context do
|
||||
Mix.Project.push(ShadowedAliases)
|
||||
|
||||
in_tmp(context.test, fn ->
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Mix.Tasks.Help.run(["compile"])
|
||||
end)
|
||||
|
||||
assert output =~ "# mix compile\n\n"
|
||||
assert output =~ "Alias for \"compile\"\n"
|
||||
assert output =~ ~r/^Location: mix.exs/m
|
||||
|
||||
assert output =~
|
||||
"\nThere is also a task named \"compile\". The documentation is shown next.\n"
|
||||
|
||||
assert output =~ "## Command line options"
|
||||
assert output =~ ~r/^Location:/m
|
||||
end)
|
||||
end
|
||||
|
||||
test "help --search PATTERN", context do
|
||||
in_tmp(context.test, fn ->
|
||||
Mix.Tasks.Help.run(["--search", "deps"])
|
||||
|
||||
@@ -61,6 +61,12 @@ defmodule Mix.Tasks.LoadconfigTest do
|
||||
Mix.Task.run("loadconfig", [config])
|
||||
assert config in Mix.Project.config_files()
|
||||
assert Mix.Project.config_mtime() > mtime
|
||||
|
||||
# Touching it should not have any deadlocks
|
||||
File.touch!(config, {{2030, 1, 1}, {0, 0, 0}})
|
||||
Mix.Task.run("loadconfig", [config])
|
||||
assert config in Mix.Project.config_files()
|
||||
assert Mix.Project.config_mtime() > mtime
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -123,6 +123,27 @@ defmodule Mix.Tasks.TestTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "--cover: in an umbrella application, reports on the coverage of each app's modules exactly once" do
|
||||
in_fixture("umbrella_cover", fn ->
|
||||
output = mix(["test", "--cover"])
|
||||
|
||||
occurrences_of_bar_coverage =
|
||||
output
|
||||
|> String.split("100.00% | Bar")
|
||||
|> length()
|
||||
|> (&(&1 - 1)).()
|
||||
|
||||
occurrences_of_foo_coverage =
|
||||
output
|
||||
|> String.split("100.00% | Foo")
|
||||
|> length()
|
||||
|> (&(&1 - 1)).()
|
||||
|
||||
assert occurrences_of_bar_coverage == 1
|
||||
assert occurrences_of_foo_coverage == 1
|
||||
end)
|
||||
end
|
||||
|
||||
test "--failed: loads only files with failures and runs just the failures" do
|
||||
in_fixture("test_failed", fn ->
|
||||
loading_only_passing_test_msg = "loading OnlyPassingTest"
|
||||
|
||||
Reference in New Issue
Block a user