Compare commits

...
64 Commits
Author SHA1 Message Date
José Valim 8ca85ed2a8 Release v1.7.3 2018-08-24 20:46:27 +02:00
José Valim af64fe7e98 Consider :runtime and :app from mix.exs (#8109)
Since those values are not stored on Hex, we should not
expect them to be available on Mix.Dep.cached. Instead
we traverse the deps in `mix.exs` to lift this information.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-24 20:13:26 +02:00
Fernando Tapia Rico 28cf40fe03 Fix arity of try/1 in :elixir_import.special_form (#8079)
Fixes #8078

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-14 12:57:12 +02:00
José Valim d38a189907 Do not use protocols while consolidating
This avoids a race condition in tests.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-06 07:47:22 +02:00
José Valim 2b338092b6 Release v1.7.2 2018-08-05 09:01:26 +02:00
Scott Riley 557b779bd4 Move parentheses in System.cmd() docs (#8046) 2018-08-04 18:44:11 +02:00
José Valim ece40480f6 Update CHANGELOG 2018-08-04 16:12:28 +02:00
José Valim a77445c46b Revert "Fix errors with ref/4 IEx helper (#8016)"
This reverts commit b9d6f4f9c1.
2018-08-04 15:37:15 +02:00
José Valim 4d55fa6814 Accordingly mark top level optional dependencies from child deps (#8035)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 14:08:33 +02:00
Fernando Tapia Rico b9d6f4f9c1 Fix errors with ref/4 IEx helper (#8016)
* Fix errors with ref/4 IEx helper

In addition, include documentation for that function.

* Update helpers.ex

* Add IEx.Helpers.ref/* tests

* Use examples that do not return ArgumentError

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 14:07:37 +02:00
Fernando Tapia Rico b0cf76c74c Fix year in negative datetimes with offset (#8038) 2018-08-04 13:18:41 +02:00
José Valim 7841fbb8e0 Do not fail suite if there are no tests to run
This matters in umbrella apps where some apps may not
have Elixir tests.

Closes #8040

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 00:46:04 +02:00
José Valim f5773fed42 Take negative years into account in DateTime (#8033)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-03 11:39:30 +02:00
José Valim e2b8ab9a0d Unify alias doc handling
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 23:20:35 +02:00
Dave Lucia 1dc6d10f30 mix help when theres a task and alias (#8020)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 23:20:30 +02:00
José Valim e7138fb2c6 Avoid Mix.ProjectStack deadlock, closes #8024
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 21:29:45 +02:00
José Valim 04ecc05adc Remove warnings for docs in the same clause
Unfortunately this leads to false positives when trying
to override the docs of an overridable definition.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-28 21:33:46 +02:00
José Valim 8aab53b941 Release v1.7.1 2018-07-26 21:33:18 +02:00
Michał Muskała 07c276ab5c Fix issues with dialyzer infinite loop (#7993)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-26 21:30:35 +02:00
David Cuddeback 89e3a81adc Skip intermediate parts when creating NaiveDateTime 2018-07-26 11:37:43 +02:00
David Cuddeback 9752f1ce05 Optimize ISO-8601 parsing for UTC 2018-07-26 11:37:43 +02:00
José Valim 06e5ec2d4c Release v1.7.0 2018-07-25 22:36:59 +02:00
José Valim b6d3f29b69 Allow to prune metadata while escaping (#7949)
We use this option in ExUnit as we are escaping
the code to eventually convert it to a string
representation and therefore the metadata is not
relevant.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Closes #7889

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 20:15:08 +02:00
László Bácsi ca82388792 Show documentation metadata in IEx (only since and deprecated for now) (#7886) 2018-07-12 20:07:45 +02:00
Lukasz Samson b33dd12d86 Fix error when :trim_bom is used with :encoding
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 14:37:14 +02:00
Dimitrios Zorbas c36b2c9070 Fix syntax highlighting of Calendar.ISO docs (#7880)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 12:44:41 +02:00
José Valim ac75cdbe05 Prepare for v1.7 release 2018-07-12 11:57:29 +02:00
69 changed files with 1137 additions and 414 deletions
+58 -2
View File
@@ -2,6 +2,21 @@
Elixir v1.7 is the last release to support Erlang/OTP 19. We recommend everyone to migrate to Erlang/OTP 20+.
## Documentation metadata
Elixir v1.7 implements [EEP 48](http://erlang.org/eep/eeps/eep-0048.html). EEP 48 aims to bring documentation interoperability across all languages running on the Erlang VM. The documentation format proposed by EEP 48 also supports metadata, which is now fully exposed to Elixir developers:
```elixir
@moduledoc "A brand new module"
@moduledoc authors: ["Jane", "Mary"], since: "1.4.0"
```
Passing metadata is supported on `@doc`, `@moduledoc` and `@typedoc`.
To access the new documentation format, developers should use `Code.fetch_docs/1`. The old documentation format is no longer available and the old `Code.get_docs/2` function will return `nil` accordingly.
Tools like IEx and ExDoc have been updated to leverage the new format and show relevant metadata to users. While Elixir allows any metadata to be given, those tools currently exhibit only `:deprecated` and `:since`. Other keys may be shown in the future.
## The `__STACKTRACE__` construct
Erlang/OTP 21.0 introduces a new way to retrieve the stacktrace that is lexically scoped and no longer relies on side-effects like `System.stacktrace/0` does. Before one would write:
@@ -81,7 +96,7 @@ ExUnit has also seen its own share of improvements. Assertions such as `assert s
Furthermore, failures in doctests are now colored and diffed.
On the `mix test` side of things, there is a new `--failed` flag that runs all tests that failed in the previous run. Finally, coverage reports generated with `mix test --cover` include a summary out of the box:
On the `mix test` side of things, there is a new `--failed` flag that runs all tests that failed the last time they ran. Finally, coverage reports generated with `mix test --cover` include a summary out of the box:
```
Generating cover results ...
@@ -101,7 +116,43 @@ Percentage | Module
77.19% | Total
```
## v1.7.0-dev
## 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 +170,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 +184,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 +199,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 +234,7 @@ Percentage | Module
* [Code] Ensure `string_to_quoted` returns error tuples instead of raising in certain constructs
* [Code.Formatter] Consistently format keyword lists in function calls with and without parens
* [Code.Formatter] Do not break after `->` when there are only comments and one-line clauses
* [File] Allow the `:trim_bom` option to be used with `:encoding`
* [Kernel] Raise on unsafe variables as some of the code emitted with unsafe variables would not correctly propagate variables or would disable tail call optimization semantics
* [Kernel] Do not crash on dynamic sizes in binary generators with collectable into in comprehensions
* [Kernel] Do not crash on literals with non-unary size in binary generators with collectable into in comprehensions
+9 -1
View File
@@ -1,6 +1,6 @@
PREFIX ?= /usr/local
SHARE_PREFIX ?= $(PREFIX)/share
CANONICAL := master/
CANONICAL := v1.7/
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -192,6 +192,14 @@ Precompiled.zip: build_man compile
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
zips: Precompiled.zip Docs.zip
@ echo ""
@ echo "## Checksums"
@ echo ""
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
@ echo ""
#==> Test tasks
+1 -1
View File
@@ -1 +1 @@
1.7.0-dev
1.7.3
+1
View File
@@ -14,6 +14,7 @@
DateTime,
Exception,
Float,
Function,
Integer,
NaiveDateTime,
Record,
+35
View File
@@ -404,6 +404,41 @@ defmodule Application do
If the configuration parameter does not exist, the function returns the
`default` value.
## Examples
`get_env/3` is commonly used to read the configuration of your OTP applications.
Since Mix configurations are commonly used to configure applications, we will use
this as a point of illustration.
Consider a new application `:my_app`. `:my_app` contains a database engine which
supports a pool of databases. The database engine needs to know the configuration for
each of those databases, and that configuration is supplied by key-value pairs in
environment of `:my_app`.
config :my_app, Databases.RepoOne,
# A database configuration
ip: "localhost"
port: 5433
config :my_app, Databases.RepoTwo,
# Another database configuration (for the same OTP app)
ip: "localhost"
port: 20717
config :my_app, my_app_databases: [Databases.RepoOne, Databases.RepoTwo]
Our database engine used by `:my_app` needs to know what databases exist, and
what the database configurations are. The database engine can make a call to
`get_env(:my_app, :my_app_databases)` to retrieve the list of databases (specified
by module names). Our database engine can then traverse each repository in the
list and then call `get_env(:my_app, Databases.RepoOne)` and so forth to retrieve
the configuration of each one.
**Important:** if you are writing a library to be used by other developers,
it is generally recommended to avoid the application environment, as the
application environment is effectively a global storage. For more information,
read our [library guidelines](/library-guidelines.html).
"""
@spec get_env(app, key, value) :: value
def get_env(app, key, default \\ nil) do
+19 -14
View File
@@ -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 """
+79 -43
View File
@@ -17,7 +17,7 @@ defmodule DateTime do
Such functions expect `t:Calendar.datetime/0` in their typespecs
(instead of `t:t/0`).
Developers should avoid creating the DateTime struct directly
Developers should avoid creating the `DateTime` struct directly
and instead rely on the functions provided by this module as
well as the ones in 3rd party calendar libraries.
@@ -25,12 +25,12 @@ defmodule DateTime do
You will notice this module only contains conversion
functions as well as functions that work on UTC. This
is because a proper DateTime implementation requires a
TimeZone database which currently is not provided as part
is because a proper `DateTime` implementation requires a
time zone database which currently is not provided as part
of Elixir.
Such may be addressed in upcoming versions, meanwhile,
use 3rd party packages to provide DateTime building and
use 3rd party packages to provide `DateTime` building and
similar functionality with time zone backing.
"""
@@ -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
+65 -32
View File
@@ -36,6 +36,39 @@ defmodule Calendar.ISO do
@months_in_year 12
@doc false
def __match_date__ do
quote do
[
<<y1, y2, y3, y4, ?-, m1, m2, ?-, d1, d2>>,
y1 >= ?0 and y1 <= ?9 and y2 >= ?0 and y2 <= ?9 and y3 >= ?0 and y3 <= ?9 and y4 >= ?0 and
y4 <= ?9 and m1 >= ?0 and m1 <= ?9 and m2 >= ?0 and m2 <= ?9 and d1 >= ?0 and d1 <= ?9 and
d2 >= ?0 and d2 <= ?9,
{
(y1 - ?0) * 1000 + (y2 - ?0) * 100 + (y3 - ?0) * 10 + (y4 - ?0),
(m1 - ?0) * 10 + (m2 - ?0),
(d1 - ?0) * 10 + (d2 - ?0)
}
]
end
end
@doc false
def __match_time__ do
quote do
[
<<h1, h2, ?:, i1, i2, ?:, s1, s2>>,
h1 >= ?0 and h1 <= ?9 and h2 >= ?0 and h2 <= ?9 and i1 >= ?0 and i1 <= ?9 and i2 >= ?0 and
i2 <= ?9 and s1 >= ?0 and s1 <= ?9 and s2 >= ?0 and s2 <= ?9,
{
(h1 - ?0) * 10 + (h2 - ?0),
(i1 - ?0) * 10 + (i2 - ?0),
(s1 - ?0) * 10 + (s2 - ?0)
}
]
end
end
@doc """
Returns the `t:Calendar.iso_days/0` format of the specified date.
@@ -351,12 +384,12 @@ defmodule Calendar.ISO do
## Examples
iex> Calendar.ISO.date_to_string(2015, 2, 28)
"2015-02-28"
iex> Calendar.ISO.date_to_string(2017, 8, 1)
"2017-08-01"
iex> Calendar.ISO.date_to_string(-99, 1, 31)
"-0099-01-31"
iex> Calendar.ISO.date_to_string(2015, 2, 28)
"2015-02-28"
iex> Calendar.ISO.date_to_string(2017, 8, 1)
"2017-08-01"
iex> Calendar.ISO.date_to_string(-99, 1, 31)
"-0099-01-31"
"""
@spec date_to_string(year, month, day) :: String.t()
@@ -378,10 +411,10 @@ defmodule Calendar.ISO do
## Examples
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
"2015-02-28 01:02:03.000004"
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
"2017-08-01 01:02:03.00000"
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
"2015-02-28 01:02:03.000004"
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
"2017-08-01 01:02:03.00000"
"""
@impl true
@@ -403,14 +436,14 @@ defmodule Calendar.ISO do
## Examples
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
"""
@impl true
@@ -452,14 +485,14 @@ defmodule Calendar.ISO do
## Examples
iex> Calendar.ISO.valid_date?(2015, 2, 28)
true
iex> Calendar.ISO.valid_date?(2015, 2, 30)
false
iex> Calendar.ISO.valid_date?(-1, 12, 31)
true
iex> Calendar.ISO.valid_date?(-1, 12, 32)
false
iex> Calendar.ISO.valid_date?(2015, 2, 28)
true
iex> Calendar.ISO.valid_date?(2015, 2, 30)
false
iex> Calendar.ISO.valid_date?(-1, 12, 31)
true
iex> Calendar.ISO.valid_date?(-1, 12, 32)
false
"""
@doc since: "1.5.0"
@@ -476,12 +509,12 @@ defmodule Calendar.ISO do
## Examples
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
true
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
true
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
false
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
true
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
true
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
false
"""
@doc since: "1.5.0"
+49 -19
View File
@@ -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
+17 -13
View File
@@ -177,7 +177,7 @@ defmodule Time do
Parses the extended "Local time" format described by
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
Timezone offset may be included in the string but they will be
Time zone offset may be included in the string but they will be
simply discarded as such information is not included in times.
As specified in the standard, the separator "T" may be omitted if
@@ -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).
+2 -1
View File
@@ -1121,7 +1121,8 @@ defmodule Code do
longer available, and therefore this function always returns `nil`.
Use `Code.fetch_docs/1` instead.
"""
@deprecated "Code.get_docs/2 always returns nil as its outdated documentation is no longer stored on BEAM files. Use Code.fetch_docs/1 instead"
@doc deprecated:
"Code.get_docs/2 always returns nil as its outdated documentation is no longer stored on BEAM files. Use Code.fetch_docs/1 instead"
@spec get_docs(module, :moduledoc | :docs | :callback_docs | :type_docs | :all) :: nil
def get_docs(_module, _kind) do
nil
+15 -8
View File
@@ -716,14 +716,21 @@ defmodule Code.Formatter do
{concat(op_string, doc), @empty, newlines, state}
end
operand_to_algebra_with_comments(
operands,
meta,
min_line,
max_line,
state,
operand_to_algebra
)
{doc, state} =
operand_to_algebra_with_comments(
operands,
meta,
min_line,
max_line,
state,
operand_to_algebra
)
if keyword?(right_arg) and context in [:parens_arg, :no_parens_arg] do
{wrap_in_parens(doc), state}
else
{doc, state}
end
end
defp binary_op_to_algebra(op, _, meta, left_arg, right_arg, context, state, _nesting)
+1 -1
View File
@@ -1163,7 +1163,7 @@ defmodule KeyError do
end
defp map_with_atom_keys_only?(term) do
is_map(term) and Enum.all?(term, fn {k, _} -> is_atom(k) end)
is_map(term) and Enum.all?(Map.to_list(term), fn {k, _} -> is_atom(k) end)
end
defp available_keys(term) when is_map(term), do: Map.keys(term)
+5 -1
View File
@@ -1512,7 +1512,8 @@ defmodule File do
in raw mode for performance reasons. Therefore, Elixir **will** open
streams in `:raw` mode with the `:read_ahead` option unless an encoding
is specified. This means any data streamed into the file must be
converted to `t:iodata/0` type. If you pass `[:utf8]` in the modes parameter,
converted to `t:iodata/0` type. If you pass e.g. `[encoding: :utf8]`
or `[encoding: {:utf16, :little}]` in the modes parameter,
the underlying stream will use `IO.write/2` and the `String.Chars` protocol
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
@@ -1524,6 +1525,9 @@ defmodule File do
If you pass `:trim_bom` in the modes parameter, the stream will
trim UTF-8, UTF-16 and UTF-32 byte order marks when reading from file.
Note that this function does not try to discover the file encoding basing
on BOM.
## Examples
# Read in 2048 byte chunks rather than lines
+15 -3
View File
@@ -140,12 +140,24 @@ defmodule File.Stream do
end
end
defp trim_bom(device, raw) do
header = if raw, do: IO.binread(device, 4), else: IO.read(device, 1)
{:ok, new_pos} = :file.position(device, bom_length(header))
defp trim_bom(device, true) do
bom_length = device |> IO.binread(4) |> bom_length()
{:ok, new_pos} = :file.position(device, bom_length)
{device, new_pos}
end
defp trim_bom(device, false) do
# Or we read the bom in the correct amount or it isn't there
case bom_length(IO.read(device, 1)) do
0 ->
{:ok, _} = :file.position(device, 0)
{device, 0}
_ ->
{device, 1}
end
end
defp bom_length(<<239, 187, 191, _rest::binary>>), do: 3
defp bom_length(<<254, 255, _rest::binary>>), do: 2
defp bom_length(<<255, 254, _rest::binary>>), do: 2
+1 -1
View File
@@ -33,7 +33,7 @@ defmodule Float do
There are also other known problems like flooring or rounding numbers. See
`round/2` and `floor/2` for more details about them.
To learn more about floting-point arithmetic visit:
To learn more about floating-point arithmetic visit:
* [0.30000000000000004.com](http://0.30000000000000004.com/)
* [What Every Programmer Should Know About Floating-Point Arithmetic](http://floating-point-gui.de/)
+36 -1
View File
@@ -7,12 +7,13 @@ defmodule IO.ANSI.Docs do
@doc """
The default options used by this module.
The supported values are:
The supported keys are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_bold` - bold text (bright)
* `:doc_code` - code blocks (cyan)
* `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow)
* `:doc_metadata` - documentation metadata keys (yellow)
* `:doc_inline_code` - inline code (cyan)
* `:doc_table_heading` - the style for table headings
* `:doc_title` - top level heading (reverse, yellow)
@@ -29,6 +30,7 @@ defmodule IO.ANSI.Docs do
doc_bold: [:bright],
doc_code: [:cyan],
doc_headings: [:yellow],
doc_metadata: [:yellow],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow],
@@ -53,6 +55,39 @@ defmodule IO.ANSI.Docs do
newline_after_block()
end
@doc """
Prints documentation metadata (only `since` and `deprecated` for now).
See `default_options/0` for docs on the supported options.
"""
@spec print_metadata(map, keyword) :: :ok
def print_metadata(metadata, options \\ []) when is_map(metadata) do
options = Keyword.merge(default_options(), options)
print_each_metadata(metadata, options) && IO.write("\n")
end
@metadata_filter [:deprecated, :since]
defp print_each_metadata(metadata, options) do
Enum.reduce(metadata, false, fn
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
label = metadata_label(key, options)
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true)
_metadata, printed ->
printed
end)
end
defp metadata_label(key, options) do
if options[:enabled] do
"#{color(:doc_metadata, options)}#{key}:#{IO.ANSI.reset()}"
else
"#{key}:"
end
end
@doc """
Prints the documentation body.
+7 -7
View File
@@ -27,7 +27,7 @@ defmodule Kernel do
If you don't want to import a function or macro from `Kernel`, use the `:except`
option and then list the function/macro by arity:
import Kernel, except: [if: 2, unless: 2]
import Kernel, except: [if: 2, unless: 2]
See `Kernel.SpecialForms.import/2` for more information on importing.
@@ -70,10 +70,10 @@ defmodule Kernel do
listed above. Some of them are:
* `Date` - `year-month-day` structs in a given calendar
* `DateTime` - date and time with timezone in a given calendar
* `DateTime` - date and time with time zone in a given calendar
* `Exception` - data raised from errors and unexpected scenarios
* `MapSet` - unordered collections of unique elements
* `NaiveDateTime` - date and time without timezone in a given calendar
* `NaiveDateTime` - date and time without time zone in a given calendar
* `Keyword` - lists of two-element tuples, often representing optional values
* `Range` - inclusive ranges between two integers
* `Regex` - regular expressions
@@ -2776,7 +2776,7 @@ defmodule Kernel do
end
try do
:elixir_quote.escape(value, false)
:elixir_quote.escape(value, :default, false)
rescue
ex in [ArgumentError] ->
raise ArgumentError,
@@ -3698,7 +3698,7 @@ defmodule Kernel do
quote(do: Kernel.LexicalTracker.read_cache(unquote(pid), unquote(integer)))
%{} ->
{escaped, _} = :elixir_quote.escape(block, false)
{escaped, _} = :elixir_quote.escape(block, :default, false)
escaped
end
@@ -3972,8 +3972,8 @@ defmodule Kernel do
module = assert_module_scope(env, kind, 2)
assert_no_function_scope(env, kind, 2)
{escaped_call, unquoted_call} = :elixir_quote.escape(call, true)
{escaped_expr, unquoted_expr} = :elixir_quote.escape(expr, true)
{escaped_call, unquoted_call} = :elixir_quote.escape(call, :default, true)
{escaped_expr, unquoted_expr} = :elixir_quote.escape(expr, :default, true)
escaped_expr =
case unquoted_expr do
+24 -7
View File
@@ -356,12 +356,7 @@ defmodule Macro do
def decompose_call(_), do: :error
@doc """
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `escape/2`
which leaves `unquote/1` statements unescaped, effectively
unquoting the contents on escape.
Recursively escapes a value so it can be inserted into a syntax tree.
## Examples
@@ -374,6 +369,26 @@ defmodule Macro do
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1
## Options
* `:unquote` - when true, this function leaves `unquote/1` and
`unquote_splicing/1` statements unescaped, effectively unquoting
the contents on escape. This option is useful only when escaping
ASTs which may have quoted fragments in them. Defaults to false.
* `:prune_metadata` - when true, removes metadata from escaped AST
nodes. Note this option changes the semantics of escaped code and
it should only be used when escaping ASTs, never values. Defaults
to false.
As an example, `ExUnit` stores the AST of every assertion, so when
an assertion fails we can show code snippets to users. Without this
option, each time the test module is compiled, we get a different
MD5 of the module byte code, because the AST contains metadata,
such as counters, specific to the compilation environment. By pruning
the metadata, we ensure that the module is deterministic and reduce
the amount of data `ExUnit` needs to keep around.
## Comparison to `Kernel.quote/2`
The `escape/2` function is sometimes confused with `Kernel.SpecialForms.quote/2`,
@@ -402,7 +417,9 @@ defmodule Macro do
"""
@spec escape(term, keyword) :: Macro.t()
def escape(expr, opts \\ []) do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
unquote = Keyword.get(opts, :unquote, false)
kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :default
elem(:elixir_quote.escape(expr, kind, unquote), 0)
end
@doc """
+1
View File
@@ -45,6 +45,7 @@ defmodule Macro.Env do
* `current_vars`
* `unused_vars`
* `prematch_vars`
* `contextual_vars`
The following fields are deprecated and must not be accessed or relied on:
-1
View File
@@ -506,7 +506,6 @@ defmodule Map do
iex> Map.delete(%{b: 2}, :a)
%{b: 2}
Inlined by the compiler.
"""
@spec delete(map, key) :: map
def delete(map, key), do: :maps.remove(key, map)
+2 -11
View File
@@ -466,7 +466,8 @@ defmodule Module do
of the corresponding setting in `Code.compiler_options/1`
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
name/arity pairs
name/arity pairs. Inlining is applied locally, calls from another
module are not affected by this option
* `@compile {:autoload, false}` - disables automatic loading of
modules after compilation. Instead, the module will be loaded after
@@ -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)
+1 -1
View File
@@ -559,7 +559,7 @@ defmodule OptionParser do
true ->
# TODO: Remove this in Elixir v2.0
IO.warn("Not passing the :switches or :strict option to OptionParser is deprecated")
IO.warn("not passing the :switches or :strict option to OptionParser is deprecated")
{[], false}
end
+1 -1
View File
@@ -636,7 +636,7 @@ defmodule Process do
See `:erlang.process_info/1` for more info.
"""
@spec info(pid) :: keyword
@spec info(pid) :: keyword | nil
def info(pid) do
nillify(:erlang.process_info(pid))
end
+2
View File
@@ -2240,6 +2240,8 @@ defmodule String do
0.8222222222222223
iex> String.jaro_distance("even", "odd")
0.0
iex> String.jaro_distance("same", "same")
1.0
"""
@spec jaro_distance(t, t) :: float
+13 -12
View File
@@ -31,21 +31,22 @@ defmodule StringIO do
## Examples
iex> StringIO.open("foo", [], fn(pid) ->
...> input = IO.gets(pid, ">")
...> IO.write(pid, "The input was #{input}")
...> StringIO.contents(pid)
...> end)
{:ok, {"", "The input was foo"}}
iex> StringIO.open("foo", [], fn(pid) ->
...> input = IO.gets(pid, ">")
...> IO.write(pid, "The input was #{input}")
...> StringIO.contents(pid)
...> end)
{:ok, {"", "The input was foo"}}
iex> StringIO.open("foo", [capture_prompt: true], fn(pid) ->
...> input = IO.gets(pid, ">")
...> IO.write(pid, "The input was #{input}")
...> StringIO.contents(pid)
...> end)
{:ok, {"", ">The input was foo"}}
iex> StringIO.open("foo", [capture_prompt: true], fn(pid) ->
...> input = IO.gets(pid, ">")
...> IO.write(pid, "The input was #{input}")
...> StringIO.contents(pid)
...> end)
{:ok, {"", ">The input was foo"}}
"""
@doc since: "1.7.0"
@spec open(binary, keyword, (pid -> res)) :: {:ok, res} when res: var
def open(string, options, function)
when is_binary(string) and is_list(options) and is_function(function, 1) do
+2 -2
View File
@@ -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}
+1 -1
View File
@@ -6,7 +6,7 @@ defmodule Tuple do
* `elem/2` - access a tuple by index
* `put_elem/3` - insert a value into a tuple by index
* `tuple_size/3` - get the number of elements in a tuple
* `tuple_size/1` - get the number of elements in a tuple
Tuples are intended as fixed-size containers for multiple elements.
To manipulate a collection of elements, use a list instead. `Enum`
@@ -136,4 +136,4 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
[v1.4]: https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md#4-deprecations
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
[v1.7]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
-1
View File
@@ -24,7 +24,6 @@
imports_hygiene=true,
unquote=true,
unquoted=false,
escape=false,
generated=false
}).
+3 -3
View File
@@ -19,7 +19,7 @@
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
'MACRO-defmodule'(_Caller, Alias, [{do, Block}]) ->
{Escaped, _} = elixir_quote:escape(Block, false),
{Escaped, _} = elixir_quote:escape(Block, default, false),
Args = [Alias, Escaped, [], env()],
{{'.', [], [elixir_module, compile]}, [], Args}.
@@ -36,8 +36,8 @@
{defp, 2}].
define({Line, E}, Kind, Call, Expr) ->
{EscapedCall, UC} = elixir_quote:escape(Call, true),
{EscapedExpr, UE} = elixir_quote:escape(Expr, true),
{EscapedCall, UC} = elixir_quote:escape(Call, default, true),
{EscapedExpr, UE} = elixir_quote:escape(Expr, default, true),
Args = [Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E#{line := Line})],
{{'.', [], [elixir_def, store_definition]}, [], Args}.
+1 -1
View File
@@ -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.
+2 -1
View File
@@ -33,7 +33,8 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, #{context := Context} = E)
Struct = load_struct(Meta, ELeft, [Assocs], InContext, EE),
assert_struct_keys(Meta, ELeft, Struct, Assocs, EE),
Keys = ['__struct__'] ++ [K || {K, _} <- Assocs],
{StructAssocs, _} = elixir_quote:escape(maps:to_list(maps:without(Keys, Struct)), false),
WithoutKeys = maps:to_list(maps:without(Keys, Struct)),
{StructAssocs, _} = elixir_quote:escape(WithoutKeys, default, false),
{{'%', Meta, [ELeft, {'%{}', MapMeta, StructAssocs ++ Assocs}]}, EE};
{_, _, Assocs} -> %% Update or match
+1 -3
View File
@@ -331,7 +331,7 @@ warn_unused_attributes(File, DataSet, DataBag, PersistedAttrs) ->
StoredAttrs = bag_lookup_element(DataBag, attributes, 2),
%% This is the same list as in Module.put_attribute
%% without moduledoc which are never warned on.
Attrs = [doc, typedoc, impl, deprecated | StoredAttrs -- PersistedAttrs],
Attrs = [doc, typedoc, impl, deprecated | StoredAttrs -- [since | PersistedAttrs]],
Query = [{{Attr, '_', '$1'}, [{is_integer, '$1'}], [[Attr, '$1']]} || Attr <- Attrs],
[elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_attribute, Key})
|| [Key, Line] <- ets:select(DataSet, Query)].
@@ -404,8 +404,6 @@ format_error({unused_attribute, impl}) ->
"module attribute @impl was set but no definition follows it";
format_error({unused_attribute, deprecated}) ->
"module attribute @deprecated was set but no definition follows it";
format_error({unused_attribute, since}) -> %% TODO: Remove this on v1.8
"module attribute @since is deprecated, please use \"@doc since: ...\" instead";
format_error({unused_attribute, Attr}) ->
io_lib:format("module attribute @~ts was set but never used", [Attr]);
format_error({invalid_module, Module}) ->
+76 -64
View File
@@ -1,5 +1,5 @@
-module(elixir_quote).
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
-export([escape/3, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
-export([dot/5, tail_list/3, list/2]). %% Quote callbacks
-include("elixir.hrl").
@@ -132,16 +132,15 @@ annotate(Tree, _Context) -> Tree.
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
escape(Expr, Kind, Unquote) ->
{Res, Q} = quote(Expr, nil, #elixir_quote{
line=true,
file=nil,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
unquote=Unquote,
escape=true
}, nil),
unquote=Unquote
}, Kind),
{Res, Q#elixir_quote.unquoted}.
%% Quotes an expression and return its quoted Elixir AST.
@@ -161,7 +160,7 @@ quote(Expr, Binding, Q, E) ->
{'{}', [], [K, [], Context]},
V
] ]
} || {K, V} <- Binding],
} || {K, V} <- Binding],
{TExprs, TQ} = do_quote(Expr, Q, E),
{{'{}', [], ['__block__', [], Vars ++ [TExprs] ]}, TQ}.
@@ -211,11 +210,7 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
do_quote_import(Name, Meta, ArgsOrAtom, Q, E);
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
%% Literals
%% Two element tuples
do_quote({Left, Right}, #elixir_quote{unquote=true} = Q, E) when
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
@@ -227,7 +222,33 @@ do_quote({Left, Right}, Q, E) ->
{TRight, RQ} = do_quote(Right, LQ, E),
{{TLeft, TRight}, RQ};
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
%% Everything else
do_quote(Other, Q, E) when is_atom(E) ->
do_escape(Other, Q, E);
do_quote({_, _, _} = Tuple, Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
do_quote(List, Q, E) when is_list(List) ->
do_quote_splice(lists:reverse(List), Q, E);
do_quote(Other, Q, _) ->
{Other, Q}.
%% do_escape
do_escape({Left, _Meta, Right}, Q, E = prune_metadata) ->
{TL, QL} = do_quote(Left, Q, E),
{TR, QR} = do_quote(Right, QL, E),
{{'{}', [], [TL, [], TR]}, QR};
do_escape(Tuple, Q, E) when is_tuple(Tuple) ->
{TT, TQ} = do_quote(tuple_to_list(Tuple), Q, E),
{{'{}', [], TT}, TQ};
do_escape(BitString, Q, _) when is_bitstring(BitString) ->
case bit_size(BitString) rem 8 of
0 ->
{BitString, Q};
@@ -236,51 +257,41 @@ do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitStri
{{'<<>>', [], [{'::', [], [Bits, {size, [], [Size]}]}, {'::', [], [Bytes, {binary, [], []}]}]}, Q}
end;
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
do_escape(Map, Q, E) when is_map(Map) ->
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
{{'%{}', [], TT}, TQ};
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
{TT, TQ} = do_quote(tuple_to_list(Tuple), Q, E),
{{'{}', [], TT}, TQ};
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
do_escape(List, Q, E) when is_list(List) ->
%% The improper case is a bit inefficient, but improper lists are rare.
case reverse_improper(List) of
{L} -> do_splice(L, Q, E);
{L, R} ->
{TL, QL} = do_splice(L, Q, E, [], []),
{L} -> do_quote_splice(L, Q, E);
{L, R} ->
{TL, QL} = do_quote_splice(L, Q, E, [], []),
{TR, QR} = do_quote(R, QL, E),
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
end;
do_quote(Other, #elixir_quote{escape=true} = Q, _)
do_escape(Other, Q, _)
when is_number(Other); is_pid(Other); is_atom(Other) ->
{Other, Q};
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
do_escape(Fun, Q, _) when is_function(Fun) ->
case (erlang:fun_info(Fun, env) == {env, []}) andalso
(erlang:fun_info(Fun, type) == {type, external}) of
true -> {Fun, Q};
false -> bad_escape(Fun)
end;
do_quote(Other, #elixir_quote{escape=true}, _) ->
bad_escape(Other);
do_quote(List, Q, E) when is_list(List) ->
do_splice(lists:reverse(List), Q, E);
do_quote(Other, Q, _) ->
{Other, Q}.
%% Quote helpers
do_escape(Other, _, _) ->
bad_escape(Other).
bad_escape(Arg) ->
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"PIDs and remote functions in the format &Mod.fun/arity">>).
%% do_quote_*
do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q, E) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
@@ -332,10 +343,41 @@ do_quote_tuple(Left, Meta, [{{unquote, _, _}, _, _}, _] = Right, Q, E) when ?def
{{'{}', [], [TLeft, meta(Meta, Q), [NewHead, Body]]}, RQ};
do_quote_tuple(Left, Meta, Right, Q, E) ->
{TLeft, LQ} = do_quote(Left, Q, E),
{TLeft, LQ} = do_quote(Left, Q, E),
{TRight, RQ} = do_quote(Right, LQ, E),
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
do_quote_splice([{'|', Meta, [{unquote_splicing, _, [Left]}, Right]} | T], #elixir_quote{unquote=true} = Q, E) ->
%% Process the remaining entries on the list.
%% For [1, 2, 3, unquote_splicing(arg) | tail], this will quote
%% 1, 2 and 3, which could even be unquotes.
{TT, QT} = do_quote_splice(T, Q, E, [], []),
{TR, QR} = do_quote(Right, QT, E),
{do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true}};
do_quote_splice(List, Q, E) ->
do_quote_splice(List, Q, E, [], []).
do_quote_splice([{unquote_splicing, Meta, [Expr]} | T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
Runtime = do_runtime_list(Meta, list, [Expr, do_list_concat(Buffer, Acc)]),
do_quote_splice(T, Q#elixir_quote{unquoted=true}, E, [], Runtime);
do_quote_splice([H | T], Q, E, Buffer, Acc) ->
{TH, TQ} = do_quote(H, Q, E),
do_quote_splice(T, TQ, E, [TH | Buffer], Acc);
do_quote_splice([], Q, _E, Buffer, Acc) ->
{do_list_concat(Buffer, Acc), Q}.
do_list_concat(Left, []) -> Left;
do_list_concat([], Right) -> Right;
do_list_concat(Left, Right) -> {{'.', [], [erlang, '++']}, [], [Left, Right]}.
do_runtime_list(Meta, Fun, Args) ->
{{'.', Meta, [elixir_quote, Fun]}, Meta, Args}.
%% Helpers
meta(Meta, Q) ->
generated(keep(Meta, Q), Q).
@@ -381,33 +423,3 @@ keynew(Key, Meta, Value) ->
{Key, _} -> Meta;
_ -> keystore(Key, Meta, Value)
end.
%% Quote splicing
do_splice([{'|', Meta, [{unquote_splicing, _, [Left]}, Right]} | T], #elixir_quote{unquote=true} = Q, E) ->
%% Process the remaining entries on the list.
%% For [1, 2, 3, unquote_splicing(arg) | tail], this will quote
%% 1, 2 and 3, which could even be unquotes.
{TT, QT} = do_splice(T, Q, E, [], []),
{TR, QR} = do_quote(Right, QT, E),
{do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true}};
do_splice(List, Q, E) ->
do_splice(List, Q, E, [], []).
do_splice([{unquote_splicing, Meta, [Expr]} | T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, E, [], do_runtime_list(Meta, list, [Expr, do_join(Buffer, Acc)]));
do_splice([H | T], Q, E, Buffer, Acc) ->
{TH, TQ} = do_quote(H, Q, E),
do_splice(T, TQ, E, [TH | Buffer], Acc);
do_splice([], Q, _E, Buffer, Acc) ->
{do_join(Buffer, Acc), Q}.
do_join(Left, []) -> Left;
do_join([], Right) -> Right;
do_join(Left, Right) -> {{'.', [], [erlang, '++']}, [], [Left, Right]}.
do_runtime_list(Meta, Fun, Args) ->
{{'.', Meta, [elixir_quote, Fun]}, Meta, Args}.
+2 -2
View File
@@ -425,7 +425,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H)
true ->
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, io_lib:format(
"found quoted atom \"~ts\" but the quotes are not required. "
"Quotes should only be used to introduced atoms with foreign characters in them",
"Quotes should only be used to introduce atoms with foreign characters in them",
[hd(Parts)]
));
@@ -646,7 +646,7 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, io_lib:format(
"found quoted keyword \"~ts\" but the quotes are not required. "
"Note that keywords are always atoms, even when quoted, and quotes "
"should only be used to introduced keywords with foreign characters in them",
"should only be used to introduce keywords with foreign characters in them",
[hd(Parts)]
));
+94 -4
View File
@@ -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)"
+44 -4
View File
@@ -1569,11 +1569,11 @@ defmodule FileTest do
src = fixture_path("utf8_bom.txt")
assert src
|> File.stream!([:utf8])
|> File.stream!([{:encoding, :utf8}])
|> Enum.take(1) == [<<239, 187, 191>> <> "Русский\n"]
assert src
|> File.stream!([:utf8], 1)
|> File.stream!([{:encoding, :utf8}], 1)
|> Enum.take(9) == ["\uFEFF", "Р", "у", "с", "с", "к", "и", "й", "\n"]
end
@@ -1581,11 +1581,51 @@ defmodule FileTest do
src = fixture_path("utf8_bom.txt")
assert src
|> File.stream!([:utf8, :trim_bom])
|> File.stream!([{:encoding, :utf8}, :trim_bom])
|> Enum.take(1) == ["Русский\n"]
assert src
|> File.stream!([:utf8, :trim_bom], 1)
|> File.stream!([{:encoding, :utf8}, :trim_bom], 1)
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
end
test "keeps BOM with UTF16 BE" do
src = fixture_path("utf16_be_bom.txt")
assert src
|> File.stream!([{:encoding, {:utf16, :big}}])
|> Enum.take(1) == ["\uFEFFРусский\n"]
end
test "keeps BOM with UTF16 LE" do
src = fixture_path("utf16_le_bom.txt")
assert src
|> File.stream!([{:encoding, {:utf16, :little}}])
|> Enum.take(1) == ["\uFEFFРусский\n"]
end
test "trims BOM via option with utf16 BE encoding" do
src = fixture_path("utf16_be_bom.txt")
assert src
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom])
|> Enum.take(1) == ["Русский\n"]
assert src
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom], 1)
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
end
test "trims BOM via option with utf16 LE encoding" do
src = fixture_path("utf16_le_bom.txt")
assert src
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom])
|> Enum.take(1) == ["Русский\n"]
assert src
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom], 1)
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
end
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,10 @@ defmodule IO.ANSI.DocsTest do
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.trim_trailing()
end
def format_metadata(map) do
capture_io(fn -> IO.ANSI.Docs.print_metadata(map, []) end)
end
def format(str) do
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.trim_trailing()
end
@@ -19,6 +23,12 @@ defmodule IO.ANSI.DocsTest do
assert String.contains?(result, " wibble ")
end
test "metadata is formatted" do
result = format_metadata(%{since: "1.2.3", deprecated: "Use that other one", author: "Alice"})
assert result == "\e[33mdeprecated:\e[0m Use that other one\n\e[33msince:\e[0m 1.2.3\n\n"
assert format_metadata(%{author: "Alice"}) == ""
end
test "first level heading is converted" do
result = format("# wibble\n\ntext\n")
assert result == "\e[33m# wibble\e[0m\n\e[0m\ntext\n\e[0m"
@@ -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 ->
+19 -13
View File
@@ -24,38 +24,44 @@ defmodule MacroTest do
import Macro.ExternalTest
describe "escape/2" do
test "handles tuples with size different than two" do
test "returns tuples with size equal to two" do
assert Macro.escape({:a, :b}) == {:a, :b}
end
test "returns lists" do
assert Macro.escape([1, 2, 3]) == [1, 2, 3]
end
test "escapes tuples with size different than two" do
assert Macro.escape({:a}) == {:{}, [], [:a]}
assert Macro.escape({:a, :b, :c}) == {:{}, [], [:a, :b, :c]}
assert Macro.escape({:a, {1, 2, 3}, :c}) == {:{}, [], [:a, {:{}, [], [1, 2, 3]}, :c]}
end
test "simply returns tuples with size equal to two" do
assert Macro.escape({:a, :b}) == {:a, :b}
end
test "simply returns any other structure" do
assert Macro.escape([1, 2, 3]) == [1, 2, 3]
end
test "handles maps" do
test "escapes maps" do
assert Macro.escape(%{a: 1}) == {:%{}, [], [a: 1]}
end
test "handles bitstring" do
test "escapes bitstring" do
assert {:<<>>, [], args} = Macro.escape(<<300::12>>)
assert [{:::, [], [1, {:size, [], [4]}]}, {:::, [], [",", {:binary, [], []}]}] = args
end
test "works recursively" do
test "escapes recursively" do
assert Macro.escape([1, {:a, :b, :c}, 3]) == [1, {:{}, [], [:a, :b, :c]}, 3]
end
test "with improper lists" do
test "escapes improper lists" do
assert Macro.escape([1 | 2]) == [{:|, [], [1, 2]}]
assert Macro.escape([1, 2 | 3]) == [1, {:|, [], [2, 3]}]
end
test "prunes metadata" do
meta = [nothing: :important, counter: 1]
assert Macro.escape({:foo, meta, []}) == {:{}, [], [:foo, meta, []]}
assert Macro.escape({:foo, meta, []}, prune_metadata: true) == {:{}, [], [:foo, [], []]}
end
test "with unquote" do
contents = quote(unquote: false, do: unquote(1))
assert Macro.escape(contents, unquote: true) == 1
@@ -376,7 +376,7 @@ end
defmodule OptionsParserDeprecationsTest do
use ExUnit.Case, async: false
@warning ~r[Not passing the :switches or :strict option to OptionParser is deprecated]
@warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]
def assert_deprecated(fun) do
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
+12 -2
View File
@@ -317,7 +317,7 @@ defmodule ExUnit.Assertions do
end
defp escape_quoted(kind, expr) do
Macro.escape({kind, [], [expr]})
Macro.escape({kind, [], [expr]}, prune_metadata: true)
end
defp extract_args({root, meta, [_ | _] = args} = expr, env) do
@@ -745,6 +745,7 @@ defmodule ExUnit.Assertions do
@doc """
Asserts `expression` will throw a value.
Returns the thrown value or fails otherwise.
## Examples
@@ -758,11 +759,19 @@ defmodule ExUnit.Assertions do
@doc """
Asserts `expression` will exit.
Returns the exit status/message or fails otherwise.
Returns the exit status/message of the current process or fails otherwise.
## Examples
assert catch_exit(exit 1) == 1
To assert exits from linked processes started from the test, trap exits
with `Process.flag/2` and assert the exit message with `assert_received/2`.
Process.flag(:trap_exit, true)
pid = spawn_link(fn -> Process.exit(self(), :normal) end)
assert_receive {:EXIT, ^pid, :normal}
"""
defmacro catch_exit(expression) do
@@ -771,6 +780,7 @@ defmodule ExUnit.Assertions do
@doc """
Asserts `expression` will cause an error.
Returns the error or fails otherwise.
## Examples
+1 -1
View File
@@ -1095,8 +1095,8 @@ defmodule IEx.Helpers do
end
@doc false
@deprecated "Use import_file_if_available/1 instead"
defmacro import_file(path, opts) when is_binary(path) and is_list(opts) do
IO.warn("import_file/2 is deprecated, please use import_file_if_available/1 instead")
import_file_if_available(path, Keyword.get(opts, :optional, false))
end
+1 -1
View File
@@ -338,7 +338,7 @@ defimpl IEx.Info, for: [Date, Time, NaiveDateTime] do
case @for do
Date -> {"D", "date"}
Time -> {"T", "time"}
NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a timezone)}}
NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a time zone)}}
end
def info(value) do
+25 -21
View File
@@ -250,8 +250,8 @@ defmodule IEx.Introspection do
case Code.ensure_loaded(module) do
{:module, _} ->
case Code.fetch_docs(module) do
{:docs_v1, _, _, _, %{} = doc, _, _} ->
print_doc(inspect(module), [], doc)
{:docs_v1, _, _, _, %{} = doc, metadata, _} ->
print_doc(inspect(module), [], doc, metadata)
{:docs_v1, _, _, _, _, _, _} ->
docs_not_found(inspect(module))
@@ -376,7 +376,7 @@ defmodule IEx.Introspection do
is_nil(docs) and spec != [] ->
message = %{"en" => "Module was compiled without docs. Showing only specs."}
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message)
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message, %{})
:ok
is_nil(docs) ->
@@ -449,7 +449,7 @@ defmodule IEx.Introspection do
defp has_content?({{_, name, _}, _, _, :none, _}), do: hd(Atom.to_charlist(name)) != ?_
defp has_content?({_, _, _, _, _}), do: true
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, _meta}, spec) do
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, metadata}, spec) do
if callback_module = doc == :none and callback_module(mod, fun, arity) do
filter = &match?({_, ^fun, ^arity}, elem(&1, 0))
@@ -458,7 +458,7 @@ defmodule IEx.Introspection do
_ -> nil
end
else
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc)
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc, metadata)
end
end
@@ -513,7 +513,7 @@ defmodule IEx.Introspection do
{:ok, docs} ->
docs
|> add_optional_callback_docs(mod)
|> Enum.each(fn {definition, _} -> IO.puts(definition) end)
|> Enum.each(fn {definition, _, _} -> IO.puts(definition) end)
end
dont_display_result()
@@ -565,12 +565,12 @@ defmodule IEx.Introspection do
docs
|> Enum.filter(filter)
|> Enum.map(fn
{{:macrocallback, fun, arity}, _, _, doc, _} ->
{{:macrocallback, fun, arity}, _, _, doc, metadata} ->
macro = {:"MACRO-#{fun}", arity + 1}
{format_callback(:macrocallback, fun, macro, callbacks), doc}
{format_callback(:macrocallback, fun, macro, callbacks), doc, metadata}
{{kind, fun, arity}, _, _, doc, _} ->
{format_callback(kind, fun, {fun, arity}, callbacks), doc}
{{kind, fun, arity}, _, _, doc, metadata} ->
{format_callback(kind, fun, {fun, arity}, callbacks), doc, metadata}
end)
{:ok, docs}
@@ -588,7 +588,7 @@ defmodule IEx.Introspection do
if optional_callbacks == [] do
docs
else
docs ++ [{format_optional_callbacks(optional_callbacks), ""}]
docs ++ [{format_optional_callbacks(optional_callbacks), "", %{}}]
end
end
@@ -637,8 +637,8 @@ defmodule IEx.Introspection do
{:ok, types} ->
printed =
for {_, {^type, _, args}} = typespec <- types do
doc = {format_type(typespec), type_doc(module, type, length(args))}
print_typespec(doc)
type_doc(module, type, length(args), typespec)
|> print_typespec()
end
if printed == [] do
@@ -657,8 +657,8 @@ defmodule IEx.Introspection do
{:ok, types} ->
printed =
for {_, {^type, _, args}} = typespec <- types, length(args) == arity do
doc = {format_type(typespec), type_doc(module, type, arity)}
print_typespec(doc)
type_doc(module, type, arity, typespec)
|> print_typespec()
end
if printed == [] do
@@ -674,12 +674,12 @@ defmodule IEx.Introspection do
dont_display_result()
end
defp type_doc(module, type, arity) do
defp type_doc(module, type, arity, typespec) do
if docs = get_docs(module, [:type]) do
{_, _, _, content, _} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
content
{_, _, _, content, metadata} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
{format_type(typespec), content, metadata}
else
:none
{format_type(typespec), :none, %{}}
end
end
@@ -717,27 +717,31 @@ defmodule IEx.Introspection do
IEx.color(:doc_inline_code, left) <> " " <> right
end
defp print_doc(heading, types, doc) do
defp print_doc(heading, types, doc, metadata) do
doc = translate_doc(doc) || ""
if opts = IEx.Config.ansi_docs() do
IO.ANSI.Docs.print_heading(heading, opts)
IO.write(types)
IO.ANSI.Docs.print_metadata(metadata, opts)
IO.ANSI.Docs.print(doc, opts)
else
IO.puts("* #{heading}\n")
IO.write(types)
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
IO.puts(doc)
end
end
defp print_typespec({types, doc}) do
defp print_typespec({types, doc, metadata}) do
IO.puts(types)
doc = translate_doc(doc)
if opts = IEx.Config.ansi_docs() do
IO.ANSI.Docs.print_metadata(metadata, opts)
doc && IO.ANSI.Docs.print(doc, opts)
else
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
doc && IO.puts(doc)
end
end
+80
View File
@@ -403,6 +403,38 @@ defmodule IEx.HelpersTest do
"No documentation for Kernel.__info__ was found\n"
end
test "prints documentation metadata" do
content = """
defmodule Sample do
@moduledoc "Sample module"
@moduledoc deprecated: "Use OtherSample", since: "1.2.3", authors: ["Alice", "Bob"]
@doc "With metadata"
@doc since: "1.2.3", author: "Alice"
@deprecated "Use OtherSample.with_metadata/0"
def with_metadata(), do: 0
@doc "Without metadata"
def without_metadata(), do: 1
end
"""
filename = "sample.ex"
with_file(filename, content, fn ->
assert c(filename, ".") == [Sample]
assert capture_io(fn -> h(Sample) end) ==
"* Sample\n\ndeprecated: Use OtherSample\nsince: 1.2.3\n\nSample module\n"
assert capture_io(fn -> h(Sample.with_metadata()) end) ==
"* def with_metadata()\n\ndeprecated: Use OtherSample.with_metadata/0\nsince: 1.2.3\n\nWith metadata\n"
assert capture_io(fn -> h(Sample.without_metadata()) end) ==
"* def without_metadata()\n\nWithout metadata\n"
end)
after
cleanup_modules([Sample])
end
test "considers underscored functions without docs by default" do
content = """
defmodule Sample do
@@ -593,6 +625,27 @@ defmodule IEx.HelpersTest do
"@callback message(t()) :: String.t()\n\n"
end
test "prints callback documentation metadata" do
filename = "callback_with_metadata.ex"
content = """
defmodule CallbackWithMetadata do
@doc "callback"
@doc since: "1.2.3", deprecated: "Use handle_test/1", purpose: :test
@callback test(:foo) :: integer
end
"""
with_file(filename, content, fn ->
assert c(filename, ".") == [CallbackWithMetadata]
assert capture_io(fn -> b(CallbackWithMetadata.test()) end) ==
"@callback test(:foo) :: integer()\n\ndeprecated: Use handle_test/1\nsince: 1.2.3\n\ncallback\n"
end)
after
cleanup_modules([CallbackWithMetadata])
end
test "prints optional callback" do
filename = "optional_callbacks.ex"
@@ -666,6 +719,33 @@ defmodule IEx.HelpersTest do
after
cleanup_modules([TypeSample])
end
test "prints type documentation metadata" do
content = """
defmodule TypeSample do
@typedoc "An id with description."
@typedoc since: "1.2.3", deprecated: "Use t/0", purpose: :test
@type id_with_desc :: {number, String.t}
end
"""
filename = "typesample.ex"
with_file(filename, content, fn ->
assert c(filename, ".") == [TypeSample]
assert capture_io(fn -> t(TypeSample.id_with_desc()) end) == """
@type id_with_desc() :: {number(), String.t()}
deprecated: Use t/0
since: 1.2.3
An id with description.
"""
end)
after
cleanup_modules([TypeSample])
end
end
describe "v" do
+1 -1
View File
@@ -194,7 +194,7 @@ defmodule IEx.InfoTest do
assert get_key(info, "Reference modules") == "NaiveDateTime, Calendar, Map"
assert get_key(info, "Description") =~
~S{a "naive" datetime (that is, a datetime without a timezone)}
~S{a "naive" datetime (that is, a datetime without a time zone)}
assert get_key(info, "Description") =~ "`~N`"
end
+19 -11
View File
@@ -416,7 +416,7 @@ defmodule Logger do
@type backend :: :gen_event.handler()
@type message :: IO.chardata() | String.Chars.t()
@type level :: :error | :info | :warn | :debug
@type metadata :: keyword(String.Chars.t())
@type metadata :: keyword()
@levels [:error, :info, :warn, :debug]
@metadata :logger_metadata
@@ -436,17 +436,25 @@ defmodule Logger do
@spec metadata(metadata) :: :ok
def metadata(keyword) do
{enabled?, metadata} = __metadata__()
metadata =
Enum.reduce(keyword, metadata, fn
{key, nil}, acc -> Keyword.delete(acc, key)
{key, val}, acc -> Keyword.put(acc, key, val)
end)
Process.put(@metadata, {enabled?, metadata})
Process.put(@metadata, {enabled?, into_metadata(keyword, metadata)})
:ok
end
defp into_metadata([], metadata), do: metadata
defp into_metadata(keyword, metadata), do: into_metadata(keyword, [], metadata)
defp into_metadata([{key, nil} | keyword], prepend, metadata) do
into_metadata(keyword, prepend, :lists.keydelete(key, 1, metadata))
end
defp into_metadata([{key, _} = pair | keyword], prepend, metadata) do
into_metadata(keyword, [pair | prepend], :lists.keydelete(key, 1, metadata))
end
defp into_metadata([], prepend, metadata) do
prepend ++ metadata
end
@doc """
Reads the current process metadata.
"""
@@ -671,7 +679,7 @@ defmodule Logger do
@doc false
def __do_log__({level, config, pdict}, chardata_or_fun, metadata) when is_list(metadata) do
%{utc_log: utc_log?, truncate: truncate, mode: mode} = config
metadata = [pid: self()] ++ Keyword.merge(pdict, metadata)
metadata = [pid: self()] ++ into_metadata(metadata, pdict)
case normalize_message(chardata_or_fun, metadata) do
{message, metadata} ->
@@ -851,7 +859,7 @@ defmodule Logger do
defp normalize_message(fun, metadata) when is_function(fun, 0) do
case fun.() do
{message, fun_metadata} -> {message, Keyword.merge(metadata, fun_metadata)}
{message, fun_metadata} -> {message, into_metadata(fun_metadata, metadata)}
:skip -> :skip
message -> {message, metadata}
end
+4
View File
@@ -339,6 +339,10 @@ defmodule Logger.Translator do
["\nStart Module: ", inspect(mod) | child_debug(min_level, debug)]
end
defp child_info(_min_level, _child) do
[]
end
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
["\nType: ", inspect(type)]
+3 -3
View File
@@ -109,7 +109,7 @@ defmodule LoggerTest do
assert capture_log(fn ->
assert Logger.bare_log(:info, "ok", application: nil, module: LoggerTest) == :ok
end) =~ msg("application= module=LoggerTest [info] ok")
end) =~ msg("module=LoggerTest [info] ok")
end
test "metadata compile-time merge" do
@@ -117,7 +117,7 @@ defmodule LoggerTest do
assert capture_log(fn ->
assert Logger.log(:info, "ok", application: nil, module: CustomTest) == :ok
end) =~ msg("application= module=CustomTest [info] ok")
end) =~ msg("module=CustomTest [info] ok")
end
test "metadata merge when the argument function returns metadata" do
@@ -127,7 +127,7 @@ defmodule LoggerTest do
assert capture_log(fn ->
assert Logger.bare_log(:info, fun, application: nil, module: LoggerTest) == :ok
end) =~ msg("application= module=Function [info] ok")
end) =~ msg("module=Function [info] ok")
end
test "enable/1 and disable/1" do
+14 -13
View File
@@ -47,7 +47,6 @@ defmodule Mix.Config do
defp put_config(value) do
Process.put(@config_key, value)
:ok
end
defp delete_config() do
@@ -60,7 +59,6 @@ defmodule Mix.Config do
defp put_files(value) do
Process.put(@files_key, value)
:ok
end
defp delete_files() do
@@ -215,20 +213,23 @@ defmodule Mix.Config do
It returns a tuple with the configuration and the imported paths.
"""
def eval!(file, imported_paths \\ []) do
put_config([])
put_files(imported_paths)
{eval_config, _} = eval_config!(Path.expand(file))
previous_config = put_config([])
previous_files = put_files(imported_paths)
case get_config!() do
[] when is_list(eval_config) ->
{validate!(eval_config), get_files!()}
try do
{eval_config, _} = eval_config!(Path.expand(file))
pdict_config ->
{pdict_config, get_files!()}
case get_config!() do
[] when is_list(eval_config) ->
{validate!(eval_config), get_files!()}
pdict_config ->
{pdict_config, get_files!()}
end
after
if previous_config, do: put_config(previous_config), else: delete_config()
if previous_files, do: put_files(previous_files), else: delete_files()
end
after
delete_config()
delete_files()
end
@doc false
+24 -5
View File
@@ -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 """
+18 -12
View File
@@ -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]
+19 -4
View File
@@ -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
+10 -4
View File
@@ -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
+6 -2
View File
@@ -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`)
+47 -19
View File
@@ -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
+9 -3
View File
@@ -145,7 +145,7 @@ defmodule Mix.Tasks.Test do
## Command line options
* `--color` - enables color in the output
* `--cover` - the directory to include coverage results
* `--cover` - runs coverage tool. See "Coverage" section below
* `--exclude` - excludes tests that match the filter
* `--force` - forces compilation regardless of modification times
* `--formatter` - formatter module
@@ -256,7 +256,13 @@ defmodule Mix.Tasks.Test do
By default, a very simple wrapper around OTP's `cover` is used as a tool,
but it can be overridden as follows:
test_coverage: [tool: CoverModule]
def project() do
[
...
test_coverage: [tool: CoverModule]
...
]
end
`CoverModule` can be any module that exports `start/2`, receiving the
compilation path and the `test_coverage` options as arguments.
@@ -401,7 +407,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: "
+2 -2
View File
@@ -350,14 +350,14 @@ defmodule Mix.Utils do
defp quoted(data), do: [?", to_string(data), ?"]
@doc false
@deprecated "Use Macro.underscore/1 instead"
def underscore(value) do
IO.warn("Mix.Utils.underscore/1 is deprecated, use Macro.underscore/1 instead")
Macro.underscore(value)
end
@doc false
@deprecated "Use Macro.camelize/1 instead"
def camelize(value) do
IO.warn("Mix.Utils.camelize/1 is deprecated, use Macro.camelize/1 instead")
Macro.camelize(value)
end
+3
View File
@@ -0,0 +1,3 @@
use Mix.Config
Mix.Config.eval!(Path.join([__DIR__, "good_config.exs"]))
:done
+3
View File
@@ -107,6 +107,9 @@ defmodule Mix.ConfigTest do
{[my_app: [key: :value]],
[fixture_path("configs/good_config.exs"), fixture_path("configs/good_import.exs")]}
assert Mix.Config.eval!(fixture_path("configs/nested_import.exs")) ==
{[], [fixture_path("configs/nested_import.exs")]}
assert_raise ArgumentError,
~r"expected runtime config for app :sample to return keyword list",
fn -> Mix.Config.eval!(fixture_path("configs/bad_app.exs")) end
+66
View File
@@ -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 = [
+27
View File
@@ -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