Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1406d853e0 | ||
|
|
42f8a069c5 | ||
|
|
e060a458cb | ||
|
|
9aeaa61411 | ||
|
|
28ee5bf396 | ||
|
|
ba85d139fb | ||
|
|
039ca64890 | ||
|
|
705952be2d | ||
|
|
43b54f9a4b | ||
|
|
9dfc7a88b3 | ||
|
|
6ce02e6a8c | ||
|
|
463fd60f2e | ||
|
|
f2719b6fea | ||
|
|
bf3ef047dc | ||
|
|
896d0b4580 | ||
|
|
bb028704f7 | ||
|
|
b27db522e0 | ||
|
|
f5e4635087 | ||
|
|
288fb9ec73 | ||
|
|
f76f9f6112 | ||
|
|
117b2bf614 | ||
|
|
a20acc460b | ||
|
|
e5208421b3 | ||
|
|
d5467a90a7 | ||
|
|
fa574e65bd | ||
|
|
8e99e3c74d | ||
|
|
1515182348 | ||
|
|
686038f040 | ||
|
|
127a4a4d8a | ||
|
|
6868af0cd6 | ||
|
|
fd8a129b15 | ||
|
|
73f76aed88 | ||
|
|
ae8262ae5b | ||
|
|
64cd202237 | ||
|
|
baf7b7a93b | ||
|
|
aee5dc280b | ||
|
|
4092c83e9b | ||
|
|
7e41005ba2 | ||
|
|
288e8ea69e | ||
|
|
36069cbcde | ||
|
|
5f735de4ca | ||
|
|
96ba93481b | ||
|
|
ea8eff362b | ||
|
|
879d67eceb | ||
|
|
704fb9599b | ||
|
|
c49a598635 | ||
|
|
86f4e3ba15 | ||
|
|
50aba8f223 | ||
|
|
06b46e71d1 | ||
|
|
26772f7a1d | ||
|
|
c6238e0bc7 | ||
|
|
2a5eca9d12 | ||
|
|
f292a072b0 | ||
|
|
cce4db17d3 | ||
|
|
53985088d5 | ||
|
|
413d8334d4 | ||
|
|
35fae47904 | ||
|
|
cf8507bdbb | ||
|
|
15735b67b7 | ||
|
|
b4c73b2a9e | ||
|
|
4fa496ca4b | ||
|
|
bcd1ca606d | ||
|
|
2199d0ca54 | ||
|
|
a794c6e952 | ||
|
|
17ae84ef42 | ||
|
|
28505a7233 | ||
|
|
4113dbc834 | ||
|
|
07167ea75e | ||
|
|
29496930fd | ||
|
|
99712761c5 | ||
|
|
67db771086 | ||
|
|
f94477b2ef | ||
|
|
75f6af7316 | ||
|
|
91465a9a42 | ||
|
|
281d749618 | ||
|
|
39da6e3980 | ||
|
|
bfaca6776a | ||
|
|
d865aaae73 | ||
|
|
39bce23c24 | ||
|
|
0deff8b14e | ||
|
|
0391b12263 | ||
|
|
85badf2a90 | ||
|
|
a8d217e225 | ||
|
|
473352f7f2 | ||
|
|
b37529e0eb | ||
|
|
fc40868ca4 | ||
|
|
9c844fc550 | ||
|
|
91113f53f2 | ||
|
|
6487698c26 | ||
|
|
27f256e68f | ||
|
|
c01454f3ee | ||
|
|
bb69c7ac7c | ||
|
|
1dc84830a9 | ||
|
|
b5056ca29e | ||
|
|
ad4edaffc3 | ||
|
|
5787447546 | ||
|
|
fa0de77862 | ||
|
|
93d098dcba |
@@ -7,12 +7,3 @@ before_test:
|
||||
- cmd: set PATH=%PATH%;C:\Program Files\erl8.3\erts-8.3\bin
|
||||
test_script:
|
||||
- cmd: C:\MinGW\msys\1.0\bin\make --keep-going test_windows
|
||||
|
||||
environment:
|
||||
ELIXIR_ASSERT_TIMEOUT: 2000
|
||||
|
||||
configuration: Test
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- configuration: Test
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[
|
||||
inputs: [
|
||||
"lib/*/{lib,unicode,test}/**/*.{ex,exs}",
|
||||
"lib/*/mix.exs"
|
||||
],
|
||||
|
||||
locals_without_parens: [
|
||||
# Formatter tests
|
||||
assert_format: 2,
|
||||
assert_format: 3,
|
||||
assert_same: 1,
|
||||
assert_same: 2,
|
||||
|
||||
# Errors tests
|
||||
assert_eval_raise: 3,
|
||||
|
||||
# Mix tests
|
||||
in_fixture: 2,
|
||||
in_tmp: 2
|
||||
]
|
||||
]
|
||||
@@ -3,6 +3,7 @@
|
||||
/lib/*/_build/
|
||||
/lib/*/tmp/
|
||||
/lib/elixir/src/*_parser.erl
|
||||
/lib/elixir/src/elixir.app.src
|
||||
/lib/elixir/test/ebin/
|
||||
/man/elixir.1
|
||||
/man/iex.1
|
||||
|
||||
+11
-6
@@ -1,8 +1,17 @@
|
||||
language: erlang
|
||||
sudo: false
|
||||
|
||||
os: linux
|
||||
otp_release: 18.0
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
otp_release: 18.1
|
||||
- os: linux
|
||||
otp_release: 18.2
|
||||
- os: linux
|
||||
otp_release: 18.3
|
||||
- os: linux
|
||||
otp_release: 19.0
|
||||
- os: linux
|
||||
@@ -13,8 +22,6 @@ matrix:
|
||||
otp_release: 19.3
|
||||
- os: linux
|
||||
otp_release: 20.0
|
||||
- os: linux
|
||||
otp_release: 20.1
|
||||
|
||||
env:
|
||||
- ELIXIR_ASSERT_TIMEOUT=2000
|
||||
@@ -23,12 +30,10 @@ script:
|
||||
- make compile
|
||||
- rm -rf .git
|
||||
- make test
|
||||
- bin/elixir bin/mix format --dry-run --check-formatted
|
||||
- dialyzer -pa lib/elixir/ebin --build_plt --output_plt elixir.plt --apps lib/elixir/ebin/elixir.beam lib/elixir/ebin/Elixir.Kernel.beam
|
||||
|
||||
notifications:
|
||||
recipients:
|
||||
- jose.valim@gmail.com
|
||||
- jose.valim@plataformatec.com.br
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
- lexmag@me.com
|
||||
- an.leopardi@gmail.com
|
||||
|
||||
|
||||
+282
-190
@@ -1,252 +1,344 @@
|
||||
# Changelog for Elixir v1.6
|
||||
# Changelog for Elixir v1.5
|
||||
|
||||
## Code formatter
|
||||
Elixir v1.5 brings new features, enhancements and bug fixes to Elixir. It is the first release to leverage features added as part of Erlang/OTP 20. It is also the last release that supports Erlang/OTP 18.
|
||||
|
||||
The big feature in Elixir v1.6 is the addition of a code formatter and an accompanying `mix format` task that adds automatic formatting to your projects.
|
||||
## UTF-8 atoms, function names and variables
|
||||
|
||||
The goal of the formatter is to automate the styling of codebases into a unique and consistent layout used across teams and the whole community. Code is now easier to write, as you no longer need to concern yourself with formatting rules. Code is also easier to read, as you no longer need to convert the styles of other developers in your mind.
|
||||
Elixir v1.5 supports non-quoted atoms and variables to be in UTF-8 when using Erlang/OTP 20+. For example:
|
||||
|
||||
The formatter also helps new developers to learn the language, by giving immediate feedback on code structure, and eases code reviews by allowing teams to focus on business rules and code quality, rather than code style.
|
||||
|
||||
To automatically format your codebase, you can run the new `mix format` task. A `.formatter.exs` file may be added to your project root for rudimentary formatter configuration. The mix task also supports flags for CI integration. For instance, you can make your build or a Pull Request fail if the code is not formatted accordingly. We also recommend developers to check their favorite editor and see if they already provide key bindings for `mix format`, allowing a file or a code snippet to be formatted without ceremony.
|
||||
|
||||
The Elixir codebase itself has been already fully formatted and all further contributions are expected to contain formatted code. We recommend existing codebases to be formatted in steps. While the formatter will correctly handle long lines and complex expressions, refactoring the code by breaking those into variables or smaller functions as you format them will lead to overall cleaner and more readable codebases.
|
||||
|
||||
## Dynamic Supervisor
|
||||
|
||||
Supervisors in Elixir are responsible for starting, shutting down and restarting child process when things go wrong. Most of the interaction with supervisors happen with the Supervisor module and it contains three main strategies: `:one_for_one`, `:rest_for_one` and `:one_for_all`.
|
||||
|
||||
However, sometimes the children of a supervisor are not known upfront and are rather started dynamically. For example, if you are building a web server, you have each request beind handled by a separate supervised process. Those cases were handled in the Supervisor module under a special strategy called `:simple_one_for_one`.
|
||||
|
||||
Unfortunately, this special strategy changed the semantics of the supervisor in regards to initialization and shutdown. Plus some APIs expected different inputs or would be completely unavailable depending on the supervision strategy.
|
||||
|
||||
Elixir v1.6 addresses this issue by introducing a new `DynamicSupervisor` module, which encapsulates the old `:simple_one_for_one` strategy and APIs in a proper module while allowing the documentation and API of the `Supervisor` module to focus on its main use cases. Having a separate `DynamicSupervisor` module also makes it simpler to add new features to the dynamic supervisor, such as the new `:max_children` option that limits the maximum number of children supervised dynamically.
|
||||
|
||||
## `@deprecated` and `@since` attributes
|
||||
|
||||
This release also introduces two new attributes associated to function definitions: `@deprecated` and `@since`. The former marks if a function or macro is deprecated, the latter annotates the version the API was introduced:
|
||||
|
||||
@doc "Breaks a collection into chunks"
|
||||
@since "1.0.0"
|
||||
@deprecated "Use chunk_every/2 instead"
|
||||
def chunk_every(collection, chunk_size) do
|
||||
...
|
||||
test "こんにちは世界" do
|
||||
assert :こんにちは世界
|
||||
end
|
||||
|
||||
The `mix xref` task was also updated to warn if your project calls deprecated code. So if a definition is marked as `@deprecated` and a module invokes it, a warning will be emitted during compilation. This effectively provides libraries and frameworks a mechanism to deprecate code without causing multiple warnings to be printed in runtime and without impacting performance.
|
||||
Or:
|
||||
|
||||
Note those attributes are not yet available to tools that generate documentation. Such functionality will be added in future releases as it requires changes to how Elixir stores documentation in BEAM files. We still recommend developers to properly annotate their APIs, as the information will then be already available when the tooling is updated.
|
||||
saudação = "Bom dia!"
|
||||
|
||||
## defguard and defguardp
|
||||
Elixir follows the recommendations in [Unicode Annex #31](http://unicode.org/reports/tr31/) to make the language more accessible to other languages and communities. Identifiers must still be a sequence of letters, followed by digits and combining marks. This means symbols, such as mathematical notations and emoji, are not allowed identifiers.
|
||||
|
||||
Elixir provides the concepts of guards: expressions used alongside pattern matching to select a matching clause. Let's see an example straight from Elixir's home page:
|
||||
|
||||
def serve_drinks(%User{age: age}) when age >= 21 do
|
||||
# Code that serves drinks!
|
||||
end
|
||||
|
||||
`%User{age: age}` is matching on a `User` struct with an age field and `when age >= 21` is the guard.
|
||||
|
||||
Since only a handful of constructs are [allowed in guards](https://hexdocs.pm/elixir/guards.html#content), if you were in a situation where you had to check the age to be more than or equal to 21 in multiple times, extracting the guard to a separate function would be [less than obvious and error prone](https://github.com/elixir-lang/elixir/issues/2469). To address those issues, this release introduces `defguard/1` and `defguardp/1`:
|
||||
|
||||
defguard is_drinking_age(age) when age >= 21
|
||||
|
||||
def serve_drinks(%User{age: age}) when is_drinking_age(age) do
|
||||
# Code that serves drinks!
|
||||
end
|
||||
For a complete reference on Elixir syntax, see the [Syntax Reference](https://hexdocs.pm/elixir/syntax-reference.html). For technical details on Unicode support, see [Unicode Syntax](https://hexdocs.pm/elixir/unicode-syntax.html).
|
||||
|
||||
## IEx improvements
|
||||
|
||||
IEx also got its share of improvements. The new code formatter allows us to pretty print code snippets, types and specifications, improving the overall experience when exploring code through the terminal.
|
||||
IEx got many improvements. The autocompletion system is now capable of autocompleting variables and user imports. New helpers have also been added:
|
||||
|
||||
The autocomplete mechanism also got smarter, being able to provide context autocompletion. For example, typing `t Enum.` and hitting TAB will autocomplete only the types in Enum (in contrast to all functions). Typing `b GenServer.` and hitting TAB will autocomplete only the behaviour callbacks.
|
||||
* `exports/1` lists all exports (functions and macros) in a given module
|
||||
* `open/1` opens up the source of a module or function directly in your editor. For example, `open MyApp.Module`
|
||||
* `runtime_info/0` prints general information about the running system, such as number of cores, runtime version, allocation of memory in the VM and more
|
||||
|
||||
Finally, the breakpoint functionality added in Elixir v1.5 has been improved to support pattern matching and guards. For example, to pattern match on a function call when the first argument is the atom `:foo`, you may do:
|
||||
IEx also features a breakpoint system for code debugging. The following functions have been added to aid debugging:
|
||||
|
||||
break! SomeFunction.call(:foo, _, _)
|
||||
* `break!/2` - sets up a breakpoint for a given `Mod.fun/arity`
|
||||
* `break!/4` - sets up a breakpoint for the given module, function, arity
|
||||
* `breaks/0` - prints all breakpoints and their ids
|
||||
* `continue/0` - continues until the next breakpoint in the same process
|
||||
* `open/0` - opens editor on the current breakpoint
|
||||
* `remove_breaks/0` - removes all breakpoints in all modules
|
||||
* `remove_breaks/1` - removes all breakpoints in a given module
|
||||
* `reset_break/1` - sets the number of stops on the given id to zero
|
||||
* `reset_break/3` - sets the number of stops on the given module, function, arity to zer
|
||||
* `respawn/0` - starts a new shell (breakpoints will ask for permission once more)
|
||||
* `whereami/1` - shows the current location
|
||||
|
||||
## mix xref
|
||||
## Exception.blame
|
||||
|
||||
`mix xref` is a task added in Elixir v1.3 which provides general information about how modules and files in an application depend on each other. This release brings many improvements to `xref`, extending the reach of the analysis and helping developers digest the vast amount of data it produces.
|
||||
`Exception.blame/3` is a new function in Elixir that is capable of attaching debug information to certain exceptions. Currently this is used to augment `FunctionClauseError`s with a summary of all clauses and which parts of clause match and which ones didn't. For example:
|
||||
|
||||
One of such additions is the `--include-siblings` option that can be given to all `xref` commands inside umbrella projects. For example, to find all of the callers of a given module or function in an umbrella:
|
||||
iex> Access.fetch(:foo, :bar)
|
||||
** (FunctionClauseError) no function clause matching in Access.fetch/2
|
||||
|
||||
$ mix xref callers SomeModule --include-siblings
|
||||
The following arguments were given to Access.fetch/2:
|
||||
|
||||
The `graph` command in `mix xref` now can also output general statistics about the graph. In [the hexpm project](https://github.com/hexpm/hexpm), you would get:
|
||||
# 1
|
||||
:foo
|
||||
|
||||
$ mix xref graph --format stats
|
||||
Tracked files: 129 (nodes)
|
||||
Compile dependencies: 256 (edges)
|
||||
Structs dependencies: 46 (edges)
|
||||
Runtime dependencies: 266 (edges)
|
||||
# 2
|
||||
:bar
|
||||
|
||||
Top 10 files with most outgoing dependencies:
|
||||
* test/support/factory.ex (18)
|
||||
* lib/hexpm/accounts/user.ex (13)
|
||||
* lib/hexpm/accounts/audit_log.ex (12)
|
||||
* lib/hexpm/web/controllers/dashboard_controller.ex (12)
|
||||
* lib/hexpm/repository/package.ex (12)
|
||||
* lib/hexpm/repository/releases.ex (11)
|
||||
* lib/hexpm/repository/release.ex (10)
|
||||
* lib/hexpm/web/controllers/package_controller.ex (10)
|
||||
* lib/mix/tasks/hexpm.stats.ex (9)
|
||||
* lib/hexpm/repository/registry_builder.ex (9)
|
||||
Attempted function clauses (showing 5 out of 5):
|
||||
|
||||
Top 10 files with most incoming dependencies:
|
||||
* lib/hexpm/web/web.ex (84)
|
||||
* lib/hexpm/web/router.ex (29)
|
||||
* lib/hexpm/web/controllers/controller_helpers.ex (29)
|
||||
* lib/hexpm/web/controllers/auth_helpers.ex (28)
|
||||
* lib/hexpm/web/views/view_helpers.ex (27)
|
||||
* lib/hexpm/web/views/icons.ex (27)
|
||||
* lib/hexpm/web/endpoint.ex (23)
|
||||
* lib/hexpm/ecto/changeset.ex (22)
|
||||
* lib/hexpm/accounts/user.ex (19)
|
||||
* lib/hexpm/repo.ex (19)
|
||||
def fetch(-%struct{} = container-, key)
|
||||
def fetch(map, key) when -is_map(map)-
|
||||
def fetch(list, key) when -is_list(list)- and is_atom(key)
|
||||
def fetch(list, key) when -is_list(list)-
|
||||
def fetch(-nil-, _key)
|
||||
|
||||
`mix xref graph` also got the `--only-nodes` and `--label` options. The former asks Mix to only output file names (nodes) without the edges. The latter allows you to focus on certain relationships:
|
||||
(elixir) lib/access.ex:261: Access.fetch/2
|
||||
|
||||
# To get all files that depend on lib/foo.ex
|
||||
mix xref graph --sink lib/foo.ex --only-nodes
|
||||
In the example above, an argument that did not match or guard that did not evaluate to true are shown between `-`. If the terminal supports ANSI coloring, they are wrapped in red instead of the `-` character.
|
||||
|
||||
# To get all files that depend on lib/foo.ex at compile time
|
||||
mix xref graph --label compile --sink lib/foo.ex --only-nodes
|
||||
Since blaming an exception can be expensive, `Exception.blame/3` must be used exclusively in debugging situations. It is not advised to apply it to production components such as a Logger. This feature has been integrated into the compiler, the command line, ExUnit and IEx.
|
||||
|
||||
# To get all files lib/foo.ex depends on
|
||||
mix xref graph --source lib/foo.ex --only-nodes
|
||||
This feature also requires Erlang/OTP 20+.
|
||||
|
||||
# To limit statistics only to compile time dependencies
|
||||
mix xref graph --format stats --label compile
|
||||
## Streamlined child specs
|
||||
|
||||
Those improvements will help developers better understand the relationship between files and reveal potentially complex parts of their systems.
|
||||
Elixir v1.5 streamlines how supervisors are defined and used in Elixir. Elixir now allows child specifications, which specify how a child process is supervised, to be defined in modules. In previous versions, a project using Phoenix would write:
|
||||
|
||||
Other improvements in Mix include better compiler diagnostics for editor integration, support for the `--slowest N` flag in `mix test` that shows the slowest tests in your suite, and a new `mix profile.eprof` task that provides time based profiling, complementing the existing `mix profile.cprof` (count based) and `mix profile.fprof` (flame based).
|
||||
import Supervisor.Spec
|
||||
|
||||
## v1.6.0 (2018-01-17)
|
||||
children = [
|
||||
supervisor(MyApp.Repo, []),
|
||||
supervisor(MyApp.Endpoint, [])
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
In Elixir v1.5, one might do:
|
||||
|
||||
children = [
|
||||
MyApp.Repo,
|
||||
MyApp.Endpoint
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
The above works by calling the `child_spec/1` function on the given modules.
|
||||
|
||||
This new approach allows `MyApp.Repo` and `MyApp.Endpoint` to control how they run under a supervisor. This reduces the chances of mistakes being made, such as starting an Ecto repository as a worker or forgetting to declare that tasks are temporary in a supervision tree.
|
||||
|
||||
If it is necessary to configure any of the children, such can be done by passing a tuple instead of an atom:
|
||||
|
||||
children = [
|
||||
{MyApp.Repo, url: "ecto://localhost:4567/my_dev"},
|
||||
MyApp.Endpoint
|
||||
]
|
||||
|
||||
The modules `Agent`, `Registry`, `Task`, and `Task.Supervisor` have been updated to include a `child_spec/1` function, allowing them to be used directly in a supervision tree similar to the examples above. `use Agent`, `use GenServer`, `use Supervisor`, and `use Task` have also been updated to automatically define an overridable `child_spec/1` function.
|
||||
|
||||
Finally, child specifications are now provided as maps (data-structures) instead of the previous `Supervisor.Spec.worker/3` and `Supervisor.Spec.supervisor/3` APIs. This behaviour also aligns with how supervisors are configured in Erlang/OTP 18+. See the updated `Supervisor` docs for more information, as well as the new `Supervisor.init/2` and `Supervisor.child_spec/2` functions.
|
||||
|
||||
## @impl
|
||||
|
||||
This release also allows developers to mark which functions in a given module are an implementation of a callback. For example, when using the [Plug](https://github.com/elixir-lang/plug) project, one needs to implement both `init/1` and `call/2` when writing a Plug:
|
||||
|
||||
defmodule MyApp do
|
||||
@behaviour Plug
|
||||
|
||||
def init(_opts) do
|
||||
opts
|
||||
end
|
||||
|
||||
def call(conn, _opts) do
|
||||
Plug.Conn.send_resp(conn, 200, "hello world")
|
||||
end
|
||||
end
|
||||
|
||||
The problem with the approach above is that, once more and more functions are added to the `MyApp` module, it becomes increasingly harder to know the purposes of the `init/1` and `call/2` functions. For example, for a developer unfamiliar with Plug, are those functions part of the `MyApp` API or are they implementations of a given callback?
|
||||
|
||||
Elixir v1.5 introduces the `@impl` attribute, which allows us to mark that certain functions are implementation of callbacks:
|
||||
|
||||
defmodule MyApp do
|
||||
@behaviour Plug
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
opts
|
||||
end
|
||||
|
||||
@impl true
|
||||
def call(conn, _opts) do
|
||||
Plug.Conn.send_resp(conn, 200, "hello world")
|
||||
end
|
||||
end
|
||||
|
||||
You may even use `@impl Plug` if you want to explicitly document which behaviour defines the callback you are implementing.
|
||||
|
||||
Overall, using `@impl` has the following advantages:
|
||||
|
||||
* Readability of the code is increased, as it is now clear which functions are part of your API and which ones are callback implementations. To reinforce this idea, `@impl true` automatically marks the function as `@doc false`, disabling documentation unless `@doc` is explicitly set
|
||||
|
||||
* If you define `@impl` before a function that is not a callback, Elixir will error. This is useful in case of typos or in case the behaviour definition changes (such as a new major version of a library you depend on is released)
|
||||
|
||||
* If you use `@impl` in one implementation, Elixir will force you to declare `@impl` for all other implementations in the same module, keeping your modules consistent
|
||||
|
||||
## Calendar improvements
|
||||
|
||||
This release brings further improvements to Calendar types. It adds arithmetic and others functions to `Time`, `Date`, `NaiveDateTime` and `Datetime` as well as conversion between different calendars.
|
||||
|
||||
## v1.5.1 (2017-08-01)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Allow markers `/` and `|` to be used in a custom EEx engine
|
||||
* [EEx.Engine] Add `handle_begin` and `handle_end` to EEx
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Add truncate to `Time`, `DateTime` and `NaiveDateTime` to facilitate microsecond precision pruning
|
||||
* [Code] Add `format_string!/2` and `format_file!/2` for automatic code formatting
|
||||
* [Code] Support column annotations in quoted expressions with `columns: true` in `Code.string_to_quoted/2`
|
||||
* [DynamicSupervisor] Add `DynamicSupervisor` designed to manage children that are added and removed dynamically
|
||||
* [Exception] Make `Exception.blame/3` extensible by adding an optional `blame/2` callback to exceptions
|
||||
* [Exception] Improve the printing of guards on blamed exceptions
|
||||
* [Enumerable] Add `Enumerable.slice/1` and optimize many `Enum` operations with the new protocol. This allows data-structures with index-based random access to provide a non-linear implementation
|
||||
* [Inspect] Show UTF-8 BOM on inspected strings
|
||||
* [Inspect.Algebra] Add `:strict` and `:flex` breaks - this gives more control over the document fitting
|
||||
* [Inspect.Algebra] Allow a group to inherit the parent group break
|
||||
* [Inspect.Algebra] Add `force_unfit/1` and `next_break_fits/2` which give more control over document fitting
|
||||
* [Inspect.Algebra] Add `collapse_lines/1` for collapsing multiple lines to a maximum value
|
||||
* [Inspect.Algebra] Allow `nest/2` to be `:reset` or be set to the current `:cursor` position
|
||||
* [Kernel] Prefix variables with V when emitting Erlang code. This improves the integration with tools such as Erlang code formatters and the GUI debugger
|
||||
* [Kernel] Warn on the use of `length(x) == 0` in guards
|
||||
* [Kernel] Warn if `catch` comes before `rescue` in try
|
||||
* [Kernel] Warn if heredoc is outdented compared to its closing quotes
|
||||
* [Kernel] Add `defguard/1` and `defguardp/1` to make it easier to build guard-safe macros
|
||||
* [Kernel.ParallelCompiler] Add `compile/2`, `compile_to_path/3` and `require/2` which provide detailed information about warnings and errors
|
||||
* [Kernel.SpecialForms] Support the `uniq: true` flag in `for` comprehensions
|
||||
* [Module] Introduce `@deprecated` and `@since` attributes
|
||||
* [List] Rearrange equals and inserts for shorter diff scripts in `List.myers_difference/2`
|
||||
* [Record] Allow `:macros` and `:includes` to be given to `Record.extract/2`
|
||||
* [Stream] Add `Stream.intersperse/2`
|
||||
* [String] Update to Unicode 10
|
||||
* [String] Allow passing empty string `match` to `String.replace/4`
|
||||
* [String] Support context and language sensitive operations in `String.upcase/2` and `String.downcase/2`. Currently only the `:greek` context is supported
|
||||
* [String] Support `:ascii` conversion in `String.upcase/2` and `String.downcase/2`
|
||||
* [Time] Add `Time.add/3`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Perform inclusive checks in `assert_in_delta`
|
||||
* [ExUnit.Callbacks] Add `ExUnit.Callbacks.start_supervised!/2`
|
||||
* [ExUnit.Case] Generate a random seed per test based on the test suite seed
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Provide contextual autocompletion: `t Enum.` will autocomplete types, `b Enum` will autocomplete callbacks
|
||||
* [IEx.CLI] Provide hints for developers when a bad host name is given to `--remsh`
|
||||
* [IEx.Helpers] Automatically include specs when showing documentation for functions/macros
|
||||
* [IEx.Helpers] Improve formatting of behaviours and typespecs by using the formatter
|
||||
* [IEx.Helpers] Allow pattern matching and guard expressions when on `IEx.break!`
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Add `:discard_threshold` to Logger to help with message queue overflow
|
||||
* [Kernel] Do not use references on function/macro definitions - this provides large improvements in compilation times in some rare corner cases
|
||||
* [Supervisor] Support mixing old and new typespecs in `Supervisor.init/2` and `Supevisor.start_link/2`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix app.start] Add `--preload-modules` to `mix app.start`
|
||||
* [mix archive.build] Allow `mix archive.build` to bundle dot files via an option
|
||||
* [mix compile] Define a behavior for Mix compiler tasks and return diagnostics from compiler tasks
|
||||
* [mix compile] Track struct dependencies between files and recompile them only if the struct changes
|
||||
* [mix deps] Support `:system_env` option when specifying dependencies
|
||||
* [mix format] Add a `mix format` task that formats the given files (or the files specified in a `.formatter.exs` file)
|
||||
* [mix profile.eprof] Add a new task for time-based profiling with eprof
|
||||
* [mix test] Run all functions in a describe block by giving the `file:line` the describe block starts
|
||||
* [mix test] Report the top N slowest tests with the `--slowest N` flag
|
||||
* [mix test] Report the number of doctests and tests separately
|
||||
* [mix xref] Support `--include-siblings` in reports for umbrella support
|
||||
* [mix xref] Add `mix xref graph --format stats`
|
||||
* [mix xref] Add `--only-nodes` and `--label` filters to mix xref graph
|
||||
* [mix xref] Add `mix xref deprecated` that shows the callsite of deprecated functions
|
||||
* [mix profile.*] Allow profile tasks to run without a project
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx.Engine] Do not re-use the value of the `init/1` callback throughout the compilation stack
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Ensure dialyzer does not emit warnings in some uses of `with`
|
||||
* [Kernel] Fix dialyzer warnings when `defmacrop` is used in modules
|
||||
* [Kernel] Ensure Elixir modules can be dialyzed without starting the Elixir application
|
||||
* [Kernel] Do not serialize references in quoted expressions
|
||||
* [Kernel] Make sure structs expansion use the latest definition available when struct modules are recompiled
|
||||
* [Task] Support `:infinity` timeout on Task streams
|
||||
* [Typespec] Ensure typespecs allow `tuple` to be used as variable names
|
||||
|
||||
## v1.5.0 (2017-07-25)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Access] Optimize `Access.get/2`
|
||||
* [Base] Optimise Base encode/decode
|
||||
* [Calendar] Implement Inspect for DateTime with Calendar.ISO
|
||||
* [Calendar] Add "ISO days" format for conversions between Calendars and `Date.convert/2`, `Time.convert/2`, `NaiveDateTime.convert/2` and `DateTime.convert/2` (as well as bang variants)
|
||||
* [Calendar] Add `:calendar` field to `Time` struct
|
||||
* [Calendar] Add `Time.diff/3`, `Date.add/2`, `Date.diff/2`, `DateTime.diff/3`
|
||||
* [Calendar] Add `Date.range/2`
|
||||
* [Calendar] Add `Date.new/4`, `DateTime.utc_now/1`, `NaiveDateTime.new/8` and `Time.new/5` that allow specifing calendar
|
||||
* [Enum] Add `Enum.chunk_by/4` and `Stream.chunk_by/4`
|
||||
* [Enum] Add `Enum.chunk_every/2` and `Enum.chunk_every/4` with a more explicit API than `Enum.chunk/2` and `Enum.chunk/4`
|
||||
* [Exception] Add `Exception.blame/3` that adds metadata to exceptions
|
||||
* [File] Add `File.read_link/1` and `File.read_link!/1`
|
||||
* [File] Introduce `:trim_bom` option for `File.stream!/2`
|
||||
* [Inspect] Add `:printable_limit` to control the limit of printable structures
|
||||
* [Integer] Add `Integer.gcd/2`
|
||||
* [Kernel] Add `left not in right` to check that the left side is not in the enumerable on the right
|
||||
* [Kernel] Use the new `debug_info` chunk in OTP 20. This provides a mechanism for tools to retrieve the Elixir AST from beam files
|
||||
* [Kernel] `defoverridable/1` accepts a module name as argument and marks all callbacks as overridable
|
||||
* [Kernel] Allow non-quoted Unicode atoms and variables according to Unicode Annex #31 (see Unicode Syntax document)
|
||||
* [Kernel] Warn when a `:__struct__` key is used when building/updating structs
|
||||
* [Kernel] Cache the AST on definitions. This speeds up the compilation time from 10% to 15% measured across different projects
|
||||
* [Kernel] Improve compiler error message on invalid patterns and guards
|
||||
* [Keyword] Add `replace/3` and `replace!/3` for replacing an existing key
|
||||
* [List] `List.starts_with?/2`
|
||||
* [Macro] Introduce `Macro.generate_arguments/2`
|
||||
* [Map] Optimize `Map.merge/3` by choosing merging direction
|
||||
* [Map] Add `replace/3` and `replace!/3` for replacing an existing key
|
||||
* [Map] Raise `BadMapError` in `Map.equal?/2` when either of the two arguments is not a map
|
||||
* [MapSet] Reduce `MapSet` size when serialized to approximately half
|
||||
* [Process] Add `Process.cancel_timer/2`
|
||||
* [Protocol] Show available implementations on `Protocol.UndefinedError` if the protocol has been consolidated
|
||||
* [Registry] Support ETS guard conditions in `Registry.match/3`
|
||||
* [Registry] Support `parallel: true` in `Registry.dispatch/3`
|
||||
* [Registry] Introduce `Registry.unregister_match/4`
|
||||
* [Stream] Add `Stream.chunk_every/2` and `Stream.chunk_every/4` with a more explicit API than `Stream.chunk/2` and `Stream.chunk/4`
|
||||
* [String] Optimise binary pattern matching in `String.split/1` and `String.trim_*/1`
|
||||
* [Supervisor] Add `Supervisor.init/2` and `Supervisor.child_spec/2`
|
||||
* [Supervisor] Allow `module` and `{module, arg}` to be given to `Supervisor.start_link/2` and invoke `module.child_spec(arg)` on each argument
|
||||
* [Task] Support `:on_timeout` in `Task.async_stream` to control how tasks are terminated
|
||||
* [Task] Add `ordered: false` support to `Task.async_stream`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Show code snippet from test source file in case of test errors
|
||||
* [ExUnit] Use `Exception.blame/3` when formatting test errors
|
||||
* [ExUnit] Make `assert_raise/2` fail if the underlying exception has a broken `message/1` implementation
|
||||
* [ExUnit] Add `start_supervised/2` and `stop_supervised/1` to ExUnit. Processes started by this function are automatically shut down when the test exits
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Support autocompletion of variable names
|
||||
* [IEx.Autocomplete] Support autocompletion of functions imported using `import Mod, only: [...]`
|
||||
* [IEx.Evaluator] Use `Exception.blame/3` when showing errors in the terminal
|
||||
* [IEx.Helpers] Add `exports/1` IEx helper to list all exports in a module
|
||||
* [IEx.Helpers] Add `break!/2`, `break!/4`, `breaks/0`, `continue/0`, `open/0`, `remove_breaks/0`, `remove_breaks/1`, `reset_break/1`, `reset_break/3` and `whereami/1` for code debugging
|
||||
* [IEx.Helpers] No longer emit warnings for IEx commands without parentheses
|
||||
* [IEx.Helpers] Add `runtime_info/0` for printing runtime system information
|
||||
* [IEx.Helpers] Add `open/1` to open the source of a given module/function in your editor
|
||||
* [IEx.Info] Implement `IEx.Info` protocol for calendar types
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Add `metadata: :all` configuration to log all metadata
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Add `--all-warnings` option to Elixir compiler that shows all warnings from the previous compilation (instead of just of the files being compiled)
|
||||
* [mix escript.build] Strip debug information from escripts by default and add option `:strip_beam` which defaults to true
|
||||
* [mix loadpaths] Ensure `--no-deps-check` do not trigger SCM callbacks (such as `git`)
|
||||
* [mix local.hex] Add `--if-missing` flag to `local.hex` mix task
|
||||
* [mix profile.cprof] Add `Mix.Tasks.Profile.Cprof` for count-based profiling
|
||||
* [mix new] New styling for generated applications
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Support path with spaces as argument to elixir.bat
|
||||
* [Inspect] Properly handle minus signal for non-decimal negative integers
|
||||
* [Integer] Do not raise on non-integer values in `is_odd`/`is_even`
|
||||
* [Kernel] Solve a precedence issue between `&` and `|`, such as `[&Foo.bar/1 | &Baz.bat/2]`
|
||||
* [Kernel] Do not load dynamic Elixir modules as `:in_memory` as this value is not officially supported by the code server. Instead, use an empty list, which is the same value used by Erlang.
|
||||
* [Kernel] Validate variable struct name is atom when used in pattern matching
|
||||
* [Macro] Fix `Macro.to_string/2` for tuple calls, such as `alias Foo.{Bar, Baz}`
|
||||
* [MapSet] Return valid MapSet when unioning a legacy MapSet
|
||||
* [Regex] Return a leading empty space when splitting on empty pattern. This makes the `split` operation consistent with the other operations in the `Regex` module
|
||||
* [Stream] Ensure `Stream.chunk_while/4` does not emit more elements than necessary when halted
|
||||
* [String] Return a leading empty space when splitting on empty string. This makes the `split` operation consistent with the other operations in the `String` module
|
||||
* [URI] Preserve empty fragments in `URI.parse/1`
|
||||
* [Calendar] Ensure `Calendar.ISO` raises a readable error when reaching up the year 10000 restriction
|
||||
* [Calendar] Return `{:error, :invalid_time}` for wrong precision instead of crashing when parsing ISO dates
|
||||
* [Enumerable] Raise `Protocol.UndefinedError` on bad functions in Enumerable implementation
|
||||
* [File] Ensure recursive file operations raise on paths with null bytes (*security issue reported by Griffin Byatt*)
|
||||
* [File] Support `:ram`/`:raw` files in `File.copy/2`
|
||||
* [Inspect] Do not use colors when inspecting error messages
|
||||
* [Kernel] Support guards on anonymous functions of zero arity
|
||||
* [Kernel] Fix compilation of maps used as maps keys inside matches
|
||||
* [Kernel] Ensure `do` clause in `with` is tail call optimizable
|
||||
* [Module] `on_definition/6` callback receives body wrapped in a keyword list, such as `[do: body]`. This solves a bug where it was impossible to distinguish between a bodyless clause and a function that returns `nil`.
|
||||
* [Path] Ensure recursive path operations raise on paths with null bytes (*security issue reported by Griffin Byatt*)
|
||||
* [Protocol] Do not lose source compile info on protocol consolidation
|
||||
* [Record] Properly escape quoted expressions passed to `defrecord`
|
||||
* [Regex] Fix `inspect/2` for regexes with `/` terminator in them
|
||||
* [Registry] Ensure `Registry.match/4` works with `:_` as key
|
||||
* [Stream] Fix stream cycle over empty enumerable
|
||||
* [String] Consider Unicode non-characters valid according to the specification in `String.valid?/1`
|
||||
* [StringIO] Fix encoding and performance issues in `StringIO.get_until`
|
||||
* [System] Raise on paths with null bytes in `System.cmd/2` and in `System.find_executable/1` (*security issue reported by Griffin Byatt*)
|
||||
* [System] Raise on ill-formed environment variables (*security issue reported by Griffin Byatt*)
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Properly account failed tests when `setup_all` fails
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Skip autocompletion of module names that are invalid without being quoted
|
||||
* [IEx] Skip autocompletion of functions with default arguments with `@doc false`
|
||||
* [IEx] Do not start oldshell alongside IEx
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix app.start] Improve the quality of reports if app fails to boot
|
||||
* [mix cmd] Allow `mix cmd` to be invoked multiple times without marking it as executed
|
||||
* [mix deps] Ensure optional dependencies in umbrella applications are loaded
|
||||
* [mix deps.update] Ensure transitive new non-Hex dependencies are also fetched when a repo is updated
|
||||
* [mix xref] Take compile dependencies with higher priority than runtime ones when building a graph
|
||||
* [mix xref] Handle external files for xref callers and warnings
|
||||
* [mix compile.elixir] Store multiple sources in case of module conflicts. This solves an issue where `_build` would get corrupted when compiling Elixir projects with module conflicts
|
||||
* [mix compile.erlang] Do not silently discard Erlang compile errors
|
||||
* [mix compile.erlang] Properly track `-compile` module attribute when specified as a list
|
||||
* [mix compile.protocols] Ensure protocol implementations do not "disappear" when switching between applications in umbrella projects by having separate consolidation paths per project
|
||||
* [mix compile.protocols] Do not raise when consolidating a protocol that was converted into a module
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect.Algebra] `surround/3` and `surround_many/6` are deprecated in favor of `container_doc/6`
|
||||
* [Kernel.ParallelCompiler] `files/2` and `files_to_path/3` are deprecated in favor of `compile/2` and `compile_to_path/3`
|
||||
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
|
||||
* [GenServer] Warn if `init/1` is not defined in `GenServer`. This brings GenServer closer to the implementation in OTP and aligns all behaviours to require the `init/1` callback
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Formatter] `:case_started` and `:case_finished` events are deprecated in favor of `:module_started` and `:module_finished`
|
||||
* [Kernel] `not left in right` is soft-deprecated in favor of `left not in right`
|
||||
|
||||
### 4. Deprecations
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] `Enum.partition/2` is deprecated in favor of `Enum.split_with/2`
|
||||
* [Keyword] `Keyword.replace/3` is deprecated in favor of `Keyword.fetch/2` and `Keyword.put/3`
|
||||
* [Map] `Map.replace/3` is deprecated in favor of `Map.fetch/2` and `Map.put/3`
|
||||
* [Macro] `Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` are deprecated in favor of `Enum.map/2`
|
||||
* [Range] Deprecate `Range.range?/1` in favor of pattern matching on `_ .. _`
|
||||
* `Atom.to_char_list/1`, `Float.to_char_list/1`, `Integer.to_char_list/1`, `Integer.to_char_list/2`, `Kernel.to_char_list/1`, `List.Chars.to_char_list/1`, `String.to_char_list/1` have been deprecated in favor of their `to_charlist` version. This aligns with the naming conventions in both Erlang and Elixir
|
||||
* [Enum] Deprecate `Enum.filter_map/3` in favor of `Enum.filter/2` + `Enum.map/2` or for-comprehensions
|
||||
* [GenEvent] Deprecate `GenEvent` and provide alternatives in its docs
|
||||
* [Kernel] Using `()` to mean `nil` is deprecated
|
||||
* [Kernel] `:as_char_lists value` in `Inspect.Opts.t/0` type, in favor of `:as_charlists`
|
||||
* [Kernel] `:char_lists` key in `Inspect.Opts.t/0` type, in favor of `:charlists`
|
||||
* [Module] Using Erlang parse transforms via `@compile {:parse_transform, _}` is deprecated
|
||||
* [Stream] Deprecate `Stream.filter_map/3` in favor of `Stream.filter/2` + `Stream.map/2`
|
||||
* [String] `String.ljust/3` and `String.rjust/3` are deprecated in favor of `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding
|
||||
* [String] `String.strip/1` and `String.strip/2` are deprecated in favor of `String.trim/1` and `String.trim/2`
|
||||
* [String] `String.lstrip/1` and `String.rstrip/1` are deprecated in favor of `String.trim_leading/1` and `String.trim_trailing/1`
|
||||
* [String] `String.lstrip/2` and `String.rstrip/2` are deprecated in favor of `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument
|
||||
* [Typespec] `char_list/0` type is deprecated in favor of `charlist/0`
|
||||
|
||||
## v1.5
|
||||
#### EEx
|
||||
|
||||
The CHANGELOG for v1.5 releases can be found [in the v1.5 branch](https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md).
|
||||
* [EEx] Deprecate `<%= ` in "middle" and "end" expressions, e.g.: `<%= else %>` and `<%= end %>`
|
||||
|
||||
## v1.4
|
||||
|
||||
The CHANGELOG for v1.4 releases can be found [in the v1.4 branch](https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md).
|
||||
|
||||
@@ -1,201 +1,13 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
Copyright 2012 Plataformatec
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
1. Definitions.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2012 Plataformatec
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,7 +1,7 @@
|
||||
REBAR ?= "$(CURDIR)/rebar"
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
CANONICAL := v1.6/
|
||||
CANONICAL := v1.5/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -22,11 +22,11 @@ GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$hea
|
||||
#==> Functions
|
||||
|
||||
define CHECK_ERLANG_RELEASE
|
||||
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 19)])' -s erlang halt | grep -q '^true'; \
|
||||
$(Q) erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 18)])' -s erlang halt | grep -q '^true'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 19.0 is required to build Elixir"; \
|
||||
echo "At least Erlang 18.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi
|
||||
fi;
|
||||
endef
|
||||
|
||||
define APP_TEMPLATE
|
||||
@@ -56,7 +56,14 @@ UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
compile: erlang elixir
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) $(call CHECK_ERLANG_RELEASE)
|
||||
$(Q) rm -f lib/elixir/src/elixir.app.src
|
||||
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
|
||||
>lib/elixir/src/elixir.app.src
|
||||
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
@@ -69,7 +76,6 @@ elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
$(call CHECK_ERLANG_RELEASE); \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler bootstrap -s erlang halt; \
|
||||
fi
|
||||
@@ -112,6 +118,7 @@ clean:
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -f lib/elixir/src/elixir.app.src
|
||||
$(Q) $(MAKE) clean_residual_files
|
||||
|
||||
clean_elixir:
|
||||
@@ -140,7 +147,7 @@ docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_e
|
||||
docs_elixir: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (elixir)"
|
||||
$(Q) rm -rf doc/elixir
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,-c lib/elixir/docs.exs)
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,-e "lib/elixir/pages/Behaviours.md" -e "lib/elixir/pages/Deprecations.md" -e "lib/elixir/pages/Guards.md" -e "lib/elixir/pages/Naming Conventions.md" -e "lib/elixir/pages/Operators.md" -e "lib/elixir/pages/Syntax Reference.md" -e "lib/elixir/pages/Typespecs.md" -e "lib/elixir/pages/Unicode Syntax.md" -e "lib/elixir/pages/Writing Documentation.md")
|
||||
|
||||
docs_eex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (eex)"
|
||||
@@ -180,7 +187,7 @@ Docs.zip: docs
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled-v$(VERSION).zip
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
|
||||
@@ -32,10 +32,10 @@ If tests pass, you are ready to move on to the [Getting Started guide][1]
|
||||
or to try Interactive Elixir by running `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version
|
||||
(Elixir requires Erlang 19.0 or later). You can check your Erlang version
|
||||
(Elixir requires Erlang 18.0 or later). You can check your Erlang version
|
||||
by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang/OTP 19 [erts-8.0] [smp:2:2] [async-threads:10] [kernel-poll:false]
|
||||
Erlang/OTP 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have properly set up your dependencies and tests still fail,
|
||||
you may want to open up a bug report, as explained next.
|
||||
@@ -46,30 +46,11 @@ For reporting bugs, [visit our issues tracker][2] and follow the steps
|
||||
for reporting a new issue. Please disclose security vulnerabilities
|
||||
privately at elixir-security@googlegroups.com.
|
||||
|
||||
## Proposing new features
|
||||
|
||||
For proposing new features, please start a discussion in the
|
||||
[Elixir Core mailing list][3]. Keep in mind that it is your responsibility
|
||||
to argue and explain why a feature is useful and how it will impact the
|
||||
codebase and the community.
|
||||
|
||||
Once a proposal is accepted, it will be added to [the issues tracker][2].
|
||||
The issues tracker focuses on *actionable items* and it holds a list of
|
||||
upcoming enhancements and pending bugs. All entries in the tracker are
|
||||
tagged for clarity and to ease collaboration.
|
||||
|
||||
Features and bug fixes that have already been merged and will be included
|
||||
in the next release are marked as "closed" in the issues tracker and are
|
||||
added to the [CHANGELOG](CHANGELOG.md).
|
||||
|
||||
Finally, remember all interactions in our official spaces follow our
|
||||
[Code of Conduct][7].
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome everyone to contribute to Elixir. To do so, there are a few
|
||||
things you need to know about the code. First, Elixir code is divided
|
||||
in applications inside the `lib` folder:
|
||||
We welcome everyone to contribute to Elixir and help us tackle existing issues!
|
||||
To do so, there are a few things you need to know about the code. First, Elixir
|
||||
code is divided in applications inside the `lib` folder:
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib
|
||||
|
||||
@@ -86,7 +67,8 @@ in applications inside the `lib` folder:
|
||||
You can run all tests in the root directory with `make test` and you can
|
||||
also run tests for a specific framework `make test_#{NAME}`, for example,
|
||||
`make test_ex_unit`. If you just changed something in the Elixir's standard
|
||||
library, you can run only that portion through `make test_stdlib`.
|
||||
library, you can run only that portion through `make test_stdlib`, as
|
||||
`test_elixir` also runs tests for the other projects (EEx, ExUnit, etc.).
|
||||
|
||||
In case you are changing a single file, you can compile and run tests only
|
||||
for that particular file for fast development cycles. For example, if you
|
||||
@@ -103,22 +85,29 @@ To recompile (including Erlang modules):
|
||||
make compile
|
||||
```
|
||||
|
||||
After your changes are done, please remember to run the full suite with
|
||||
`make test` and then `mix format` to guarantee all files are properly
|
||||
formatted.
|
||||
|
||||
If your contribution fails during the bootstrapping of the language,
|
||||
you can rebuild the language from scratch with:
|
||||
If your contribution fails the build during the bootstrapping of the language,
|
||||
you can reproduce it locally by deleting all of Elixir beam files and compiling
|
||||
again:
|
||||
|
||||
```sh
|
||||
make clean_elixir compile
|
||||
```
|
||||
|
||||
Similarly, if you can't get Elixir to compile or the tests to pass after
|
||||
updating an existing checkout, run `make clean compile`. You can check
|
||||
[the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
Or to rebuild everything from scratch without running tests:
|
||||
|
||||
```sh
|
||||
make clean compile
|
||||
```
|
||||
|
||||
More tasks can be found by reading the [Makefile](./Makefile).
|
||||
|
||||
After your changes are done, please remember to run the full suite with
|
||||
`make test`.
|
||||
|
||||
From time to time, your tests may fail in an existing Elixir checkout and
|
||||
may require a clean start by running `make clean compile`. You can always
|
||||
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
[send a pull request](https://help.github.com/articles/using-pull-requests/).
|
||||
We have saved some excellent pull requests we have received in the past in
|
||||
@@ -128,28 +117,28 @@ case you are looking for some examples:
|
||||
* [Add String.valid? – Pull Request](https://github.com/elixir-lang/elixir/pull/1058)
|
||||
* [Implement capture_io for ExUnit – Pull Request](https://github.com/elixir-lang/elixir/pull/1059)
|
||||
|
||||
We usually keep a list of enhancements and bugs [in the issue tracker][2].
|
||||
For proposing new features, please start a discussion in the
|
||||
[Elixir Core mailing list][3]. Keep in mind that it is your responsibility
|
||||
to argue and explain why a feature is useful and how it will impact the
|
||||
codebase and the community. Finally, remember all interactions in our official
|
||||
spaces follow our [Code of Conduct][7].
|
||||
|
||||
### Reviewing changes
|
||||
|
||||
Once a pull request is sent, the Elixir team will review your changes.
|
||||
We outline our process below to clarify the roles of everyone involved.
|
||||
|
||||
All pull requests must be approved by two committers before being merged into
|
||||
the repository. If any changes are necessary, the team will leave appropriate
|
||||
comments requesting changes to the code. Unfortunately we cannot guarantee a
|
||||
pull request will be merged, even when modifications are requested, as the Elixir
|
||||
team will re-evaluate the contribution as it changes.
|
||||
|
||||
Committers may also push style changes directly to your branch. If you would
|
||||
rather manage all changes yourself, you can disable "Allow edits from maintainers"
|
||||
feature when submitting your pull request.
|
||||
the repository. In case any changes are necessary, the team will leave
|
||||
appropriate comments requesting changes to the code.
|
||||
|
||||
The Elixir team may optionally assign someone to review a pull request.
|
||||
In case someone is assigned, they must explicitly approve the code before
|
||||
another team member can merge it.
|
||||
|
||||
When the review finishes, your pull request will be squashed and merged
|
||||
into the repository. If you have carefully organized your commits and
|
||||
believe they should be merged without squashing, leave a comment.
|
||||
When review is completed, your pull request will be squashed and merged
|
||||
into the repository.
|
||||
|
||||
## Building documentation
|
||||
|
||||
@@ -163,20 +152,20 @@ cd ex_doc && ../elixir/bin/mix do deps.get, compile
|
||||
cd ../elixir && make docs
|
||||
```
|
||||
|
||||
This will produce documentation sets for `elixir`, `mix`, etc. under
|
||||
This will produce documentation sets for `elixir`, `mix`, etc., under
|
||||
the `doc` directory. If you are planning to contribute documentation,
|
||||
[please check our best practices for writing documentation](https://hexdocs.pm/elixir/writing-documentation.html).
|
||||
|
||||
## Development links
|
||||
|
||||
* [Elixir Getting Started guide][1]
|
||||
* [Elixir Website][1]
|
||||
* [Elixir Documentation][6]
|
||||
* [Elixir Core Mailing list (development)][3]
|
||||
* [Issues tracker][2]
|
||||
* [Code of Conduct][7]
|
||||
* **[#elixir-lang][4]** on [Freenode][5] IRC
|
||||
|
||||
[1]: https://elixir-lang.org/getting-started/introduction.html
|
||||
[1]: http://elixir-lang.org
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: https://groups.google.com/group/elixir-lang-core
|
||||
[4]: https://webchat.freenode.net/?channels=#elixir-lang
|
||||
|
||||
+9
-11
@@ -10,28 +10,26 @@ This document simply outlines the release process:
|
||||
|
||||
3. Ensure CHANGELOG is updated and add current date
|
||||
|
||||
4. Update "Compatibility and Deprecations" if a new OTP version is supported. If a new `vMAJOR.MINOR`, replace "master" with "vVERSION" in the "Deprecations" section
|
||||
4. If a new `vMAJOR.MINOR`, replace "master" with "vVERSION" in the "Deprecations" page and commit
|
||||
|
||||
5. If a new `vMAJOR.MINOR`, create a new branch "vMAJOR.MINOR" and set `CANONICAL=` in Makefile
|
||||
|
||||
6. Add an entry for the new version to the OTP compatibility table in the "Compatibility and Deprecations" page
|
||||
6. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
7. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
7. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
8. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
8. Recompile an existing project (for example, Ecto) to ensure manifests can be upgraded
|
||||
|
||||
9. Recompile an existing project (for example, Ecto) to ensure manifests can be upgraded
|
||||
9. Push branch and the new tag
|
||||
|
||||
10. Push branch and the new tag
|
||||
10. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases
|
||||
|
||||
11. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases
|
||||
11. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
12. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
13. After a new `vMAJOR.MINOR`, move back to master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vMAJOR.MINOR+1"
|
||||
12. After a new `vMAJOR.MINOR`, move back to master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vMAJOR.MINOR+1"
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG.md
|
||||
* lib/elixir/src/elixir.app.src
|
||||
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ if """"=="%par:--sname=%" (set parsErlang=%parsErlang% -sname %1 &
|
||||
if """"=="%par:--name=%" (set parsErlang=%parsErlang% -name %1 && shift)
|
||||
if """"=="%par:--logger-otp-reports=%" (set parsErlang=%parsErlang% -logger handle_otp_reports %1 && shift)
|
||||
if """"=="%par:--logger-sasl-reports=%" (set parsErlang=%parsErlang% -logger handle_sasl_reports %1 && shift)
|
||||
if """"=="%par:--erl=%" (set "beforeExtra=%beforeExtra% %~1" && shift)
|
||||
if """"=="%par:--erl=%" (set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
goto:startloop
|
||||
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
|
||||
+14
-14
@@ -112,13 +112,13 @@ defmodule EEx do
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding() do
|
||||
info = Keyword.merge([file: __ENV__.file, line: __ENV__.line], options)
|
||||
args = Enum.map(args, fn arg -> {arg, [line: info[:line]], nil} end)
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> {arg, [line: info[:line]], nil} end
|
||||
compiled = EEx.compile_string(source, info)
|
||||
|
||||
case kind do
|
||||
:def -> def unquote(name)(unquote_splicing(args)), do: unquote(compiled)
|
||||
:defp -> defp unquote(name)(unquote_splicing(args)), do: unquote(compiled)
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -149,15 +149,15 @@ defmodule EEx do
|
||||
"""
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding() do
|
||||
info = Keyword.merge(options, file: file, line: 1)
|
||||
args = Enum.map(args, fn arg -> {arg, [line: 1], nil} end)
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@external_resource file
|
||||
@file file
|
||||
case kind do
|
||||
:def -> def unquote(name)(unquote_splicing(args)), do: unquote(compiled)
|
||||
:defp -> defp unquote(name)(unquote_splicing(args)), do: unquote(compiled)
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -166,7 +166,7 @@ defmodule EEx do
|
||||
Gets a string `source` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
@spec compile_string(String.t(), keyword) :: Macro.t() | no_return
|
||||
@spec compile_string(String.t, keyword) :: Macro.t | no_return
|
||||
def compile_string(source, options \\ []) when is_binary(source) and is_list(options) do
|
||||
EEx.Compiler.compile(source, options)
|
||||
end
|
||||
@@ -175,9 +175,9 @@ defmodule EEx do
|
||||
Gets a `filename` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
@spec compile_file(String.t(), keyword) :: Macro.t() | no_return
|
||||
@spec compile_file(String.t, keyword) :: Macro.t | no_return
|
||||
def compile_file(filename, options \\ []) when is_binary(filename) and is_list(options) do
|
||||
options = Keyword.merge(options, file: filename, line: 1)
|
||||
options = Keyword.merge options, [file: filename, line: 1]
|
||||
compile_string(File.read!(filename), options)
|
||||
end
|
||||
|
||||
@@ -190,7 +190,7 @@ defmodule EEx do
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
@spec eval_string(String.t(), keyword, keyword) :: any
|
||||
@spec eval_string(String.t, keyword, keyword) :: any
|
||||
def eval_string(source, bindings \\ [], options \\ [])
|
||||
when is_binary(source) and is_list(bindings) and is_list(options) do
|
||||
compiled = compile_string(source, options)
|
||||
@@ -209,10 +209,10 @@ defmodule EEx do
|
||||
EEx.eval_file "sample.eex", [bar: "baz"] #=> "foo baz"
|
||||
|
||||
"""
|
||||
@spec eval_file(String.t(), keyword, keyword) :: any
|
||||
@spec eval_file(String.t, keyword, keyword) :: any
|
||||
def eval_file(filename, bindings \\ [], options \\ [])
|
||||
when is_binary(filename) and is_list(bindings) and is_list(options) do
|
||||
options = Keyword.put(options, :file, filename)
|
||||
options = Keyword.put options, :file, filename
|
||||
compiled = compile_file(filename, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
end
|
||||
|
||||
+24
-67
@@ -9,25 +9,17 @@ defmodule EEx.Compiler do
|
||||
and the engine together by handling the tokens and invoking
|
||||
the engine every time a full expression or text is received.
|
||||
"""
|
||||
@spec compile(String.t(), keyword) :: Macro.t() | no_return
|
||||
@spec compile(String.t, keyword) :: Macro.t | no_return
|
||||
def compile(source, opts) when is_binary(source) and is_list(opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
trim = opts[:trim] || false
|
||||
|
||||
case EEx.Tokenizer.tokenize(source, line, trim: trim) do
|
||||
{:ok, tokens} ->
|
||||
state = %{
|
||||
engine: opts[:engine] || @default_engine,
|
||||
file: file,
|
||||
line: line,
|
||||
quoted: [],
|
||||
start_line: nil
|
||||
}
|
||||
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
init = state.engine.init(opts)
|
||||
generate_buffer(tokens, init, [], state)
|
||||
|
||||
{:error, line, message} ->
|
||||
raise EEx.SyntaxError, line: line, file: file, message: message
|
||||
end
|
||||
@@ -42,78 +34,53 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([{:expr, line, mark, chars} | rest], buffer, scope, state) do
|
||||
expr = Code.string_to_quoted!(chars, line: line, file: state.file)
|
||||
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), expr)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{:start_expr, start_line, mark, chars} | rest], buffer, scope, state) do
|
||||
{contents, line, rest} = look_ahead_text(rest, start_line, chars)
|
||||
|
||||
{contents, rest} =
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [contents | scope], %{
|
||||
state
|
||||
| quoted: [],
|
||||
line: line,
|
||||
start_line: start_line
|
||||
})
|
||||
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [contents | scope],
|
||||
%{state | quoted: [], line: line, start_line: start_line})
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), contents)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{:middle_expr, line, '', chars} | rest], buffer, [current | scope], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
state = %{state | line: line}
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [wrapped | scope], state)
|
||||
generate_buffer(rest, state.engine.handle_begin(buffer), [wrapped | scope], %{state | line: line})
|
||||
end
|
||||
|
||||
defp generate_buffer(
|
||||
[{:middle_expr, line, modifier, chars} | t],
|
||||
buffer,
|
||||
[_ | _] = scope,
|
||||
state
|
||||
) do
|
||||
message =
|
||||
"unexpected beginning of EEx tag \"<%#{modifier}\" on \"<%#{modifier}#{chars}%>\", " <>
|
||||
"please remove \"#{modifier}\" accordingly"
|
||||
|
||||
:elixir_errors.warn(line, state.file, message)
|
||||
defp generate_buffer([{:middle_expr, line, modifier, chars} | t], buffer, scope, state) do
|
||||
message = "unexpected beginning of EEx tag \"<%#{modifier}\" on \"<%#{modifier}#{chars}%>\", " <>
|
||||
"please remove \"#{modifier}\" accordingly"
|
||||
:elixir_errors.warn line, state.file, message
|
||||
generate_buffer([{:middle_expr, line, '', chars} | t], buffer, scope, state)
|
||||
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
|
||||
# raise EEx.SyntaxError, message: message, file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:middle_expr, line, _, chars} | _], _buffer, [], state) do
|
||||
raise EEx.SyntaxError,
|
||||
message: "unexpected middle of expression <%#{chars}%>",
|
||||
file: state.file,
|
||||
line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, '', chars} | rest], buffer, [current | _], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = Code.string_to_quoted!(wrapped, line: state.start_line, file: state.file)
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quoted(tuples, state.quoted)
|
||||
{buffer, rest}
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, modifier, chars} | t], buffer, [_ | _] = scope, state) do
|
||||
message =
|
||||
"unexpected beginning of EEx tag \"<%#{modifier}\" on end of " <>
|
||||
"expression \"<%#{modifier}#{chars}%>\", please remove \"#{modifier}\" accordingly"
|
||||
|
||||
:elixir_errors.warn(line, state.file, message)
|
||||
message = "unexpected beginning of EEx tag \"<%#{modifier}\" on end of expression \"<%#{modifier}#{chars}%>\", " <>
|
||||
"please remove \"#{modifier}\" accordingly"
|
||||
:elixir_errors.warn line, state.file, message
|
||||
generate_buffer([{:end_expr, line, '', chars} | t], buffer, scope, state)
|
||||
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
|
||||
# raise EEx.SyntaxError, message: message, file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, _, chars} | _], _buffer, [], state) do
|
||||
raise EEx.SyntaxError,
|
||||
message: "unexpected end of expression <%#{chars}%>",
|
||||
file: state.file,
|
||||
line: line
|
||||
raise EEx.SyntaxError, message: "unexpected end of expression <%#{chars}%>",
|
||||
file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
@@ -121,10 +88,8 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, state) do
|
||||
raise EEx.SyntaxError,
|
||||
message: "unexpected end of string, expected a closing '<% end %>'",
|
||||
file: state.file,
|
||||
line: state.line
|
||||
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
|
||||
file: state.file, line: state.line
|
||||
end
|
||||
|
||||
# Creates a placeholder and wrap it inside the expression block
|
||||
@@ -133,30 +98,22 @@ defmodule EEx.Compiler do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.quoted)
|
||||
placeholder = '__EEX__(' ++ Integer.to_charlist(key) ++ ');'
|
||||
count = current ++ placeholder ++ new_lines ++ chars
|
||||
new_state = %{state | quoted: [{key, state.engine.handle_end(buffer)} | state.quoted]}
|
||||
|
||||
{count, new_state}
|
||||
{current ++ placeholder ++ new_lines ++ chars,
|
||||
%{state | quoted: [{key, state.engine.handle_end(buffer)} | state.quoted]}}
|
||||
end
|
||||
|
||||
# Look text ahead on expressions
|
||||
|
||||
defp look_ahead_text(
|
||||
[{:text, text}, {:middle_expr, line, _, chars} | rest] = tokens,
|
||||
start,
|
||||
contents
|
||||
) do
|
||||
defp look_ahead_text([{:text, text}, {:middle_expr, line, _, chars} | rest] = tokens, start, contents) do
|
||||
if only_spaces?(text) do
|
||||
{contents ++ text ++ chars, line, rest}
|
||||
else
|
||||
{contents, start, tokens}
|
||||
end
|
||||
end
|
||||
|
||||
defp look_ahead_text([{:middle_expr, line, _, chars} | rest], _start, contents) do
|
||||
{contents ++ chars, line, rest}
|
||||
end
|
||||
|
||||
defp look_ahead_text(tokens, start, contents) do
|
||||
{contents, start, tokens}
|
||||
end
|
||||
@@ -168,7 +125,7 @@ defmodule EEx.Compiler do
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quoted({:__EEX__, _, [key]}, quoted) do
|
||||
{^key, value} = List.keyfind(quoted, key, 0)
|
||||
{^key, value} = List.keyfind quoted, key, 0
|
||||
value
|
||||
end
|
||||
|
||||
@@ -181,7 +138,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp insert_quoted(list, quoted) when is_list(list) do
|
||||
Enum.map(list, &insert_quoted(&1, quoted))
|
||||
Enum.map list, &insert_quoted(&1, quoted)
|
||||
end
|
||||
|
||||
defp insert_quoted(other, _quoted) do
|
||||
|
||||
+12
-38
@@ -25,20 +25,7 @@ defmodule EEx.Engine do
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
|
||||
* `""`
|
||||
* `"="`
|
||||
* `"/"`
|
||||
* `"|"`
|
||||
|
||||
Markers `"/"` and `"|"` are only for use in custom EEx engines
|
||||
and are not implemented by default. Using them without the
|
||||
implementation raises `EEx.SyntaxError`.
|
||||
|
||||
If your engine does not implement all markers, please ensure that
|
||||
`handle_expr/3` falls back to `EEx.Engine.handle_expr/3`
|
||||
to raise the proper error message.
|
||||
as marker. The allowed markers so far are: `""` and `"="`.
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
@@ -50,11 +37,11 @@ defmodule EEx.Engine do
|
||||
@type state :: term
|
||||
|
||||
@callback init(opts :: keyword) :: state
|
||||
@callback handle_body(state) :: Macro.t()
|
||||
@callback handle_text(state, text :: String.t()) :: state
|
||||
@callback handle_expr(state, marker :: String.t(), expr :: Macro.t()) :: state
|
||||
@callback handle_body(state) :: Macro.t
|
||||
@callback handle_text(state, text :: String.t) :: state
|
||||
@callback handle_expr(state, marker :: String.t, expr :: Macro.t) :: state
|
||||
@callback handle_begin(state) :: state
|
||||
@callback handle_end(state) :: Macro.t()
|
||||
@callback handle_end(state) :: Macro.t
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -104,33 +91,27 @@ defmodule EEx.Engine do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec handle_assign(Macro.t()) :: Macro.t()
|
||||
@spec handle_assign(Macro.t) :: Macro.t
|
||||
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
line = meta[:line] || 0
|
||||
quote(line: line, do: EEx.Engine.fetch_assign!(var!(assigns), unquote(name)))
|
||||
quote line: line, do: EEx.Engine.fetch_assign!(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Raise on 2.0
|
||||
@spec fetch_assign!(Access.t(), Access.key()) :: term | nil
|
||||
@spec fetch_assign!(Access.t, Access.key) :: term | nil
|
||||
def fetch_assign!(assigns, key) do
|
||||
case Access.fetch(assigns, key) do
|
||||
{:ok, val} ->
|
||||
val
|
||||
|
||||
:error ->
|
||||
keys = Enum.map(assigns, &elem(&1, 0))
|
||||
|
||||
IO.warn(
|
||||
"assign @#{key} not available in EEx template. " <>
|
||||
"Please ensure all assigns are given as options. " <>
|
||||
"Available assigns: #{inspect(keys)}"
|
||||
)
|
||||
|
||||
IO.warn "assign @#{key} not available in EEx template. " <>
|
||||
"Please ensure all assigns are given as options. " <>
|
||||
"Available assigns: #{inspect keys}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -167,7 +148,7 @@ defmodule EEx.Engine do
|
||||
The default implementation simply concatenates text to the buffer.
|
||||
"""
|
||||
def handle_text(buffer, text) do
|
||||
quote(do: unquote(buffer) <> unquote(text))
|
||||
quote do: unquote(buffer) <> unquote(text)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -175,8 +156,6 @@ defmodule EEx.Engine do
|
||||
|
||||
<% Elixir expression - inline with output %>
|
||||
<%= Elixir expression - replace with result %>
|
||||
<%/ Elixir expression - raise EEx.SyntaxError, to be implemented by custom engines %>
|
||||
<%| Elixir expression - raise EEx.SyntaxError, to be implemented by custom engines %>
|
||||
|
||||
All other markers are not implemented by this engine.
|
||||
"""
|
||||
@@ -194,9 +173,4 @@ defmodule EEx.Engine do
|
||||
tmp2
|
||||
end
|
||||
end
|
||||
|
||||
def handle_expr(_buffer, marker, _expr) when marker in ["/", "|"] do
|
||||
raise EEx.SyntaxError,
|
||||
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
defmodule EEx.Tokenizer do
|
||||
@moduledoc false
|
||||
|
||||
@type content :: IO.chardata()
|
||||
@type content :: IO.chardata
|
||||
@type line :: non_neg_integer
|
||||
@type marker :: '=' | '/' | '|' | ''
|
||||
@type token ::
|
||||
{:text, content}
|
||||
| {:expr | :start_expr | :middle_expr | :end_expr, line, marker, content}
|
||||
@type token :: {:text, content} |
|
||||
{:expr | :start_expr | :middle_expr | :end_expr, line, '=' | '', content}
|
||||
|
||||
@doc """
|
||||
Tokenizes the given charlist or binary.
|
||||
@@ -21,7 +19,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
Or `{:error, line, error}` in case of errors.
|
||||
"""
|
||||
@spec tokenize(binary | charlist, line, keyword) :: {:ok, [token]} | {:error, line, String.t()}
|
||||
@spec tokenize(binary | charlist, line, keyword) :: {:ok, [token]} | {:error, line, String.t}
|
||||
def tokenize(bin, line, opts \\ [])
|
||||
|
||||
def tokenize(bin, line, opts)
|
||||
@@ -35,17 +33,15 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, opts, buffer, acc) do
|
||||
tokenize(t, line, opts, [?%, ?< | buffer], acc)
|
||||
tokenize t, line, opts, [?%, ?< | buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, line, opts, buffer, acc) do
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, _, new_line, rest} ->
|
||||
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
tokenize(rest, new_line, opts, buffer, acc)
|
||||
tokenize rest, new_line, opts, buffer, acc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,24 +49,22 @@ defmodule EEx.Tokenizer do
|
||||
{marker, t} = retrieve_marker(t)
|
||||
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, expr, new_line, rest} ->
|
||||
token = token_name(expr)
|
||||
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize(rest, new_line, opts, [], [final | acc])
|
||||
tokenize rest, new_line, opts, [], [final | acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, line, opts, buffer, acc) do
|
||||
tokenize(t, line + 1, opts, [?\n | buffer], acc)
|
||||
tokenize t, line + 1, opts, [?\n | buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h | t], line, opts, buffer, acc) do
|
||||
tokenize(t, line, opts, [h | buffer], acc)
|
||||
tokenize t, line, opts, [h | buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], _line, _opts, buffer, acc) do
|
||||
@@ -79,8 +73,8 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Retrieve marker for <%
|
||||
|
||||
defp retrieve_marker([marker | t]) when marker in [?=, ?/, ?|] do
|
||||
{[marker], t}
|
||||
defp retrieve_marker('=' ++ t) do
|
||||
{'=', t}
|
||||
end
|
||||
|
||||
defp retrieve_marker(t) do
|
||||
@@ -94,11 +88,11 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp expr('\n' ++ t, line, buffer) do
|
||||
expr(t, line + 1, [?\n | buffer])
|
||||
expr t, line + 1, [?\n | buffer]
|
||||
end
|
||||
|
||||
defp expr([h | t], line, buffer) do
|
||||
expr(t, line, [h | buffer])
|
||||
expr t, line, [h | buffer]
|
||||
end
|
||||
|
||||
defp expr([], line, _buffer) do
|
||||
@@ -129,8 +123,8 @@ defmodule EEx.Tokenizer do
|
||||
# token and, if so, it is not followed by an "end"
|
||||
# token. If this is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
|
||||
{:ok, tokens} ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
{:ok, _line, _column, tokens} ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
if fn_index && end_index(tokens) > fn_index do
|
||||
@@ -138,28 +132,27 @@ defmodule EEx.Tokenizer do
|
||||
else
|
||||
:middle_expr
|
||||
end
|
||||
|
||||
_error ->
|
||||
:middle_expr
|
||||
end
|
||||
end
|
||||
|
||||
defp token_name('esle' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('esle' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
|
||||
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
|
||||
|
||||
defp token_name(_) do
|
||||
:expr
|
||||
end
|
||||
|
||||
defp fn_index(tokens) do
|
||||
Enum.find_index(tokens, fn
|
||||
Enum.find_index tokens, fn
|
||||
{:fn_paren, _} -> true
|
||||
{:fn, _} -> true
|
||||
_ -> false
|
||||
end)
|
||||
{:fn, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
@@ -190,12 +183,10 @@ defmodule EEx.Tokenizer do
|
||||
# including the line break following it if there is one.
|
||||
defp trim_if_needed(rest, line, opts, buffer, acc) do
|
||||
original = {rest, line, buffer}
|
||||
|
||||
if opts[:trim] do
|
||||
case {trim_left(buffer, acc), trim_right(rest, line)} do
|
||||
{{true, new_buffer}, {true, new_rest, new_line}} ->
|
||||
{new_rest, new_line, new_buffer}
|
||||
|
||||
_ ->
|
||||
original
|
||||
end
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
defmodule EEx.MixProject do
|
||||
defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :eex,
|
||||
version: System.version(),
|
||||
version: System.version,
|
||||
build_per_environment: false
|
||||
]
|
||||
end
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
Code.require_file("../test_helper.exs", __DIR__)
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
# TODO: Make this async: true once capture_io is removed
|
||||
use ExUnit.Case
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval("foo bar", "foo bar")
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns as keywords" do
|
||||
assert_eval("1", "<%= @foo %>", assigns: [foo: 1])
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a map" do
|
||||
assert_eval("1", "<%= @foo %>", assigns: %{foo: 1})
|
||||
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
|
||||
end
|
||||
|
||||
test "error with missing assigns" do
|
||||
stderr =
|
||||
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
assert_eval("", "<%= @foo %>", assigns: %{})
|
||||
end)
|
||||
|
||||
stderr = ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
assert_eval "", "<%= @foo %>", assigns: %{}
|
||||
end)
|
||||
assert stderr =~ "assign @foo not available in EEx template"
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval("1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>")
|
||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
test "preserves line numbers" do
|
||||
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
|
||||
|
||||
Macro.prewalk(result, fn
|
||||
{_left, meta, _right} ->
|
||||
assert Keyword.get(meta, :line, 0) in [0, 1]
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file("../test_helper.exs", __DIR__)
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -13,102 +13,86 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar '}]}
|
||||
assert T.tokenize('foo <% bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded slash code" do
|
||||
assert T.tokenize('foo <%/ bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '/', ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded pipe code" do
|
||||
assert T.tokenize('foo <%| bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '|', ' bar '}]}
|
||||
assert T.tokenize('foo <%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == {:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar '}]}
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
string = '''
|
||||
foo <%= bar
|
||||
foo <%= bar
|
||||
|
||||
baz %>
|
||||
<% foo %>
|
||||
'''
|
||||
baz %>
|
||||
<% foo %>
|
||||
'''
|
||||
|
||||
exprs = [
|
||||
assert T.tokenize(string, 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, '=', ' bar\n\nbaz '},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, '', ' foo '},
|
||||
{:text, '\n'}
|
||||
]
|
||||
|
||||
assert T.tokenize(string, 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == {:ok, [{:text, 'foo <% true %>'}]}
|
||||
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true %>'}
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) ==
|
||||
{:ok, [{:text, 'foo <% true do %>bar<% end %>'}]}
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation with interpolation" do
|
||||
exprs = [
|
||||
assert T.tokenize('a <%% b <%= c %> <%= d %> e %> f', 1) == {:ok, [
|
||||
{:text, 'a <% b '},
|
||||
{:expr, 1, '=', ' c '},
|
||||
{:text, ' '},
|
||||
{:expr, 1, '=', ' d '},
|
||||
{:text, ' e %> f'}
|
||||
]
|
||||
]}
|
||||
|
||||
assert T.tokenize('a <%% b <%= c %> <%= d %> e %> f', 1) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "improperly formatted quotation with interpolation" do
|
||||
exprs = [
|
||||
assert T.tokenize('<%%% a <%%= b %> c %>', 1) == {:ok, [
|
||||
{:text, '<%% a <%= b %> c %>'}
|
||||
]
|
||||
|
||||
assert T.tokenize('<%%% a <%%= b %> c %>', 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
exprs = [
|
||||
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
|
||||
{:text, 'foo '}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <%# true %>', 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
exprs = [
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
exprs = [
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
exprs = [
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' cond do '},
|
||||
{:middle_expr, 1, '', ' false -> '},
|
||||
@@ -116,65 +100,51 @@ defmodule EEx.TokenizerTest do
|
||||
{:middle_expr, 1, '', ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) ==
|
||||
{:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
exprs = [
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, '', ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, '', ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, '', ' end '}
|
||||
]
|
||||
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "trim mode" do
|
||||
template = '\t<%= if true do %> \n TRUE \n <% else %>\n FALSE \n <% end %> '
|
||||
|
||||
exprs = [
|
||||
assert T.tokenize(template, 1, trim: true) == {:ok, [
|
||||
{:start_expr, 1, '=', ' if true do '},
|
||||
{:text, ' TRUE \n'},
|
||||
{:middle_expr, 3, '', ' else '},
|
||||
{:text, ' FALSE \n'},
|
||||
{:end_expr, 5, '', ' end '}
|
||||
]
|
||||
|
||||
assert T.tokenize(template, 1, trim: true) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "trim mode with comment" do
|
||||
exprs = [
|
||||
assert T.tokenize(' <%# comment %> \n123', 1, trim: true) == {:ok, [
|
||||
{:text, '123'}
|
||||
]
|
||||
|
||||
assert T.tokenize(' <%# comment %> \n123', 1, trim: true) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "trim mode with CRLF" do
|
||||
exprs = [
|
||||
assert T.tokenize('0\r\n <%= 12 %> \r\n34', 1, trim: true) == {:ok, [
|
||||
{:text, '0\r\n'},
|
||||
{:expr, 2, '=', ' 12 '},
|
||||
{:text, '34'}
|
||||
]
|
||||
|
||||
assert T.tokenize('0\r\n <%= 12 %> \r\n34', 1, trim: true) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "trim mode set to false" do
|
||||
exprs = [
|
||||
assert T.tokenize(' <%= 12 %> \n', 1, trim: false) == {:ok, [
|
||||
{:text, ' '},
|
||||
{:expr, 1, '=', ' 12 '},
|
||||
{:text, ' \n'}
|
||||
]
|
||||
|
||||
assert T.tokenize(' <%= 12 %> \n', 1, trim: false) == {:ok, exprs}
|
||||
]}
|
||||
end
|
||||
|
||||
test "trim mode no false positives" do
|
||||
|
||||
+84
-134
@@ -1,39 +1,39 @@
|
||||
Code.require_file("test_helper.exs", __DIR__)
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
EEx.function_from_string(:def, :string_sample, "<%= a + b %>", [:a, :b])
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file(:defp, :private_file_sample, filename, [:bar])
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file(:def, :public_file_sample, filename, [:bar])
|
||||
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
|
||||
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
@file "unknown"
|
||||
def unknown do
|
||||
fill_in_stacktrace()
|
||||
{__ENV__.line, hd(tl(System.stacktrace()))}
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
try do
|
||||
:erlang.error("failed")
|
||||
:erlang.error "failed"
|
||||
catch
|
||||
:error, _ -> System.stacktrace()
|
||||
:error, _ -> System.stacktrace
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -55,7 +55,7 @@ defmodule EExTest do
|
||||
|
||||
describe "evaluates" do
|
||||
test "simple string" do
|
||||
assert_eval("foo bar", "foo bar")
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "Unicode" do
|
||||
@@ -63,14 +63,13 @@ defmodule EExTest do
|
||||
• <%= "•" %> •
|
||||
<%= "Jößé Vâlìm" %> Jößé Vâlìm
|
||||
"""
|
||||
|
||||
assert_eval(" • • •\n Jößé Vâlìm Jößé Vâlìm\n", template)
|
||||
assert_eval " • • •\n Jößé Vâlìm Jößé Vâlìm\n", template
|
||||
end
|
||||
|
||||
test "trim mode" do
|
||||
string = "<%= 123 %> \n456\n <%= 789 %>"
|
||||
expected = "123456\n789"
|
||||
assert_eval(expected, string, [], trim: true)
|
||||
assert_eval expected, string, [], trim: true
|
||||
end
|
||||
|
||||
test "trim mode with middle expression" do
|
||||
@@ -82,141 +81,103 @@ defmodule EExTest do
|
||||
that
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
expected = " that\n"
|
||||
assert_eval(expected, string, [], trim: true)
|
||||
assert_eval expected, string, [], trim: true
|
||||
end
|
||||
|
||||
test "embedded code" do
|
||||
assert_eval("foo bar", "foo <%= :bar %>")
|
||||
assert_eval "foo bar", "foo <%= :bar %>"
|
||||
end
|
||||
|
||||
test "embedded code with binding" do
|
||||
assert EEx.eval_string("foo <%= bar %>", bar: 1) == "foo 1"
|
||||
assert EEx.eval_string("foo <%= bar %>", [bar: 1]) == "foo 1"
|
||||
end
|
||||
|
||||
test "embedded code with do end when true" do
|
||||
assert_eval("foo bar", "foo <%= if true do %>bar<% end %>")
|
||||
assert_eval "foo bar", "foo <%= if true do %>bar<% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with do end when false" do
|
||||
assert_eval("foo ", "foo <%= if false do %>bar<% end %>")
|
||||
assert_eval "foo ", "foo <%= if false do %>bar<% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with do end and expression" do
|
||||
assert_eval("foo bar", "foo <%= if true do %><%= :bar %><% end %>")
|
||||
assert_eval "foo bar", "foo <%= if true do %><%= :bar %><% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with do end and multiple expressions" do
|
||||
assert_eval(
|
||||
"foo bar baz",
|
||||
"foo <%= if true do %>bar <% Process.put(:eex_text, 1) %><%= :baz %><% end %>"
|
||||
)
|
||||
|
||||
assert_eval "foo bar baz", "foo <%= if true do %>bar <% Process.put(:eex_text, 1) %><%= :baz %><% end %>"
|
||||
assert Process.get(:eex_text) == 1
|
||||
end
|
||||
|
||||
test "embedded code with middle expression" do
|
||||
assert_eval("foo bar", "foo <%= if true do %>bar<% else %>baz<% end %>")
|
||||
assert_eval "foo bar", "foo <%= if true do %>bar<% else %>baz<% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with evaluated middle expression" do
|
||||
assert_eval("foo baz", "foo <%= if false do %>bar<% else %>baz<% end %>")
|
||||
assert_eval "foo baz", "foo <%= if false do %>bar<% else %>baz<% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with nested do end" do
|
||||
assert_eval("foo bar", "foo <%= if true do %><%= if true do %>bar<% end %><% end %>")
|
||||
assert_eval "foo bar", "foo <%= if true do %><%= if true do %>bar<% end %><% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with nested do end with middle expression" do
|
||||
assert_eval(
|
||||
"foo baz",
|
||||
"foo <%= if true do %><%= if false do %>bar<% else %>baz<% end %><% end %>"
|
||||
)
|
||||
assert_eval "foo baz", "foo <%= if true do %><%= if false do %>bar<% else %>baz<% end %><% end %>"
|
||||
end
|
||||
|
||||
test "embedded code with parentheses after end in end token" do
|
||||
assert_eval(
|
||||
" 101 102 103 ",
|
||||
"<%= Enum.map([1, 2, 3], (fn x -> %> <%= 100 + x %> <% end) ) %>"
|
||||
)
|
||||
assert_eval " 101 102 103 ", "<%= Enum.map([1, 2, 3], (fn x -> %> <%= 100 + x %> <% end) ) %>"
|
||||
end
|
||||
|
||||
test "embedded code with variable definition" do
|
||||
assert_eval("foo 1", "foo <% bar = 1 %><%= bar %>")
|
||||
assert_eval "foo 1", "foo <% bar = 1 %><%= bar %>"
|
||||
end
|
||||
|
||||
test "embedded code with require" do
|
||||
assert_eval("foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>")
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>"
|
||||
end
|
||||
|
||||
test "with end of token" do
|
||||
assert_eval("foo bar %>", "foo bar %>")
|
||||
assert_eval "foo bar %>", "foo bar %>"
|
||||
end
|
||||
end
|
||||
|
||||
describe "raises syntax errors" do
|
||||
test "when the token is invalid" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
|
||||
EEx.compile_string("foo <%= bar")
|
||||
end
|
||||
end
|
||||
|
||||
test "when middle expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected middle of expression <% else %>", fn ->
|
||||
EEx.compile_string("<% if true %> foo<% else %>bar<% end %>")
|
||||
EEx.compile_string "foo <%= bar"
|
||||
end
|
||||
end
|
||||
|
||||
test "when end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected end of expression <% end %>", fn ->
|
||||
EEx.compile_string("foo <% end %>")
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected end of expression <% end %>", fn ->
|
||||
EEx.compile_string "foo <% end %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "when start expression is found without an end expression" do
|
||||
msg = "nofile:2: unexpected end of string, expected a closing '<% end %>'"
|
||||
|
||||
assert_raise EEx.SyntaxError, msg, fn ->
|
||||
EEx.compile_string("foo\n<% if true do %>")
|
||||
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
|
||||
EEx.compile_string "foo\n<% if true do %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "when nested end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected end of expression <% end %>", fn ->
|
||||
EEx.compile_string("foo <% if true do %><% end %><% end %>")
|
||||
EEx.compile_string "foo <% if true do %><% end %><% end %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "when middle expression has a modifier" do
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
|
||||
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
|
||||
EEx.compile_string "foo <%= if true do %>true<%= else %>false<% end %>"
|
||||
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
|
||||
end
|
||||
|
||||
test "when end expression has a modifier" do
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
|
||||
end) =~
|
||||
~s[unexpected beginning of EEx tag \"<%=\" on end of expression \"<%= end %>\"]
|
||||
end
|
||||
|
||||
test "when trying to use marker '/' without implementation" do
|
||||
msg =
|
||||
~r/unsupported EEx syntax <%\/ %> \(the syntax is valid but not supported by the current EEx engine\)/
|
||||
|
||||
assert_raise EEx.SyntaxError, msg, fn ->
|
||||
EEx.compile_string("<%/ true %>")
|
||||
end
|
||||
end
|
||||
|
||||
test "when trying to use marker '|' without implementation" do
|
||||
msg =
|
||||
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
|
||||
|
||||
assert_raise EEx.SyntaxError, msg, fn ->
|
||||
EEx.compile_string("<%| true %>")
|
||||
end
|
||||
EEx.compile_string "foo <%= if true do %>true<% else %>false<%= end %>"
|
||||
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on end of expression \"<%= end %>\"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -232,7 +193,7 @@ defmodule EExTest do
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects line numbers inside nested expressions" do
|
||||
@@ -252,7 +213,7 @@ defmodule EExTest do
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects line numbers inside start expression" do
|
||||
@@ -272,7 +233,7 @@ defmodule EExTest do
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects line numbers inside middle expression with ->" do
|
||||
@@ -294,7 +255,7 @@ defmodule EExTest do
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects line number inside middle expressions with keywords" do
|
||||
@@ -316,11 +277,11 @@ defmodule EExTest do
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects files" do
|
||||
assert_eval("sample.ex", "<%= __ENV__.file %>", [], file: "sample.ex")
|
||||
assert_eval "sample.ex", "<%= __ENV__.file %>", [], file: "sample.ex"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -342,7 +303,7 @@ defmodule EExTest do
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "inside cond" do
|
||||
@@ -362,7 +323,7 @@ defmodule EExTest do
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval(expected, string)
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "inside cond with do end" do
|
||||
@@ -377,7 +338,7 @@ defmodule EExTest do
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval("\n\n Good\n \n", string)
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
end
|
||||
|
||||
@@ -391,7 +352,7 @@ defmodule EExTest do
|
||||
<%= 789 %>
|
||||
"""
|
||||
|
||||
assert_eval("123\n\n789\n", string)
|
||||
assert_eval "123\n\n789\n", string
|
||||
end
|
||||
|
||||
test "inside comprehensions" do
|
||||
@@ -400,8 +361,7 @@ defmodule EExTest do
|
||||
<% end %>
|
||||
<%= all || :done %>
|
||||
"""
|
||||
|
||||
assert_eval("\ndone\n", string, packages: nil, all: nil)
|
||||
assert_eval "\ndone\n", string, packages: nil, all: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -409,19 +369,17 @@ defmodule EExTest do
|
||||
test "evaluates the source" do
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
|
||||
result = EEx.eval_file(filename)
|
||||
assert_normalized_newline_equal("foo bar.\n", result)
|
||||
assert_normalized_newline_equal "foo bar.\n", result
|
||||
end
|
||||
|
||||
test "evaluates the source with bindings" do
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
result = EEx.eval_file(filename, bar: 1)
|
||||
assert_normalized_newline_equal("foo 1\n", result)
|
||||
result = EEx.eval_file(filename, [bar: 1])
|
||||
assert_normalized_newline_equal "foo 1\n", result
|
||||
end
|
||||
|
||||
test "raises an Exception when file is missing" do
|
||||
msg = "could not read file \"non-existent.eex\": no such file or directory"
|
||||
|
||||
assert_raise File.Error, msg, fn ->
|
||||
assert_raise File.Error, "could not read file \"non-existent.eex\": no such file or directory", fn ->
|
||||
filename = "non-existent.eex"
|
||||
EEx.compile_file(filename)
|
||||
end
|
||||
@@ -429,31 +387,48 @@ defmodule EExTest do
|
||||
|
||||
test "sets external resource attribute" do
|
||||
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
end
|
||||
end
|
||||
|
||||
describe "precompiled" do
|
||||
|
||||
test "from string" do
|
||||
assert EExTest.Compiled.string_sample(1, 2) == "3"
|
||||
end
|
||||
|
||||
test "from file" do
|
||||
assert_normalized_newline_equal("foo 1\n", EExTest.Compiled.file_sample(1))
|
||||
assert_normalized_newline_equal("foo 1\n", EExTest.Compiled.public_file_sample(1))
|
||||
assert_normalized_newline_equal "foo 1\n", EExTest.Compiled.file_sample(1)
|
||||
assert_normalized_newline_equal "foo 1\n", EExTest.Compiled.public_file_sample(1)
|
||||
end
|
||||
|
||||
test "from file does not affect backtrace" do
|
||||
file = to_charlist(Path.relative_to_cwd(__ENV__.file))
|
||||
assert EExTest.Compiled.before_compile ==
|
||||
{8,
|
||||
{EExTest.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_charlist(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExTest.Compiled.before_compile() ==
|
||||
{8, {EExTest.Compiled, :before_compile, 0, [file: file, line: 7]}}
|
||||
assert EExTest.Compiled.after_compile ==
|
||||
{23,
|
||||
{EExTest.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_charlist(Path.relative_to_cwd(__ENV__.file)), line: 22]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExTest.Compiled.after_compile() ==
|
||||
{23, {EExTest.Compiled, :after_compile, 0, [file: file, line: 22]}}
|
||||
|
||||
assert EExTest.Compiled.unknown() ==
|
||||
{29, {EExTest.Compiled, :unknown, 0, [file: 'unknown', line: 28]}}
|
||||
assert EExTest.Compiled.unknown ==
|
||||
{29,
|
||||
{EExTest.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 28]
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -480,49 +455,24 @@ defmodule EExTest do
|
||||
buffer <> ":TEXT(#{String.trim(text)})"
|
||||
end
|
||||
|
||||
def handle_expr(buffer, "/", expr) do
|
||||
buffer <> ":DIV(#{Macro.to_string(expr)})"
|
||||
end
|
||||
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
buffer <> ":EQUAL(#{Macro.to_string(expr)})"
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
describe "custom engines" do
|
||||
test "text" do
|
||||
assert_eval("BODY(INIT:TEXT(foo))", "foo", [], engine: TestEngine)
|
||||
end
|
||||
|
||||
test "custom marker" do
|
||||
assert_eval("BODY(INIT:TEXT(foo):DIV(:bar))", "foo <%/ :bar %>", [], engine: TestEngine)
|
||||
assert_eval "BODY(INIT:TEXT(foo))", "foo", [], engine: TestEngine
|
||||
end
|
||||
|
||||
test "begin/end" do
|
||||
assert_eval(
|
||||
~s[BODY(INIT:TEXT(foo):EQUAL(if() do\n "BEGIN:TEXT(this):END"\nelse\n "BEGIN:TEXT(that):END"\nend))],
|
||||
"foo <%= if do %>this<% else %>that<% end %>",
|
||||
[],
|
||||
engine: TestEngine
|
||||
)
|
||||
end
|
||||
|
||||
test "not implemented custom marker" do
|
||||
msg =
|
||||
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
|
||||
|
||||
assert_raise EEx.SyntaxError, msg, fn ->
|
||||
assert_eval({:wrapped, "foo baz"}, "foo <%| :bar %>", [], engine: TestEngine)
|
||||
end
|
||||
assert_eval ~s[BODY(INIT:TEXT(foo):EQUAL(if() do\n "BEGIN:TEXT(this):END"\nelse\n "BEGIN:TEXT(that):END"\nend))],
|
||||
"foo <%= if do %>this<% else %>that<% end %>", [], engine: TestEngine
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ [], opts \\ []) do
|
||||
opts = Enum.into([file: __ENV__.file, engine: opts[:engine] || EEx.Engine], opts)
|
||||
opts = Enum.into [file: __ENV__.file, engine: EEx.Engine], opts
|
||||
result = EEx.eval_string(actual, binding, opts)
|
||||
assert result == expected
|
||||
end
|
||||
|
||||
@@ -1 +1 @@
|
||||
ExUnit.start(trace: "--trace" in System.argv())
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
# Returns config for Elixir docs
|
||||
[
|
||||
extras: Path.wildcard("lib/elixir/pages/*.md"),
|
||||
groups_for_modules: [
|
||||
# [Kernel, Kernel.SpecialForms],
|
||||
|
||||
"Basic Types": [
|
||||
Atom,
|
||||
Base,
|
||||
Bitwise,
|
||||
Calendar,
|
||||
Calendar.ISO,
|
||||
Date,
|
||||
DateTime,
|
||||
Exception,
|
||||
Float,
|
||||
Integer,
|
||||
NaiveDateTime,
|
||||
Record,
|
||||
Regex,
|
||||
String,
|
||||
Time,
|
||||
Tuple,
|
||||
URI,
|
||||
Version,
|
||||
],
|
||||
|
||||
"Collections & Enumerables": [
|
||||
Access,
|
||||
Date.Range,
|
||||
Enum,
|
||||
Keyword,
|
||||
List,
|
||||
Map,
|
||||
MapSet,
|
||||
Range,
|
||||
Stream,
|
||||
],
|
||||
|
||||
"IO & System": [
|
||||
File,
|
||||
File.Stat,
|
||||
File.Stream,
|
||||
IO,
|
||||
IO.ANSI,
|
||||
IO.Stream,
|
||||
OptionParser,
|
||||
Path,
|
||||
Port,
|
||||
StringIO,
|
||||
System,
|
||||
],
|
||||
|
||||
"Modules & Code": [
|
||||
Code,
|
||||
Kernel.ParallelCompiler,
|
||||
Macro,
|
||||
Macro.Env,
|
||||
Module,
|
||||
],
|
||||
|
||||
"Processes & Applications": [
|
||||
Agent,
|
||||
Application,
|
||||
DynamicSupervisor,
|
||||
GenServer,
|
||||
Node,
|
||||
Process,
|
||||
Registry,
|
||||
Supervisor,
|
||||
Task,
|
||||
Task.Supervisor,
|
||||
],
|
||||
|
||||
"Protocols": [
|
||||
Collectable,
|
||||
Enumerable,
|
||||
Inspect,
|
||||
Inspect.Algebra,
|
||||
Inspect.Opts,
|
||||
List.Chars,
|
||||
Protocol,
|
||||
String.Chars,
|
||||
],
|
||||
|
||||
"Deprecated": [
|
||||
Behaviour,
|
||||
Dict,
|
||||
GenEvent,
|
||||
HashDict,
|
||||
HashSet,
|
||||
Set,
|
||||
Supervisor.Spec
|
||||
],
|
||||
]
|
||||
]
|
||||
+42
-136
@@ -145,15 +145,15 @@ defmodule Access do
|
||||
@type value :: any
|
||||
|
||||
@type get_fun(data, get_value) ::
|
||||
(:get, data, (term -> term) ->
|
||||
{get_value, new_data :: container})
|
||||
(:get, data, (term -> term) ->
|
||||
{get_value, new_data :: container})
|
||||
|
||||
@type get_and_update_fun(data, get_value) ::
|
||||
(:get_and_update, data, (term -> term) ->
|
||||
{get_value, new_data :: container} | :pop)
|
||||
(:get_and_update, data, (term -> term) ->
|
||||
{get_value, new_data :: container} | :pop)
|
||||
|
||||
@type access_fun(data, get_value) ::
|
||||
get_fun(data, get_value) | get_and_update_fun(data, get_value)
|
||||
get_fun(data, get_value) | get_and_update_fun(data, get_value)
|
||||
|
||||
@doc """
|
||||
Invoked in order to access the value stored under `key` in the given term `term`.
|
||||
@@ -216,8 +216,8 @@ defmodule Access do
|
||||
See the implementations of `Map.get_and_update/3` or `Keyword.get_and_update/3`
|
||||
for more examples.
|
||||
"""
|
||||
@callback get_and_update(data, key, (value -> {get_value, value} | :pop)) :: {get_value, data}
|
||||
when get_value: var, data: container | any_container
|
||||
@callback get_and_update(data, key, (value -> {get_value, value} | :pop)) ::
|
||||
{get_value, data} when get_value: var, data: container | any_container
|
||||
|
||||
@doc """
|
||||
Invoked to "pop" the value under `key` out of the given data structure.
|
||||
@@ -233,21 +233,17 @@ defmodule Access do
|
||||
"""
|
||||
@callback pop(data, key) :: {value, data} when data: container | any_container
|
||||
|
||||
defmacrop raise_undefined_behaviour(exception, module, top) do
|
||||
defmacrop raise_undefined_behaviour(e, struct, top) do
|
||||
quote do
|
||||
stacktrace = System.stacktrace()
|
||||
|
||||
exception =
|
||||
stacktrace = System.stacktrace
|
||||
e =
|
||||
case stacktrace do
|
||||
[unquote(top) | _] ->
|
||||
reason = "#{inspect(unquote(module))} does not implement the Access behaviour"
|
||||
%{unquote(exception) | reason: reason}
|
||||
|
||||
%{unquote(e) | reason: "#{inspect unquote(struct)} does not implement the Access behaviour"}
|
||||
_ ->
|
||||
unquote(exception)
|
||||
unquote(e)
|
||||
end
|
||||
|
||||
reraise exception, stacktrace
|
||||
reraise e, stacktrace
|
||||
end
|
||||
end
|
||||
|
||||
@@ -262,11 +258,11 @@ defmodule Access do
|
||||
@spec fetch(nil_container, any) :: :error
|
||||
def fetch(container, key)
|
||||
|
||||
def fetch(%module{} = container, key) do
|
||||
module.fetch(container, key)
|
||||
def fetch(%struct{} = container, key) do
|
||||
struct.fetch(container, key)
|
||||
rescue
|
||||
exception in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour(exception, module, {^module, :fetch, [^container, ^key], _})
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
|
||||
end
|
||||
|
||||
def fetch(map, key) when is_map(map) do
|
||||
@@ -285,7 +281,7 @@ defmodule Access do
|
||||
|
||||
def fetch(list, key) when is_list(list) do
|
||||
raise ArgumentError,
|
||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
||||
end
|
||||
|
||||
def fetch(nil, _key) do
|
||||
@@ -303,12 +299,12 @@ defmodule Access do
|
||||
@spec get(nil_container, any, default) :: default when default: var
|
||||
def get(container, key, default \\ nil)
|
||||
|
||||
def get(%module{} = container, key, default) do
|
||||
def get(%{__struct__: struct} = container, key, default) do
|
||||
try do
|
||||
module.fetch(container, key)
|
||||
struct.fetch(container, key)
|
||||
rescue
|
||||
exception in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour(exception, module, {^module, :fetch, [^container, ^key], _})
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
|
||||
else
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
@@ -331,7 +327,7 @@ defmodule Access do
|
||||
|
||||
def get(list, key, _default) when is_list(list) do
|
||||
raise ArgumentError,
|
||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
||||
end
|
||||
|
||||
def get(nil, _key, default) do
|
||||
@@ -352,19 +348,15 @@ defmodule Access do
|
||||
The returned value is a two-element tuple with the "get" value returned by
|
||||
`fun` and a new container with the updated value under `key`.
|
||||
"""
|
||||
@spec get_and_update(data, key, (value -> {get_value, value} | :pop)) :: {get_value, data}
|
||||
when get_value: var, data: container
|
||||
@spec get_and_update(data, key, (value -> {get_value, value} | :pop)) ::
|
||||
{get_value, data} when get_value: var, data: container
|
||||
def get_and_update(container, key, fun)
|
||||
|
||||
def get_and_update(%module{} = container, key, fun) do
|
||||
module.get_and_update(container, key, fun)
|
||||
def get_and_update(%{__struct__: struct} = container, key, fun) do
|
||||
struct.get_and_update(container, key, fun)
|
||||
rescue
|
||||
exception in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour(
|
||||
exception,
|
||||
module,
|
||||
{^module, :get_and_update, [^container, ^key, ^fun], _}
|
||||
)
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :get_and_update, [^container, ^key, ^fun], _}
|
||||
end
|
||||
|
||||
def get_and_update(map, key, fun) when is_map(map) do
|
||||
@@ -376,7 +368,8 @@ defmodule Access do
|
||||
end
|
||||
|
||||
def get_and_update(nil, key, _fun) do
|
||||
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key} on a nil value"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -406,11 +399,11 @@ defmodule Access do
|
||||
|
||||
"""
|
||||
@spec pop(data, key) :: {value, data} when data: container
|
||||
def pop(%module{} = container, key) do
|
||||
module.pop(container, key)
|
||||
def pop(%{__struct__: struct} = container, key) do
|
||||
struct.pop(container, key)
|
||||
rescue
|
||||
exception in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour(exception, module, {^module, :pop, [^container, ^key], _})
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :pop, [^container, ^key], _}
|
||||
end
|
||||
|
||||
def pop(map, key) when is_map(map) do
|
||||
@@ -422,7 +415,8 @@ defmodule Access do
|
||||
end
|
||||
|
||||
def pop(nil, key) do
|
||||
raise ArgumentError, "could not pop key #{inspect(key)} on a nil value"
|
||||
raise ArgumentError,
|
||||
"could not pop key #{inspect key} on a nil value"
|
||||
end
|
||||
|
||||
## Accessors
|
||||
@@ -471,10 +465,8 @@ defmodule Access do
|
||||
fn
|
||||
:get, data, next ->
|
||||
next.(Map.get(data, key, default))
|
||||
|
||||
:get_and_update, data, next ->
|
||||
value = Map.get(data, key, default)
|
||||
|
||||
case next.(value) do
|
||||
{get, update} -> {get, Map.put(data, key, update)}
|
||||
:pop -> {value, Map.delete(data, key)}
|
||||
@@ -515,17 +507,14 @@ defmodule Access do
|
||||
fn
|
||||
:get, %{} = data, next ->
|
||||
next.(Map.fetch!(data, key))
|
||||
|
||||
:get_and_update, %{} = data, next ->
|
||||
value = Map.fetch!(data, key)
|
||||
|
||||
case next.(value) do
|
||||
{get, update} -> {get, Map.put(data, key, update)}
|
||||
:pop -> {value, Map.delete(data, key)}
|
||||
end
|
||||
|
||||
_op, data, _next ->
|
||||
raise "Access.key!/1 expected a map/struct, got: #{inspect(data)}"
|
||||
raise "Access.key!/1 expected a map/struct, got: #{inspect data}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -562,17 +551,14 @@ defmodule Access do
|
||||
fn
|
||||
:get, data, next when is_tuple(data) ->
|
||||
next.(:erlang.element(pos, data))
|
||||
|
||||
:get_and_update, data, next when is_tuple(data) ->
|
||||
value = :erlang.element(pos, data)
|
||||
|
||||
case next.(value) do
|
||||
{get, update} -> {get, :erlang.setelement(pos, data, update)}
|
||||
:pop -> raise "cannot pop data from a tuple"
|
||||
end
|
||||
|
||||
_op, data, _next ->
|
||||
raise "Access.elem/1 expected a tuple, got: #{inspect(data)}"
|
||||
raise "Access.elem/1 expected a tuple, got: #{inspect data}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -623,7 +609,7 @@ defmodule Access do
|
||||
end
|
||||
|
||||
defp all(_op, data, _next) do
|
||||
raise "Access.all/0 expected a list, got: #{inspect(data)}"
|
||||
raise "Access.all/0 expected a list, got: #{inspect data}"
|
||||
end
|
||||
|
||||
defp all([head | rest], next, gets, updates) do
|
||||
@@ -685,7 +671,7 @@ defmodule Access do
|
||||
"""
|
||||
@spec at(non_neg_integer) :: access_fun(data :: list, get_value :: term)
|
||||
def at(index) when is_integer(index) and index >= 0 do
|
||||
fn op, data, next -> at(op, data, index, next) end
|
||||
fn(op, data, next) -> at(op, data, index, next) end
|
||||
end
|
||||
|
||||
defp at(:get, data, index, next) when is_list(data) do
|
||||
@@ -697,7 +683,7 @@ defmodule Access do
|
||||
end
|
||||
|
||||
defp at(_op, data, _index, _next) do
|
||||
raise "Access.at/1 expected a list, got: #{inspect(data)}"
|
||||
raise "Access.at/1 expected a list, got: #{inspect data}"
|
||||
end
|
||||
|
||||
defp get_and_update_at([head | rest], 0, next, updates) do
|
||||
@@ -714,84 +700,4 @@ defmodule Access do
|
||||
defp get_and_update_at([], _index, _next, updates) do
|
||||
{nil, :lists.reverse(updates)}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Returns a function that accesses all elements of a list that match the provided predicate.
|
||||
|
||||
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
|
||||
`Kernel.get_and_update_in/3`, and friends.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> get_in(list, [Access.filter(&(&1.salary > 20)), :name])
|
||||
["francine"]
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary <= 20)), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{["john"], [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
`filter/1` can also be used to pop elements out of a list or
|
||||
a key inside of a list:
|
||||
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> pop_in(list, [Access.filter(&(&1.salary >= 20))])
|
||||
{[%{name: "francine", salary: 30}], [%{name: "john", salary: 10}]}
|
||||
iex> pop_in(list, [Access.filter(&(&1.salary >= 20)), :name])
|
||||
{["francine"], [%{name: "john", salary: 10}, %{salary: 30}]}
|
||||
|
||||
When no match is found, an empty list is returned and the update function is never called
|
||||
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> get_in(list, [Access.filter(&(&1.salary >= 50)), :name])
|
||||
[]
|
||||
iex> get_and_update_in(list, [Access.filter(&(&1.salary >= 50)), :name], fn
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{[], [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
An error is raised if the predicate is not a function or is of the incorrect arity:
|
||||
|
||||
iex> get_in([], [Access.filter(5)])
|
||||
** (FunctionClauseError) no function clause matching in Access.filter/1
|
||||
|
||||
An error is raised if the accessed structure is not a list:
|
||||
|
||||
iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
|
||||
** (RuntimeError) Access.filter/1 expected a list, got: %{}
|
||||
"""
|
||||
@spec filter((term -> boolean)) :: access_fun(data :: list, get_value :: list)
|
||||
def filter(func) when is_function(func) do
|
||||
fn op, data, next -> filter(op, data, func, next) end
|
||||
end
|
||||
|
||||
defp filter(:get, data, func, next) when is_list(data) do
|
||||
data |> Enum.filter(func) |> Enum.map(next)
|
||||
end
|
||||
|
||||
defp filter(:get_and_update, data, func, next) when is_list(data) do
|
||||
get_and_update_filter(data, func, next, [], [])
|
||||
end
|
||||
|
||||
defp filter(_op, data, _func, _next) do
|
||||
raise "Access.filter/1 expected a list, got: #{inspect(data)}"
|
||||
end
|
||||
|
||||
defp get_and_update_filter([head | rest], func, next, updates, gets) do
|
||||
if func.(head) do
|
||||
case next.(head) do
|
||||
{get, update} ->
|
||||
get_and_update_filter(rest, func, next, [update | updates], [get | gets])
|
||||
|
||||
:pop ->
|
||||
get_and_update_filter(rest, func, next, updates, [head | gets])
|
||||
end
|
||||
else
|
||||
get_and_update_filter(rest, func, next, [head | updates], gets)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_and_update_filter([], _func, _next, updates, gets) do
|
||||
{:lists.reverse(gets), :lists.reverse(updates)}
|
||||
end
|
||||
end
|
||||
|
||||
+16
-15
@@ -18,7 +18,7 @@ defmodule Agent do
|
||||
defmodule Mix.TasksServer do
|
||||
use Agent
|
||||
|
||||
def start_link(_) do
|
||||
def start_link do
|
||||
Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
|
||||
end
|
||||
|
||||
@@ -77,7 +77,7 @@ defmodule Agent do
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification id, defauts to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -149,17 +149,18 @@ defmodule Agent do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
@opts unquote(opts)
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
spec = [
|
||||
id: opts[:id] || __MODULE__,
|
||||
start: Macro.escape(opts[:start]) || quote(do: {__MODULE__, :start_link, [arg]}),
|
||||
restart: opts[:restart] || :permanent,
|
||||
shutdown: opts[:shutdown] || 5000,
|
||||
type: :worker
|
||||
]
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]}
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
%{unquote_splicing(spec)}
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
@@ -210,7 +211,7 @@ defmodule Agent do
|
||||
%RuntimeError{message: "oops"}
|
||||
|
||||
"""
|
||||
@spec start_link((() -> term), GenServer.options()) :: on_start
|
||||
@spec start_link((() -> term), GenServer.options) :: on_start
|
||||
def start_link(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
@@ -222,7 +223,7 @@ defmodule Agent do
|
||||
instead of an anonymous function; `fun` in `module` will be called with the
|
||||
given arguments `args` to initialize the state.
|
||||
"""
|
||||
@spec start_link(module, atom, [any], GenServer.options()) :: on_start
|
||||
@spec start_link(module, atom, [any], GenServer.options) :: on_start
|
||||
def start_link(module, fun, args, options \\ []) do
|
||||
GenServer.start_link(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
@@ -235,11 +236,11 @@ defmodule Agent do
|
||||
## Examples
|
||||
|
||||
iex> {:ok, pid} = Agent.start(fn -> 42 end)
|
||||
iex> Agent.get(pid, fn state -> state end)
|
||||
iex> Agent.get(pid, fn(state) -> state end)
|
||||
42
|
||||
|
||||
"""
|
||||
@spec start((() -> term), GenServer.options()) :: on_start
|
||||
@spec start((() -> term), GenServer.options) :: on_start
|
||||
def start(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start(Agent.Server, fun, options)
|
||||
end
|
||||
@@ -249,7 +250,7 @@ defmodule Agent do
|
||||
|
||||
See `start_link/4` for more information.
|
||||
"""
|
||||
@spec start(module, atom, [any], GenServer.options()) :: on_start
|
||||
@spec start(module, atom, [any], GenServer.options) :: on_start
|
||||
def start(module, fun, args, options \\ []) do
|
||||
GenServer.start(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule Agent.Server do
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
case run(fun, [state]) do
|
||||
{reply, state} -> {:reply, reply, state}
|
||||
other -> {:stop, {:bad_return_value, other}, state}
|
||||
other -> {:stop, {:bad_return_value, other}, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+84
-160
@@ -9,105 +9,17 @@ defmodule Application do
|
||||
Applications are defined with an application file named `APP.app` where
|
||||
`APP` is the application name, usually in `underscore_case`. The application
|
||||
file must reside in the same `ebin` directory as the compiled modules of the
|
||||
application. In Elixir, the Mix build tool is responsible for compiling your
|
||||
source code and generating your application `.app` file. You can learn more
|
||||
about the generation of `.app` files by typing `mix help compile.app`.
|
||||
application.
|
||||
|
||||
Once your application is compiled, running your system is a matter of starting
|
||||
your current application and its dependencies. Differently from other languages,
|
||||
Elixir does not have a `main` procedure that is responsible for starting your
|
||||
system. Instead, you start one or more applications, each with their own
|
||||
initialization and termination logic.
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stopping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment
|
||||
and the application callback module.
|
||||
|
||||
Applications also provide an "application environment", which provides one
|
||||
mechanism for configuring long running applications. We will learn more about
|
||||
the tooling, start and shutdown and the application environment in the next
|
||||
sections.
|
||||
|
||||
## Start and shutdown
|
||||
|
||||
Starting an application is done via the "application module callback", which
|
||||
is a module that defines the `start/2` function. The `start/2` function should
|
||||
then start a supervisor, which is often called as the top-level supervisor, since
|
||||
it sits at the root of a potentially long supervision tree. When the system is
|
||||
shutting down, all applications shut down their top-level supervisor, which
|
||||
terminates children in the opposite order they are started.
|
||||
|
||||
Shutting down a live system cleanly can be done by calling `System.stop/1`.
|
||||
It will shut down all applications in the opposite order they are started.
|
||||
Each application will then shutdown its top-level supervisor, if one is
|
||||
available, [which then shuts down its children](Supervisor.html#module-start-and-shutdown).
|
||||
|
||||
From Erlang/OTP 19.1, a SIGTERM from the operating system will automatically
|
||||
translate to `System.stop/0`. Erlang/OTP 20 gives user more explicit control
|
||||
over OS signals via the `:os.set_signal/2` function.
|
||||
|
||||
### Application module callback
|
||||
|
||||
An application may start and stop a supervision tree when it boots via
|
||||
the application module callback.
|
||||
|
||||
The first step is to pass the module callback in the application definition
|
||||
in the `mix.exs` file:
|
||||
|
||||
def application do
|
||||
[mod: {MyApp, []}]
|
||||
end
|
||||
|
||||
Our application now requires the `MyApp` module to provide an application
|
||||
callback. This can be done by invoking `use Application` in that module and
|
||||
defining a `start/2` callback, for example:
|
||||
|
||||
defmodule MyApp do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
children = []
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
`start/2` typically returns `{:ok, pid}` or `{:ok, pid, state}` where
|
||||
`pid` identifies the supervision tree and `state` is the application state.
|
||||
`args` is the second element of the tuple given to the `:mod` option.
|
||||
|
||||
The `type` argument passed to `start/2` is usually `:normal` unless in a
|
||||
distributed setup where application takeovers and failovers are configured.
|
||||
Distributed applications is beyond the scope of this documentation. For those
|
||||
interested on the topic, please access the OTP documentation:
|
||||
|
||||
* [`:application` module](http://www.erlang.org/doc/man/application.html)
|
||||
* [Applications – OTP Design Principles](http://www.erlang.org/doc/design_principles/applications.html)
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
application state and can return any value. Note that shutting down the
|
||||
supervisor is automatically handled by the VM.
|
||||
|
||||
An application without a supervision tree doesn't define an application
|
||||
module callback in the application definition in `mix.exs` file. Even though
|
||||
there is no module with application callbacks such as `start/2` and
|
||||
`stop/1`, the application can be started and stopped the same way as an
|
||||
application with a supervision tree.
|
||||
|
||||
## Tooling
|
||||
|
||||
The Mix build tool can also be used to start your applications. For example,
|
||||
`mix test` automatically starts your application dependencies and your application
|
||||
itself before your test runs. `mix run --no-halt` boots your current project and
|
||||
can be used to start a long running system. See `mix help run`.
|
||||
|
||||
Developers can also use tools like [Distillery](https://github.com/bitwalker/distillery)
|
||||
that build **releases**. Releases are able to package all of your source code
|
||||
as well as the Erlang VM into a single directory. Releases also give you explicit
|
||||
control over how each application is started and in which order. They also provide
|
||||
a more streamlined mechanism for starting and stopping systems, debugging, logging,
|
||||
as well as system monitoring.
|
||||
|
||||
Finally, Elixir provides tools such as escripts and archives, which are
|
||||
different mechanisms for packaging your application. Those are typically used
|
||||
when tools must be shared between developers and not as deployment options.
|
||||
See `mix help archive.build` and `mix help escript.build` for more detail.
|
||||
You can learn more about Mix generation of `.app` files by typing
|
||||
`mix help compile.app`.
|
||||
|
||||
## Application environment
|
||||
|
||||
@@ -128,22 +40,60 @@ defmodule Application do
|
||||
Application.get_env(:APP_NAME, :hello)
|
||||
#=> :world
|
||||
|
||||
Applications and dependencies in Mix projects are typically configured
|
||||
via the `config/config.exs` file. For example, someone using your
|
||||
application can configure the `:hello` key as follows:
|
||||
|
||||
config :APP_NAME, hello: :brand_new_world
|
||||
|
||||
Keep in mind configuration files are only useful to configure static
|
||||
values. For example, if you need to configure your applications based
|
||||
on the system environment, the file system or on database entries,
|
||||
then those configurations are better placed at runtime. For example,
|
||||
one may configure applications dynamically via `put_env/3`.
|
||||
It is also possible to put and delete values from the application value,
|
||||
including new values that are not defined in the environment file (although
|
||||
this should be avoided).
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the functions in this module for directly accessing or modifying
|
||||
the environment of other applications (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
## Application module callback
|
||||
|
||||
Often times, an application defines a supervision tree that must be started
|
||||
and stopped when the application starts and stops. For such, we need to
|
||||
define an application module callback. The first step is to define the
|
||||
module callback in the application definition in the `mix.exs` file:
|
||||
|
||||
def application do
|
||||
[mod: {MyApp, []}]
|
||||
end
|
||||
|
||||
Our application now requires the `MyApp` module to provide an application
|
||||
callback. This can be done by invoking `use Application` in that module and
|
||||
defining a `start/2` callback, for example:
|
||||
|
||||
defmodule MyApp do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
MyApp.Supervisor.start_link()
|
||||
end
|
||||
end
|
||||
|
||||
`start/2` typically returns `{:ok, pid}` or `{:ok, pid, state}` where
|
||||
`pid` identifies the supervision tree and `state` is the application state.
|
||||
`args` is the second element of the tuple given to the `:mod` option.
|
||||
|
||||
The `type` argument passed to `start/2` is usually `:normal` unless in a
|
||||
distributed setup where application takeovers and failovers are configured.
|
||||
This particular aspect of applications is explained in more detail in the
|
||||
OTP documentation:
|
||||
|
||||
* [`:application` module](http://www.erlang.org/doc/man/application.html)
|
||||
* [Applications – OTP Design Principles](http://www.erlang.org/doc/design_principles/applications.html)
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
application state and can return any value. Note that shutting down the
|
||||
supervisor is automatically handled by the VM.
|
||||
|
||||
An application without a supervision tree doesn't define an application
|
||||
module callback in the application definition in `mix.exs` file. Even though
|
||||
there is no module with application callbacks such as `start/2` and
|
||||
`stop/1`, the application can be started and stopped the same way as an
|
||||
application with a supervision tree.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -181,14 +131,14 @@ defmodule Application do
|
||||
callback.
|
||||
"""
|
||||
@callback start(start_type, start_args :: term) ::
|
||||
{:ok, pid}
|
||||
| {:ok, pid, state}
|
||||
| {:error, reason :: term}
|
||||
{:ok, pid} |
|
||||
{:ok, pid, state} |
|
||||
{:error, reason :: term}
|
||||
|
||||
@doc """
|
||||
Called after an application has been stopped.
|
||||
Called when an application is stopped.
|
||||
|
||||
This function is called after an application has been stopped, i.e., after its
|
||||
This function is called when an application has stopped, i.e., when its
|
||||
supervision tree has been stopped. It should do the opposite of what the
|
||||
`start/2` callback did, and should perform any necessary cleanup. The return
|
||||
value of this callback is ignored.
|
||||
@@ -210,7 +160,8 @@ defmodule Application do
|
||||
in the order they are listed in.
|
||||
"""
|
||||
@callback start_phase(phase :: term, start_type, phase_args :: term) ::
|
||||
:ok | {:error, reason :: term}
|
||||
:ok |
|
||||
{:error, reason :: term}
|
||||
|
||||
@optional_callbacks start_phase: 3
|
||||
|
||||
@@ -234,26 +185,15 @@ defmodule Application do
|
||||
@type state :: term
|
||||
@type start_type :: :permanent | :transient | :temporary
|
||||
|
||||
@application_keys [
|
||||
:description,
|
||||
:id,
|
||||
:vsn,
|
||||
:modules,
|
||||
:maxP,
|
||||
:maxT,
|
||||
:registered,
|
||||
:included_applications,
|
||||
:applications,
|
||||
:mod,
|
||||
:start_phases
|
||||
]
|
||||
@application_keys [:description, :id, :vsn, :modules, :maxP, :maxT, :registered,
|
||||
:included_applications, :applications, :mod, :start_phases]
|
||||
|
||||
@doc """
|
||||
Returns the spec for `app`.
|
||||
|
||||
The following keys are returned:
|
||||
|
||||
* #{Enum.map_join(@application_keys, "\n * ", &"`#{inspect(&1)}`")}
|
||||
* #{Enum.map_join @application_keys, "\n * ", &inspect/1}
|
||||
|
||||
Note the environment is not returned as it can be accessed via
|
||||
`fetch_env/2`. Returns `nil` if the application is not loaded.
|
||||
@@ -262,7 +202,7 @@ defmodule Application do
|
||||
def spec(app) do
|
||||
case :application.get_all_key(app) do
|
||||
{:ok, info} -> :lists.keydelete(:env, 1, info)
|
||||
:undefined -> nil
|
||||
:undefined -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -336,27 +276,11 @@ defmodule Application do
|
||||
@spec fetch_env!(app, key) :: value | no_return
|
||||
def fetch_env!(app, key) do
|
||||
case fetch_env(app, key) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:ok, value} -> value
|
||||
:error ->
|
||||
vsn = :application.get_key(app, :vsn)
|
||||
app = inspect(app)
|
||||
key = inspect(key)
|
||||
|
||||
case vsn do
|
||||
{:ok, _} ->
|
||||
raise ArgumentError,
|
||||
"could not fetch application environment #{key} for application #{app} " <>
|
||||
"because configuration #{key} was not set"
|
||||
|
||||
:undefined ->
|
||||
raise ArgumentError,
|
||||
"could not fetch application environment #{key} for application #{app} " <>
|
||||
"because the application was not loaded/started. If your application " <>
|
||||
"depends on #{app} at runtime, make sure to load/start it or list it " <>
|
||||
"under :extra_applications in your mix.exs file"
|
||||
end
|
||||
raise ArgumentError,
|
||||
"application #{inspect app} is not loaded, " <>
|
||||
"or the configuration parameter #{inspect key} is not set"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -377,7 +301,7 @@ defmodule Application do
|
||||
in the application resource file on load. This means persistent values will
|
||||
stick after the application is loaded and also on application reload.
|
||||
"""
|
||||
@spec put_env(app, key, value, timeout: timeout, persistent: boolean) :: :ok
|
||||
@spec put_env(app, key, value, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def put_env(app, key, value, opts \\ []) do
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
@@ -387,7 +311,7 @@ defmodule Application do
|
||||
|
||||
See `put_env/4` for a description of the options.
|
||||
"""
|
||||
@spec delete_env(app, key, timeout: timeout, persistent: boolean) :: :ok
|
||||
@spec delete_env(app, key, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def delete_env(app, key, opts \\ []) do
|
||||
:application.unset_env(app, key, opts)
|
||||
end
|
||||
@@ -517,22 +441,21 @@ defmodule Application do
|
||||
For more information on code paths, check the `Code` module in
|
||||
Elixir and also Erlang's [`:code` module](http://www.erlang.org/doc/man/code.html).
|
||||
"""
|
||||
@spec app_dir(app) :: String.t()
|
||||
@spec app_dir(app) :: String.t
|
||||
def app_dir(app) when is_atom(app) do
|
||||
case :code.lib_dir(app) do
|
||||
lib when is_list(lib) -> IO.chardata_to_string(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect(app)}"
|
||||
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect app}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given path inside `app_dir/1`.
|
||||
"""
|
||||
@spec app_dir(app, String.t() | [String.t()]) :: String.t()
|
||||
@spec app_dir(app, String.t | [String.t]) :: String.t
|
||||
def app_dir(app, path) when is_binary(path) do
|
||||
Path.join(app_dir(app), path)
|
||||
end
|
||||
|
||||
def app_dir(app, path) when is_list(path) do
|
||||
Path.join([app_dir(app) | path])
|
||||
end
|
||||
@@ -550,7 +473,7 @@ defmodule Application do
|
||||
"""
|
||||
@spec loaded_applications :: [tuple]
|
||||
def loaded_applications do
|
||||
:application.loaded_applications()
|
||||
:application.loaded_applications
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -558,7 +481,7 @@ defmodule Application do
|
||||
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t()
|
||||
@spec format_error(any) :: String.t
|
||||
def format_error(reason) do
|
||||
try do
|
||||
do_format_error(reason)
|
||||
@@ -577,8 +500,8 @@ defmodule Application do
|
||||
|
||||
# {:error, reason} return value
|
||||
defp do_format_error({reason, {mod, :start, args}}) do
|
||||
Exception.format_mfa(mod, :start, args) <>
|
||||
" returned an error: " <> Exception.format_exit(reason)
|
||||
Exception.format_mfa(mod, :start, args) <> " returned an error: " <>
|
||||
Exception.format_exit(reason)
|
||||
end
|
||||
|
||||
# error or exit(reason) call, use exit reason as reason.
|
||||
@@ -588,7 +511,8 @@ defmodule Application do
|
||||
|
||||
# bad return value
|
||||
defp do_format_error({:bad_return, {{mod, :start, args}, return}}) do
|
||||
Exception.format_mfa(mod, :start, args) <> " returned a bad value: " <> inspect(return)
|
||||
Exception.format_mfa(mod, :start, args) <>
|
||||
" returned a bad value: " <> inspect(return)
|
||||
end
|
||||
|
||||
defp do_format_error({:already_started, app}) when is_atom(app) do
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule Atom do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec to_string(atom) :: String.t()
|
||||
@spec to_string(atom) :: String.t
|
||||
def to_string(atom) do
|
||||
:erlang.atom_to_binary(atom, :utf8)
|
||||
end
|
||||
|
||||
+138
-352
@@ -92,10 +92,10 @@ defmodule Base do
|
||||
|
||||
"""
|
||||
|
||||
b16_alphabet = '0123456789ABCDEF'
|
||||
b64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b16_alphabet = '0123456789ABCDEF'
|
||||
b64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b64url_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
b32_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
defmacrop encode_pair(alphabet, case, value) do
|
||||
@@ -108,7 +108,6 @@ defmodule Base do
|
||||
|
||||
defp encode_pair_clauses(alphabet, case) when case in [:sensitive, :upper] do
|
||||
shift = shift(alphabet)
|
||||
|
||||
alphabet
|
||||
|> Enum.with_index()
|
||||
|> encode_clauses(shift)
|
||||
@@ -116,9 +115,8 @@ defmodule Base do
|
||||
|
||||
defp encode_pair_clauses(alphabet, :lower) do
|
||||
shift = shift(alphabet)
|
||||
|
||||
alphabet
|
||||
|> Stream.map(fn c -> if c in ?A..?Z, do: c - ?A + ?a, else: c end)
|
||||
|> Stream.map(fn c -> (if c in ?A..?Z, do: c - ?A + ?a, else: c) end)
|
||||
|> Enum.with_index()
|
||||
|> encode_clauses(shift)
|
||||
end
|
||||
@@ -131,11 +129,10 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp encode_clauses(alphabet, shift) do
|
||||
for {encoding1, value1} <- alphabet,
|
||||
{encoding2, value2} <- alphabet do
|
||||
for {encoding1, value1} <- alphabet, {encoding2, value2} <- alphabet do
|
||||
encoding = bsl(encoding1, 8) + encoding2
|
||||
value = bsl(value1, shift) + value2
|
||||
[clause] = quote(do: (unquote(value) -> unquote(encoding)))
|
||||
[clause] = quote do: (unquote(value) -> unquote(encoding))
|
||||
clause
|
||||
end
|
||||
end
|
||||
@@ -153,7 +150,6 @@ defmodule Base do
|
||||
alphabet
|
||||
|> Enum.with_index()
|
||||
|> decode_clauses()
|
||||
|
||||
clauses ++ bad_digit_clause()
|
||||
end
|
||||
|
||||
@@ -162,8 +158,8 @@ defmodule Base do
|
||||
alphabet
|
||||
|> Stream.with_index()
|
||||
|> Enum.split_with(fn {encoding, _} -> encoding in ?A..?Z end)
|
||||
|
||||
lowers = Enum.map(uppers, fn {encoding, value} -> {encoding - ?A + ?a, value} end)
|
||||
lowers =
|
||||
Enum.map(uppers, fn {encoding, value} -> {encoding - ?A + ?a, value} end)
|
||||
|
||||
if length(uppers) > length(rest) do
|
||||
decode_mixed_clauses(lowers, rest)
|
||||
@@ -174,12 +170,10 @@ defmodule Base do
|
||||
|
||||
defp decode_char_clauses(alphabet, :mixed) when length(alphabet) == 16 do
|
||||
alphabet = Enum.with_index(alphabet)
|
||||
|
||||
lowers =
|
||||
alphabet
|
||||
|> Stream.filter(fn {encoding, _} -> encoding in ?A..?Z end)
|
||||
|> Enum.map(fn {encoding, value} -> {encoding - ?A + ?a, value} end)
|
||||
|
||||
decode_mixed_clauses(alphabet, lowers)
|
||||
end
|
||||
|
||||
@@ -187,19 +181,18 @@ defmodule Base do
|
||||
alphabet
|
||||
|> Stream.with_index()
|
||||
|> Enum.flat_map(fn {encoding, value} = pair ->
|
||||
if encoding in ?A..?Z do
|
||||
[pair, {encoding - ?A + ?a, value}]
|
||||
else
|
||||
[pair]
|
||||
end
|
||||
end)
|
||||
if encoding in ?A..?Z do
|
||||
[pair, {encoding - ?A + ?a, value}]
|
||||
else
|
||||
[pair]
|
||||
end
|
||||
end)
|
||||
|> decode_clauses()
|
||||
end
|
||||
|
||||
defp decode_mixed_clauses(first, second) do
|
||||
first_clauses = decode_clauses(first)
|
||||
second_clauses = decode_clauses(second) ++ bad_digit_clause()
|
||||
|
||||
join_clause =
|
||||
quote do
|
||||
encoding ->
|
||||
@@ -207,13 +200,12 @@ defmodule Base do
|
||||
unquote(second_clauses)
|
||||
end
|
||||
end
|
||||
|
||||
first_clauses ++ join_clause
|
||||
end
|
||||
|
||||
defp decode_clauses(alphabet) do
|
||||
for {encoding, value} <- alphabet do
|
||||
[clause] = quote(do: (unquote(encoding) -> unquote(value)))
|
||||
[clause] = quote do: (unquote(encoding) -> unquote(value))
|
||||
clause
|
||||
end
|
||||
end
|
||||
@@ -222,13 +214,14 @@ defmodule Base do
|
||||
quote do
|
||||
c ->
|
||||
raise ArgumentError,
|
||||
"non-alphabet digit found: #{inspect(<<c>>, binaries: :as_strings)} (byte #{c})"
|
||||
"non-alphabet digit found: #{inspect <<c>>, binaries: :as_strings} (byte #{c})"
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_pad(body, "", _, _), do: body
|
||||
defp maybe_pad(body, tail, false, _), do: body <> tail
|
||||
|
||||
defp maybe_pad(body, "", _, _),
|
||||
do: body
|
||||
defp maybe_pad(body, tail, false, _),
|
||||
do: body <> tail
|
||||
defp maybe_pad(body, tail, _, group_size) do
|
||||
case group_size - rem(byte_size(tail), group_size) do
|
||||
^group_size -> body <> tail
|
||||
@@ -511,7 +504,7 @@ defmodule Base do
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary, keyword) :: binary
|
||||
def url_decode64!(string, opts \\ []) when is_binary(string) do
|
||||
def url_decode64!(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
string |> remove_ignored(opts[:ignore]) |> do_decode64url(pad?)
|
||||
end
|
||||
@@ -776,7 +769,6 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp remove_ignored(string, nil), do: string
|
||||
|
||||
defp remove_ignored(string, :whitespace) do
|
||||
for <<char::8 <- string>>, char not in '\s\t\r\n', into: <<>>, do: <<char::8>>
|
||||
end
|
||||
@@ -793,75 +785,39 @@ defmodule Base do
|
||||
|
||||
for {case, fun} <- enc16 do
|
||||
defp do_encode16(unquote(case), data) do
|
||||
split = 8 * div(byte_size(data), 8)
|
||||
split = 8 * div(byte_size(data), 8)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(fun)(c1)::16,
|
||||
unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16,
|
||||
unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16,
|
||||
unquote(fun)(c6)::16,
|
||||
unquote(fun)(c7)::16,
|
||||
unquote(fun)(c8)::16
|
||||
>>
|
||||
<<unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16, unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16, unquote(fun)(c6)::16,
|
||||
unquote(fun)(c7)::16, unquote(fun)(c8)::16>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> ->
|
||||
<<
|
||||
main::binary,
|
||||
unquote(fun)(c1)::16,
|
||||
unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16,
|
||||
unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16,
|
||||
unquote(fun)(c6)::16,
|
||||
unquote(fun)(c7)::16
|
||||
>>
|
||||
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16, unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16, unquote(fun)(c6)::16,
|
||||
unquote(fun)(c7)::16>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> ->
|
||||
<<
|
||||
main::binary,
|
||||
unquote(fun)(c1)::16,
|
||||
unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16,
|
||||
unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16,
|
||||
unquote(fun)(c6)::16
|
||||
>>
|
||||
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16, unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16, unquote(fun)(c6)::16>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8>> ->
|
||||
<<
|
||||
main::binary,
|
||||
unquote(fun)(c1)::16,
|
||||
unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16,
|
||||
unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16
|
||||
>>
|
||||
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16, unquote(fun)(c4)::16,
|
||||
unquote(fun)(c5)::16>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<
|
||||
main::binary,
|
||||
unquote(fun)(c1)::16,
|
||||
unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16,
|
||||
unquote(fun)(c4)::16
|
||||
>>
|
||||
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16, unquote(fun)(c4)::16>>
|
||||
<<c1::8, c2::8, c3::8>> ->
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16, unquote(fun)(c3)::16>>
|
||||
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16,
|
||||
unquote(fun)(c3)::16>>
|
||||
<<c1::8, c2::8>> ->
|
||||
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16>>
|
||||
|
||||
<<c1::8>> ->
|
||||
<<main::binary, unquote(fun)(c1)::16>>
|
||||
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
@@ -880,50 +836,27 @@ defmodule Base do
|
||||
|
||||
for {case, fun} <- dec16 do
|
||||
defp do_decode16(unquote(case), string) do
|
||||
split = 8 * div(byte_size(string), 8)
|
||||
split = 8 * div(byte_size(string), 8)
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(fun)(c1)::4,
|
||||
unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4,
|
||||
unquote(fun)(c4)::4,
|
||||
unquote(fun)(c5)::4,
|
||||
unquote(fun)(c6)::4,
|
||||
unquote(fun)(c7)::4,
|
||||
unquote(fun)(c8)::4
|
||||
>>
|
||||
<<unquote(fun)(c1)::4, unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4, unquote(fun)(c4)::4,
|
||||
unquote(fun)(c5)::4, unquote(fun)(c6)::4,
|
||||
unquote(fun)(c7)::4, unquote(fun)(c8)::4>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::4,
|
||||
unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4,
|
||||
unquote(fun)(c4)::4,
|
||||
unquote(fun)(c5)::4,
|
||||
unquote(fun)(c6)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::4, unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4, unquote(fun)(c4)::4,
|
||||
unquote(fun)(c5)::4, unquote(fun)(c6)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::4,
|
||||
unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4,
|
||||
unquote(fun)(c4)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::4, unquote(fun)(c2)::4,
|
||||
unquote(fun)(c3)::4, unquote(fun)(c4)::4>>
|
||||
<<c1::8, c2::8>> ->
|
||||
<<main::bits, unquote(fun)(c1)::4, unquote(fun)(c2)::4>>
|
||||
|
||||
<<_::8>> ->
|
||||
raise ArgumentError, "odd-length string"
|
||||
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
@@ -946,47 +879,29 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp unquote(do_encode)(<<>>, _), do: <<>>
|
||||
|
||||
defp unquote(do_encode)(data, pad?) do
|
||||
split = 6 * div(byte_size(data), 6)
|
||||
split = 6 * div(byte_size(data), 6)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
|
||||
main =
|
||||
for <<c1::12, c2::12, c3::12, c4::12 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(pair)(c1)::16,
|
||||
unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16,
|
||||
unquote(pair)(c4)::16
|
||||
>>
|
||||
end
|
||||
|
||||
tail =
|
||||
case rest do
|
||||
<<c1::12, c2::12, c3::12, c::4>> ->
|
||||
<<
|
||||
unquote(pair)(c1)::16,
|
||||
unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16,
|
||||
unquote(char)(bsl(c, 2))::8
|
||||
>>
|
||||
|
||||
<<c1::12, c2::12, c3::8>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16, unquote(pair)(bsl(c3, 4))::16>>
|
||||
|
||||
<<c1::12, c2::12>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16>>
|
||||
|
||||
<<c1::12, c2::4>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(char)(bsl(c2, 2))::8>>
|
||||
|
||||
<<c1::8>> ->
|
||||
<<unquote(pair)(bsl(c1, 4))::16>>
|
||||
|
||||
<<>> ->
|
||||
<<>>
|
||||
end
|
||||
|
||||
main = for <<c1::12, c2::12, c3::12, c4::12 <- main>>, into: <<>> do
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16, unquote(pair)(c4)::16>>
|
||||
end
|
||||
tail = case rest do
|
||||
<<c1::12, c2::12, c3::12, c::4>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16, unquote(pair)(c3)::16,
|
||||
unquote(char)(bsl(c, 2))::8>>
|
||||
<<c1::12, c2::12, c3::8>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16,
|
||||
unquote(pair)(bsl(c3, 4))::16>>
|
||||
<<c1::12, c2::12>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16>>
|
||||
<<c1::12, c2::4>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(char)(bsl(c2, 2))::8>>
|
||||
<<c1::8>> ->
|
||||
<<unquote(pair)(bsl(c1, 4))::16>>
|
||||
<<>> ->
|
||||
<<>>
|
||||
end
|
||||
maybe_pad(main, tail, pad?, 4)
|
||||
end
|
||||
end
|
||||
@@ -1000,106 +915,49 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp unquote(do_decode)(<<>>, _), do: <<>>
|
||||
|
||||
defp unquote(do_decode)(string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(segs)-binary-unit(64), rest::binary>> = string
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6,
|
||||
unquote(fun)(c7)::6,
|
||||
unquote(fun)(c8)::6
|
||||
>>
|
||||
<<unquote(fun)(c1)::6, unquote(fun)(c2)::6, unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6, unquote(fun)(c5)::6, unquote(fun)(c6)::6,
|
||||
unquote(fun)(c7)::6, unquote(fun)(c8)::6>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::bits, unquote(fun)(c1)::6, bsr(unquote(fun)(c2), 4)::2>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6, bsr(unquote(fun)(c3), 2)::4>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
bsr(unquote(fun)(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
bsr(unquote(fun)(c6), 4)::2
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6, unquote(fun)(c5)::6,
|
||||
bsr(unquote(fun)(c6), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6,
|
||||
bsr(unquote(fun)(c7), 2)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6, unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6, bsr(unquote(fun)(c7), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6,
|
||||
unquote(fun)(c7)::6,
|
||||
unquote(fun)(c8)::6
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6, unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6, unquote(fun)(c7)::6, unquote(fun)(c8)::6>>
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
<<main::bits, unquote(fun)(c1)::6, bsr(unquote(fun)(c2), 4)::2>>
|
||||
|
||||
<<c1::8, c2::8, c3::8>> when not pad? ->
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6, bsr(unquote(fun)(c3), 2)::4>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
bsr(unquote(fun)(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
bsr(unquote(fun)(c6), 4)::2
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6, unquote(fun)(c5)::6,
|
||||
bsr(unquote(fun)(c6), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::6,
|
||||
unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6,
|
||||
unquote(fun)(c4)::6,
|
||||
unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6,
|
||||
bsr(unquote(fun)(c7), 2)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6,
|
||||
unquote(fun)(c3)::6, unquote(fun)(c4)::6, unquote(fun)(c5)::6,
|
||||
unquote(fun)(c6)::6, bsr(unquote(fun)(c7), 2)::4>>
|
||||
_ ->
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
@@ -1123,44 +981,28 @@ defmodule Base do
|
||||
end
|
||||
|
||||
defp unquote(do_encode)(_, <<>>, _), do: <<>>
|
||||
|
||||
defp unquote(do_encode)(unquote(case), data, pad?) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
|
||||
main =
|
||||
for <<c1::10, c2::10, c3::10, c4::10 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(pair)(c1)::16,
|
||||
unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16,
|
||||
unquote(pair)(c4)::16
|
||||
>>
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16, unquote(pair)(c4)::16>>
|
||||
end
|
||||
|
||||
tail =
|
||||
case rest do
|
||||
<<c1::10, c2::10, c3::10, c4::2>> ->
|
||||
<<
|
||||
unquote(pair)(c1)::16,
|
||||
unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16,
|
||||
unquote(char)(bsl(c4, 3))::8
|
||||
>>
|
||||
|
||||
<<c1::10, c2::10, c3::4>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16, unquote(char)(bsl(c3, 1))::8>>
|
||||
|
||||
<<c1::10, c2::6>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(bsl(c2, 4))::16>>
|
||||
|
||||
<<c1::8>> ->
|
||||
<<unquote(pair)(bsl(c1, 2))::16>>
|
||||
|
||||
<<>> ->
|
||||
<<>>
|
||||
end
|
||||
|
||||
tail = case rest do
|
||||
<<c1::10, c2::10, c3::10, c4::2>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16,
|
||||
unquote(pair)(c3)::16, unquote(char)(bsl(c4, 3))::8>>
|
||||
<<c1::10, c2::10, c3::4>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16,
|
||||
unquote(char)(bsl(c3, 1))::8>>
|
||||
<<c1::10, c2::6>> ->
|
||||
<<unquote(pair)(c1)::16, unquote(pair)(bsl(c2, 4))::16>>
|
||||
<<c1::8>> ->
|
||||
<<unquote(pair)(bsl(c1, 2))::16>>
|
||||
<<>> ->
|
||||
<<>>
|
||||
end
|
||||
maybe_pad(main, tail, pad?, 8)
|
||||
end
|
||||
end
|
||||
@@ -1179,103 +1021,47 @@ defmodule Base do
|
||||
defp unquote(do_decode)(unquote(case), string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(segs)-binary-unit(64), rest::binary>> = string
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5,
|
||||
unquote(fun)(c6)::5,
|
||||
unquote(fun)(c7)::5,
|
||||
unquote(fun)(c8)::5
|
||||
>>
|
||||
<<unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5, unquote(fun)(c6)::5,
|
||||
unquote(fun)(c7)::5, unquote(fun)(c8)::5>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::bits, unquote(fun)(c1)::5, bsr(unquote(fun)(c2), 2)::3>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
bsr(unquote(fun)(c4), 4)::1
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, bsr(unquote(fun)(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
bsr(unquote(fun)(c5), 1)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
bsr(unquote(fun)(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5,
|
||||
unquote(fun)(c6)::5,
|
||||
bsr(unquote(fun)(c7), 3)::2
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5, unquote(fun)(c6)::5,
|
||||
bsr(unquote(fun)(c7), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5,
|
||||
unquote(fun)(c6)::5,
|
||||
unquote(fun)(c7)::5,
|
||||
unquote(fun)(c8)::5
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5, unquote(fun)(c6)::5,
|
||||
unquote(fun)(c7)::5, unquote(fun)(c8)::5>>
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
<<main::bits, unquote(fun)(c1)::5, bsr(unquote(fun)(c2), 2)::3>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
bsr(unquote(fun)(c4), 4)::1
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, bsr(unquote(fun)(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
bsr(unquote(fun)(c5), 1)::4
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
bsr(unquote(fun)(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(fun)(c1)::5,
|
||||
unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5,
|
||||
unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5,
|
||||
unquote(fun)(c6)::5,
|
||||
bsr(unquote(fun)(c7), 3)::2
|
||||
>>
|
||||
|
||||
<<main::bits, unquote(fun)(c1)::5, unquote(fun)(c2)::5,
|
||||
unquote(fun)(c3)::5, unquote(fun)(c4)::5,
|
||||
unquote(fun)(c5)::5, unquote(fun)(c6)::5,
|
||||
bsr(unquote(fun)(c7), 3)::2>>
|
||||
_ ->
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
This module has been deprecated.
|
||||
|
||||
Instead of `defcallback/1` and `defmacrocallback/1`, the `@callback` and
|
||||
`@macrocallback` module attributes can be used (respectively). See the
|
||||
@@ -21,7 +21,7 @@ defmodule Behaviour do
|
||||
Defines a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback(spec) do
|
||||
do_defcallback(:defmacro, split_spec(spec, quote(do: Macro.t())))
|
||||
do_defcallback(:defmacro, split_spec(spec, quote(do: Macro.t)))
|
||||
end
|
||||
|
||||
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
|
||||
@@ -44,25 +44,21 @@ defmodule Behaviour do
|
||||
case Macro.decompose_call(spec) do
|
||||
{name, args} ->
|
||||
do_callback(kind, name, args, return, guards)
|
||||
|
||||
_ ->
|
||||
raise ArgumentError, "invalid syntax in #{kind}callback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, return, guards) do
|
||||
fun = fn
|
||||
:lists.foreach fn
|
||||
{:::, _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
|
||||
other ->
|
||||
ensure_not_default(other)
|
||||
other
|
||||
end
|
||||
|
||||
:lists.foreach(fun, args)
|
||||
end, args
|
||||
|
||||
spec =
|
||||
quote do
|
||||
@@ -70,8 +66,8 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
case kind do
|
||||
:def -> quote(do: @callback(unquote(spec)))
|
||||
:defmacro -> quote(do: @macrocallback(unquote(spec)))
|
||||
:def -> quote(do: @callback unquote(spec))
|
||||
:defmacro -> quote(do: @macrocallback unquote(spec))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,9 +82,8 @@ defmodule Behaviour do
|
||||
quote do
|
||||
warning =
|
||||
"the Behaviour module is deprecated. Instead of using this module, " <>
|
||||
"use the @callback and @macrocallback module attributes. See the " <>
|
||||
"documentation for Module for more information on these attributes"
|
||||
|
||||
"use the @callback and @macrocallback module attributes. See the " <>
|
||||
"documentation for Module for more information on these attributes"
|
||||
IO.warn(warning)
|
||||
|
||||
@doc false
|
||||
|
||||
+20
-23
@@ -37,17 +37,14 @@ defmodule Bitwise do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(options) do
|
||||
except =
|
||||
cond do
|
||||
Keyword.get(options, :only_operators) ->
|
||||
[bnot: 1, band: 2, bor: 2, bxor: 2, bsl: 2, bsr: 2]
|
||||
|
||||
Keyword.get(options, :skip_operators) ->
|
||||
[~~~: 1, &&&: 2, |||: 2, ^^^: 2, <<<: 2, >>>: 2]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
except = cond do
|
||||
Keyword.get(options, :only_operators) ->
|
||||
[bnot: 1, band: 2, bor: 2, bxor: 2, bsl: 2, bsr: 2]
|
||||
Keyword.get(options, :skip_operators) ->
|
||||
[~~~: 1, &&&: 2, |||: 2, ^^^: 2, <<<: 2, >>>: 2]
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
|
||||
quote do
|
||||
import Bitwise, except: unquote(except)
|
||||
@@ -64,7 +61,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote(do: :erlang.bnot(unquote(expr)))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -77,7 +74,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote(do: :erlang.bnot(unquote(expr)))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -88,7 +85,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote(do: :erlang.band(unquote(left), unquote(right)))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -99,7 +96,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote(do: :erlang.band(unquote(left), unquote(right)))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -110,7 +107,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote(do: :erlang.bor(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -121,7 +118,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote(do: :erlang.bor(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -132,7 +129,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote(do: :erlang.bxor(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -143,7 +140,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote(do: :erlang.bxor(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -160,7 +157,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote(do: :erlang.bsl(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -177,7 +174,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote(do: :erlang.bsl(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -194,7 +191,7 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote(do: :erlang.bsr(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -211,6 +208,6 @@ defmodule Bitwise do
|
||||
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote(do: :erlang.bsr(unquote(left), unquote(right)))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
+19
-77
@@ -35,9 +35,9 @@ defmodule Calendar do
|
||||
@typedoc """
|
||||
The internal date format that is used when converting between calendars.
|
||||
|
||||
This is the number of days including the fractional part that has passed of
|
||||
This is the amount of days including the fractional part that has passed of
|
||||
the last day since 0000-01-01+00:00T00:00.00000 in ISO 8601 notation (also
|
||||
known as midnight 1 January BC 1 of the proleptic Gregorian calendar).
|
||||
known as midnight 1 January BC 1 of the Proleptic Gregorian Calendar).
|
||||
|
||||
The `parts_per_day` represent how many subparts the current day is subdivided in
|
||||
(for different calendars, picking a different `parts_per_day` might make sense).
|
||||
@@ -59,10 +59,10 @@ defmodule Calendar do
|
||||
@type calendar :: module
|
||||
|
||||
@typedoc "The time zone ID according to the IANA tz database (e.g. Europe/Zurich)"
|
||||
@type time_zone :: String.t()
|
||||
@type time_zone :: String.t
|
||||
|
||||
@typedoc "The time zone abbreviation (e.g. CET or CEST or BST etc.)"
|
||||
@type zone_abbr :: String.t()
|
||||
@type zone_abbr :: String.t
|
||||
|
||||
@typedoc "The time zone UTC offset in seconds"
|
||||
@type utc_offset :: integer
|
||||
@@ -74,43 +74,16 @@ defmodule Calendar do
|
||||
@type date :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day}
|
||||
|
||||
@typedoc "Any map/struct that contains the time fields"
|
||||
@type time :: %{
|
||||
optional(any) => any,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
@type time :: %{optional(any) => any, hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
|
||||
@typedoc "Any map/struct that contains the naive_datetime fields"
|
||||
@type naive_datetime :: %{
|
||||
optional(any) => any,
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
@type naive_datetime :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
|
||||
@typedoc "Any map/struct that contains the datetime fields"
|
||||
@type datetime :: %{
|
||||
optional(any) => any,
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
}
|
||||
@type datetime :: %{optional(any) => any, calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}
|
||||
|
||||
@doc """
|
||||
Returns how many days there are in the given year-month.
|
||||
@@ -134,47 +107,33 @@ defmodule Calendar do
|
||||
@doc """
|
||||
Converts the date into a string according to the calendar.
|
||||
"""
|
||||
@callback date_to_string(year, month, day) :: String.t()
|
||||
@callback date_to_string(year, month, day) :: String.t
|
||||
|
||||
@doc """
|
||||
Converts the datetime (without time zone) into a string according to the calendar.
|
||||
"""
|
||||
@callback naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) ::
|
||||
String.t()
|
||||
@callback naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) :: String.t
|
||||
|
||||
@doc """
|
||||
Converts the datetime (with time zone) into a string according to the calendar.
|
||||
"""
|
||||
@callback datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
) :: String.t()
|
||||
@callback datetime_to_string(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset) :: String.t
|
||||
|
||||
@doc """
|
||||
Converts the time into a string according to the calendar.
|
||||
"""
|
||||
@callback time_to_string(hour, minute, second, microsecond) :: String.t()
|
||||
@callback time_to_string(hour, minute, second, microsecond) :: String.t
|
||||
|
||||
@doc """
|
||||
Converts the given datetime (with time zone) into the `t:iso_days` format.
|
||||
"""
|
||||
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) ::
|
||||
iso_days
|
||||
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) :: iso_days
|
||||
|
||||
@doc """
|
||||
Converts `t:iso_days` to the Calendar's datetime format.
|
||||
"""
|
||||
@callback naive_datetime_from_iso_days(iso_days) ::
|
||||
{year, month, day, hour, minute, second, microsecond}
|
||||
@callback naive_datetime_from_iso_days(iso_days) :: {year, month, day, hour, minute, second, microsecond}
|
||||
|
||||
@doc """
|
||||
Converts the given time to the `t:day_fraction` format.
|
||||
@@ -229,26 +188,9 @@ defmodule Calendar do
|
||||
between them. If they are compatible, this means that we can also convert
|
||||
dates as well as naive datetimes between them.
|
||||
"""
|
||||
@spec compatible_calendars?(Calendar.calendar(), Calendar.calendar()) :: boolean
|
||||
@spec compatible_calendars?(Calendar.calendar, Calendar.calendar) :: boolean
|
||||
def compatible_calendars?(calendar, calendar), do: true
|
||||
|
||||
def compatible_calendars?(calendar1, calendar2) do
|
||||
calendar1.day_rollover_relative_to_midnight_utc() ==
|
||||
calendar2.day_rollover_relative_to_midnight_utc()
|
||||
calendar1.day_rollover_relative_to_midnight_utc() == calendar2.day_rollover_relative_to_midnight_utc()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a microsecond tuple truncated to a given precision (`:microsecond`,
|
||||
`:millisecond` or `:second`).
|
||||
"""
|
||||
@spec truncate(Calendar.microsecond(), :microsecond | :millisecond | :second) ::
|
||||
Calendar.microsecond()
|
||||
def truncate(microsecond_tuple, :microsecond), do: microsecond_tuple
|
||||
|
||||
def truncate({microsecond, precision}, :millisecond) do
|
||||
output_precision = min(precision, 3)
|
||||
{div(microsecond, 1000) * 1000, output_precision}
|
||||
end
|
||||
|
||||
def truncate(_, :second), do: {0, 0}
|
||||
end
|
||||
|
||||
@@ -36,8 +36,8 @@ defmodule Date do
|
||||
## Using epochs
|
||||
|
||||
The `add/2` and `diff/2` functions can be used for computing dates
|
||||
or retrieving the number of days between instants. For example, if there
|
||||
is an interest in computing the number of days from the Unix epoch
|
||||
or retrieving the amount of days betweens instants. For example, if there
|
||||
is an interest in computing the amount of days from the Unix epoch
|
||||
(1970-01-01):
|
||||
|
||||
iex> Date.diff(~D[2010-04-17], ~D[1970-01-01])
|
||||
@@ -53,12 +53,8 @@ defmodule Date do
|
||||
@enforce_keys [:year, :month, :day]
|
||||
defstruct [:year, :month, :day, calendar: Calendar.ISO]
|
||||
|
||||
@type t :: %Date{
|
||||
year: Calendar.year(),
|
||||
month: Calendar.month(),
|
||||
day: Calendar.day(),
|
||||
calendar: Calendar.calendar()
|
||||
}
|
||||
@type t :: %Date{year: Calendar.year, month: Calendar.month,
|
||||
day: Calendar.day, calendar: Calendar.calendar}
|
||||
|
||||
@doc """
|
||||
Returns a range of dates.
|
||||
@@ -74,6 +70,9 @@ defmodule Date do
|
||||
iex> Date.range(~D[1999-01-01], ~D[2000-01-01])
|
||||
#DateRange<~D[1999-01-01], ~D[2000-01-01]>
|
||||
|
||||
iex> Date.range(~N[2000-01-01 09:00:00], ~D[1999-01-01])
|
||||
#DateRange<~N[2000-01-01 09:00:00], ~D[1999-01-01]>
|
||||
|
||||
A range of dates implements the `Enumerable` protocol, which means
|
||||
functions in the `Enum` module can be used to work with
|
||||
ranges:
|
||||
@@ -87,8 +86,8 @@ defmodule Date do
|
||||
-366
|
||||
"""
|
||||
|
||||
@spec range(Date.t(), Date.t()) :: Date.Range.t()
|
||||
def range(%Date{calendar: calendar} = first, %Date{calendar: calendar} = last) do
|
||||
@spec range(Calendar.date, Calendar.date) :: Date.Range.t
|
||||
def range(%{calendar: calendar} = first, %{calendar: calendar} = last) do
|
||||
{first_days, _} = to_iso_days(first)
|
||||
{last_days, _} = to_iso_days(last)
|
||||
|
||||
@@ -96,11 +95,12 @@ defmodule Date do
|
||||
first: first,
|
||||
last: last,
|
||||
first_in_iso_days: first_days,
|
||||
last_in_iso_days: last_days
|
||||
last_in_iso_days: last_days,
|
||||
}
|
||||
end
|
||||
|
||||
def range(%Date{}, %Date{}) do
|
||||
def range(%{calendar: _, year: _, month: _, day: _},
|
||||
%{calendar: _, year: _, month: _, day: _}) do
|
||||
raise ArgumentError, "both dates must have matching calendars"
|
||||
end
|
||||
|
||||
@@ -114,18 +114,18 @@ defmodule Date do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec utc_today(Calendar.calendar()) :: t
|
||||
@spec utc_today(Calendar.calendar) :: t
|
||||
def utc_today(calendar \\ Calendar.ISO)
|
||||
|
||||
def utc_today(Calendar.ISO) do
|
||||
{:ok, {year, month, day}, _, _} = Calendar.ISO.from_unix(System.os_time(), :native)
|
||||
{:ok, {year, month, day}, _, _} = Calendar.ISO.from_unix(System.os_time, :native)
|
||||
%Date{year: year, month: month, day: day}
|
||||
end
|
||||
|
||||
def utc_today(calendar) do
|
||||
calendar
|
||||
|> DateTime.utc_now()
|
||||
|> DateTime.to_date()
|
||||
|> DateTime.utc_now
|
||||
|> DateTime.to_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -145,7 +145,7 @@ defmodule Date do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec leap_year?(Calendar.date()) :: boolean()
|
||||
@spec leap_year?(Calendar.date) :: boolean()
|
||||
def leap_year?(date)
|
||||
|
||||
def leap_year?(%{calendar: calendar, year: year}) do
|
||||
@@ -165,7 +165,7 @@ defmodule Date do
|
||||
29
|
||||
|
||||
"""
|
||||
@spec days_in_month(Calendar.date()) :: Calendar.day()
|
||||
@spec days_in_month(Calendar.date) :: Calendar.day
|
||||
def days_in_month(date)
|
||||
|
||||
def days_in_month(%{calendar: calendar, year: year, month: month}) do
|
||||
@@ -193,8 +193,7 @@ defmodule Date do
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@spec new(Calendar.year(), Calendar.month(), Calendar.day(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
@spec new(Calendar.year, Calendar.month, Calendar.day) :: {:ok, t} | {:error, atom}
|
||||
def new(year, month, day, calendar \\ Calendar.ISO) do
|
||||
if calendar.valid_date?(year, month, day) do
|
||||
{:ok, %Date{year: year, month: month, day: day, calendar: calendar}}
|
||||
@@ -214,7 +213,7 @@ defmodule Date do
|
||||
"2000-02-28"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.date()) :: String.t()
|
||||
@spec to_string(Calendar.date) :: String.t
|
||||
def to_string(date)
|
||||
|
||||
def to_string(%{calendar: calendar, year: year, month: month, day: day}) do
|
||||
@@ -237,14 +236,15 @@ defmodule Date do
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
@spec from_iso8601(String.t) :: {: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)
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO),
|
||||
do: convert(date, calendar)
|
||||
else
|
||||
_ -> {:error, :invalid_format}
|
||||
end
|
||||
@@ -267,14 +267,13 @@ defmodule Date do
|
||||
iex> Date.from_iso8601!("2015:01:23")
|
||||
** (ArgumentError) cannot parse "2015:01:23" as date, reason: :invalid_format
|
||||
"""
|
||||
@spec from_iso8601!(String.t(), Calendar.calendar()) :: t
|
||||
@spec from_iso8601!(String.t) :: t
|
||||
def from_iso8601!(string, calendar \\ Calendar.ISO) do
|
||||
case from_iso8601(string, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError, "cannot parse #{inspect(string)} as date, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot parse #{inspect string} as date, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -301,7 +300,7 @@ defmodule Date do
|
||||
"2000-02-28"
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.date(), :extended | :basic) :: String.t()
|
||||
@spec to_iso8601(Calendar.date, :extended | :basic) :: String.t
|
||||
def to_iso8601(date, format \\ :extended) when format in [:basic, :extended] do
|
||||
%{year: year, month: month, day: day} = convert!(date, Calendar.ISO)
|
||||
Calendar.ISO.date_to_iso8601(year, month, day, format)
|
||||
@@ -323,7 +322,7 @@ defmodule Date do
|
||||
{2000, 1, 1}
|
||||
|
||||
"""
|
||||
@spec to_erl(Calendar.date()) :: :calendar.date()
|
||||
@spec to_erl(Calendar.date) :: :calendar.date
|
||||
def to_erl(date) do
|
||||
%{year: year, month: month, day: day} = convert!(date, Calendar.ISO)
|
||||
{year, month, day}
|
||||
@@ -344,11 +343,12 @@ defmodule Date do
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@spec from_erl(:calendar.date(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
@spec from_erl(:calendar.date) :: {:ok, t} | {:error, atom}
|
||||
def from_erl(tuple, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_erl({year, month, day}, calendar) do
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO), do: convert(date, calendar)
|
||||
with {:ok, date} <- new(year, month, day, Calendar.ISO),
|
||||
do: convert(date, calendar)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -362,15 +362,13 @@ defmodule Date do
|
||||
** (ArgumentError) cannot convert {2000, 13, 1} to date, reason: :invalid_date
|
||||
|
||||
"""
|
||||
@spec from_erl!(:calendar.date(), Calendar.calendar()) :: t
|
||||
def from_erl!(tuple, calendar \\ Calendar.ISO) do
|
||||
case from_erl(tuple, calendar) do
|
||||
@spec from_erl!(:calendar.date) :: t
|
||||
def from_erl!(tuple) do
|
||||
case from_erl(tuple) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(tuple)} to date, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot convert #{inspect tuple} to date, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -397,18 +395,15 @@ defmodule Date do
|
||||
:eq
|
||||
|
||||
"""
|
||||
@spec compare(Calendar.date(), Calendar.date()) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar} = date1, %{calendar: calendar} = date2) do
|
||||
%{year: year1, month: month1, day: day1} = date1
|
||||
%{year: year2, month: month2, day: day2} = date2
|
||||
|
||||
@spec compare(Calendar.date, Calendar.date) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar, year: year1, month: month1, day: day1},
|
||||
%{calendar: calendar, year: year2, month: month2, day: day2}) do
|
||||
case {{year1, month1, day1}, {year2, month2, day2}} do
|
||||
{first, second} when first > second -> :gt
|
||||
{first, second} when first < second -> :lt
|
||||
_ -> :eq
|
||||
end
|
||||
end
|
||||
|
||||
def compare(date1, date2) do
|
||||
if Calendar.compatible_calendars?(date1.calendar, date2.calendar) do
|
||||
case {to_iso_days(date1), to_iso_days(date2)} do
|
||||
@@ -418,7 +413,7 @@ defmodule Date do
|
||||
end
|
||||
else
|
||||
raise ArgumentError, """
|
||||
cannot compare #{inspect(date1)} with #{inspect(date2)}.
|
||||
cannot compare #{inspect date1} with #{inspect date2}.
|
||||
|
||||
This comparison would be ambiguous as their calendars have incompatible day rollover moments.
|
||||
Specify an exact time of day (using `DateTime`s) to resolve this ambiguity
|
||||
@@ -427,7 +422,7 @@ defmodule Date do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given `date` from its calendar to the given `calendar`.
|
||||
Converts the given `date` from it's calendar to the given `calendar`.
|
||||
|
||||
Returns `{:ok, date}` if the calendars are compatible,
|
||||
or `{:error, :incompatible_calendars}` if they are not.
|
||||
@@ -444,19 +439,16 @@ defmodule Date do
|
||||
{:ok, %Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}}
|
||||
|
||||
"""
|
||||
@spec convert(Calendar.date(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
@spec convert(Calendar.date, Calendar.calendar) :: {:ok, t} | {:error, :incompatible_calendars}
|
||||
def convert(%{calendar: calendar, year: year, month: month, day: day}, calendar) do
|
||||
{:ok, %Date{calendar: calendar, year: year, month: month, day: day}}
|
||||
end
|
||||
|
||||
def convert(%{calendar: calendar} = date, target_calendar) do
|
||||
if Calendar.compatible_calendars?(calendar, target_calendar) do
|
||||
result_date =
|
||||
date
|
||||
|> to_iso_days()
|
||||
|> from_iso_days(target_calendar)
|
||||
|
||||
{:ok, result_date}
|
||||
else
|
||||
{:error, :incompatible_calendars}
|
||||
@@ -477,23 +469,20 @@ defmodule Date do
|
||||
%Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}
|
||||
|
||||
"""
|
||||
@spec convert!(Calendar.date(), Calendar.calendar()) :: t
|
||||
@spec convert!(Calendar.date, Calendar.calendar) :: t
|
||||
def convert!(date, calendar) do
|
||||
case convert(date, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(date)} to target calendar #{inspect(calendar)}, " <>
|
||||
"reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot convert #{inspect date} to target calendar #{inspect calendar}, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds the number of days to the given `date`.
|
||||
|
||||
The days are counted as Gregorian days. The date is returned in the same
|
||||
The days are counted as gregorian days. The date is returned in the same
|
||||
calendar as it was given in.
|
||||
|
||||
## Examples
|
||||
@@ -507,16 +496,16 @@ defmodule Date do
|
||||
~D[2000-01-03]
|
||||
|
||||
"""
|
||||
@spec add(Calendar.date(), integer()) :: t
|
||||
@spec add(Calendar.date, integer()) :: t
|
||||
def add(%{calendar: calendar} = date, days) do
|
||||
{iso_days, fraction} = to_iso_days(date)
|
||||
from_iso_days({iso_days + days, fraction}, calendar)
|
||||
{iso_days_days, fraction} = to_iso_days(date)
|
||||
from_iso_days({iso_days_days + days, fraction}, calendar)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculates the difference between two dates, in a full number of days.
|
||||
|
||||
It returns the number of Gregorian days between the dates. Only `Date`
|
||||
It returns the number of gregorian days between the dates. Only `Date`
|
||||
structs that follow the same or compatible calendars can be compared
|
||||
this way. If two calendars are not compatible, it will raise.
|
||||
|
||||
@@ -531,13 +520,11 @@ defmodule Date do
|
||||
-2
|
||||
|
||||
"""
|
||||
@spec diff(Calendar.date(), Calendar.date()) :: integer
|
||||
def diff(%{calendar: Calendar.ISO} = date1, %{calendar: Calendar.ISO} = date2) do
|
||||
%{year: year1, month: month1, day: day1} = date1
|
||||
%{year: year2, month: month2, day: day2} = date2
|
||||
|
||||
Calendar.ISO.date_to_iso_days(year1, month1, day1) -
|
||||
Calendar.ISO.date_to_iso_days(year2, month2, day2)
|
||||
@spec diff(Calendar.date, Calendar.date) :: integer
|
||||
def diff(%{calendar: Calendar.ISO, year: year1, month: month1, day: day1},
|
||||
%{calendar: Calendar.ISO, year: year2, month: month2, day: day2}) do
|
||||
Calendar.ISO.date_to_iso_days_days(year1, month1, day1) -
|
||||
Calendar.ISO.date_to_iso_days_days(year2, month2, day2)
|
||||
end
|
||||
|
||||
def diff(%{calendar: calendar1} = date1, %{calendar: calendar2} = date2) do
|
||||
@@ -546,24 +533,21 @@ defmodule Date do
|
||||
{days2, _} = to_iso_days(date2)
|
||||
days1 - days2
|
||||
else
|
||||
raise ArgumentError,
|
||||
"cannot calculate the difference between #{inspect(date1)} and #{inspect(date2)} because their calendars are not compatible and thus the result would be ambiguous"
|
||||
raise ArgumentError, "cannot calculate the difference between #{inspect date1} and #{inspect date2} because their calendars are not compatible and thus the result would be ambiguous"
|
||||
end
|
||||
end
|
||||
|
||||
defp to_iso_days(%{calendar: Calendar.ISO, year: year, month: month, day: day}) do
|
||||
{Calendar.ISO.date_to_iso_days(year, month, day), {0, 86_400_000_000}}
|
||||
{Calendar.ISO.date_to_iso_days_days(year, month, day), {0, 86400000000}}
|
||||
end
|
||||
|
||||
defp to_iso_days(%{calendar: calendar, year: year, month: month, day: day}) do
|
||||
calendar.naive_datetime_to_iso_days(year, month, day, 0, 0, 0, {0, 0})
|
||||
end
|
||||
|
||||
defp from_iso_days({days, _}, Calendar.ISO) do
|
||||
{year, month, day} = Calendar.ISO.date_from_iso_days(days)
|
||||
{year, month, day} = Calendar.ISO.date_from_iso_days_days(days)
|
||||
%Date{year: year, month: month, day: day, calendar: Calendar.ISO}
|
||||
end
|
||||
|
||||
defp from_iso_days(iso_days, target_calendar) do
|
||||
{year, month, day, _, _, _, _} = target_calendar.naive_datetime_from_iso_days(iso_days)
|
||||
%Date{year: year, month: month, day: day, calendar: target_calendar}
|
||||
@@ -586,7 +570,7 @@ defmodule Date do
|
||||
2
|
||||
|
||||
"""
|
||||
@spec day_of_week(Calendar.date()) :: non_neg_integer()
|
||||
@spec day_of_week(Calendar.date) :: non_neg_integer()
|
||||
def day_of_week(date)
|
||||
|
||||
def day_of_week(%{calendar: calendar, year: year, month: month, day: day}) do
|
||||
|
||||
@@ -12,27 +12,17 @@ defmodule Date.Range do
|
||||
The remaining fields are private and should not be accessed.
|
||||
"""
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
first: Date.t(),
|
||||
last: Date.t(),
|
||||
first_in_iso_days: Calendar.iso_days(),
|
||||
last_in_iso_days: Calendar.iso_days()
|
||||
}
|
||||
@type t :: %__MODULE__{first: Date.t, last: Date.t,
|
||||
first_in_iso_days: Calendar.days,
|
||||
last_in_iso_days: Calendar.days}
|
||||
|
||||
defstruct [:first, :last, :first_in_iso_days, :last_in_iso_days]
|
||||
|
||||
defimpl Enumerable do
|
||||
def member?(%{first: %{calendar: calendar}} = range, %Date{calendar: calendar} = date) do
|
||||
%{
|
||||
first: first,
|
||||
last: last,
|
||||
first_in_iso_days: first_in_iso_days,
|
||||
last_in_iso_days: last_in_iso_days
|
||||
} = range
|
||||
|
||||
%{year: first_year, month: first_month, day: first_day} = first
|
||||
%{year: last_year, month: last_month, day: last_day} = last
|
||||
%{year: year, month: month, day: day} = date
|
||||
def member?(%{first: %{calendar: calendar, year: first_year, month: first_month, day: first_day},
|
||||
last: %{calendar: calendar, year: last_year, month: last_month, day: last_day},
|
||||
first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days},
|
||||
%Date{calendar: calendar, year: year, month: month, day: day}) do
|
||||
first = {first_year, first_month, first_day}
|
||||
last = {last_year, last_month, last_day}
|
||||
date = {year, month, day}
|
||||
@@ -48,45 +38,13 @@ defmodule Date.Range do
|
||||
{:ok, false}
|
||||
end
|
||||
|
||||
def count(%{first_in_iso_days: first, last_in_iso_days: last}) do
|
||||
{:ok, abs(first - last) + 1}
|
||||
def count(%Date.Range{first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days}) do
|
||||
{:ok, abs(first_in_iso_days - last_in_iso_days) + 1}
|
||||
end
|
||||
|
||||
def slice(range) do
|
||||
%{
|
||||
first_in_iso_days: first,
|
||||
last_in_iso_days: last,
|
||||
first: %{calendar: calendar}
|
||||
} = range
|
||||
|
||||
if first <= last do
|
||||
{:ok, last - first + 1, &slice_asc(first + &1, &2, calendar)}
|
||||
else
|
||||
{:ok, first - last + 1, &slice_desc(first - &1, &2, calendar)}
|
||||
end
|
||||
end
|
||||
|
||||
defp slice_asc(current, 1, calendar), do: [date_from_iso_days(current, calendar)]
|
||||
|
||||
defp slice_asc(current, remaining, calendar) do
|
||||
[date_from_iso_days(current, calendar) | slice_asc(current + 1, remaining - 1, calendar)]
|
||||
end
|
||||
|
||||
defp slice_desc(current, 1, calendar), do: [date_from_iso_days(current, calendar)]
|
||||
|
||||
defp slice_desc(current, remaining, calendar) do
|
||||
[date_from_iso_days(current, calendar) | slice_desc(current - 1, remaining - 1, calendar)]
|
||||
end
|
||||
|
||||
def reduce(range, acc, fun) do
|
||||
%{
|
||||
first_in_iso_days: first_in_iso_days,
|
||||
last_in_iso_days: last_in_iso_days,
|
||||
first: %{calendar: calendar}
|
||||
} = range
|
||||
|
||||
up? = first_in_iso_days <= last_in_iso_days
|
||||
reduce(first_in_iso_days, last_in_iso_days, acc, fun, calendar, up?)
|
||||
def reduce(%Date.Range{first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days,
|
||||
first: %{calendar: calendar}}, acc, fun) do
|
||||
reduce(first_in_iso_days, last_in_iso_days, acc, fun, calendar, first_in_iso_days <= last_in_iso_days)
|
||||
end
|
||||
|
||||
defp reduce(_x, _y, {:halt, acc}, _fun, _calendar, _up?) do
|
||||
@@ -98,26 +56,24 @@ defmodule Date.Range do
|
||||
end
|
||||
|
||||
defp reduce(x, y, {:cont, acc}, fun, calendar, up? = true) when x <= y do
|
||||
reduce(x + 1, y, fun.(date_from_iso_days(x, calendar), acc), fun, calendar, up?)
|
||||
reduce(x + 1, y, fun.(date_from_iso_days_days(x, calendar), acc), fun, calendar, up?)
|
||||
end
|
||||
|
||||
defp reduce(x, y, {:cont, acc}, fun, calendar, up? = false) when x >= y do
|
||||
reduce(x - 1, y, fun.(date_from_iso_days(x, calendar), acc), fun, calendar, up?)
|
||||
reduce(x - 1, y, fun.(date_from_iso_days_days(x, calendar), acc), fun, calendar, up?)
|
||||
end
|
||||
|
||||
defp reduce(_, _, {:cont, acc}, _fun, _calendar, _up) do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
defp date_from_iso_days(days, Calendar.ISO) do
|
||||
{year, month, day} = Calendar.ISO.date_from_iso_days(days)
|
||||
defp date_from_iso_days_days(days, Calendar.ISO) do
|
||||
{year, month, day} = Calendar.ISO.date_from_iso_days_days(days)
|
||||
%Date{year: year, month: month, day: day, calendar: Calendar.ISO}
|
||||
end
|
||||
|
||||
defp date_from_iso_days(days, calendar) do
|
||||
{year, month, day, _, _, _, _} =
|
||||
calendar.naive_datetime_from_iso_days({days, {0, 86_400_000_000}})
|
||||
|
||||
defp date_from_iso_days_days(days, calendar) do
|
||||
{year, month, day, _, _, _, _} = calendar.naive_datetime_from_iso_days({days, {0, 86400000000}})
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
end
|
||||
|
||||
+119
-398
@@ -34,38 +34,16 @@ defmodule DateTime do
|
||||
similar functionality with time zone backing.
|
||||
"""
|
||||
|
||||
@enforce_keys [:year, :month, :day, :hour, :minute, :second] ++
|
||||
[:time_zone, :zone_abbr, :utc_offset, :std_offset]
|
||||
@enforce_keys [:year, :month, :day, :hour, :minute, :second,
|
||||
:time_zone, :zone_abbr, :utc_offset, :std_offset]
|
||||
defstruct [:year, :month, :day, :hour, :minute, :second, :time_zone,
|
||||
:zone_abbr, :utc_offset, :std_offset, microsecond: {0, 0}, calendar: Calendar.ISO]
|
||||
|
||||
defstruct [
|
||||
:year,
|
||||
:month,
|
||||
:day,
|
||||
:hour,
|
||||
:minute,
|
||||
:second,
|
||||
:time_zone,
|
||||
:zone_abbr,
|
||||
:utc_offset,
|
||||
:std_offset,
|
||||
microsecond: {0, 0},
|
||||
calendar: Calendar.ISO
|
||||
]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
year: Calendar.year(),
|
||||
month: Calendar.month(),
|
||||
day: Calendar.day(),
|
||||
calendar: Calendar.calendar(),
|
||||
hour: Calendar.hour(),
|
||||
minute: Calendar.minute(),
|
||||
second: Calendar.second(),
|
||||
microsecond: Calendar.microsecond(),
|
||||
time_zone: Calendar.time_zone(),
|
||||
zone_abbr: Calendar.zone_abbr(),
|
||||
utc_offset: Calendar.utc_offset(),
|
||||
std_offset: Calendar.std_offset()
|
||||
}
|
||||
@type t :: %__MODULE__{year: Calendar.year, month: Calendar.month, day: Calendar.day,
|
||||
calendar: Calendar.calendar, hour: Calendar.hour, minute: Calendar.minute,
|
||||
second: Calendar.second, microsecond: Calendar.microsecond,
|
||||
time_zone: Calendar.time_zone, zone_abbr: Calendar.zone_abbr,
|
||||
utc_offset: Calendar.utc_offset, std_offset: Calendar.std_offset}
|
||||
|
||||
@unix_days :calendar.date_to_gregorian_days({1970, 1, 1})
|
||||
|
||||
@@ -79,9 +57,9 @@ defmodule DateTime do
|
||||
"Etc/UTC"
|
||||
|
||||
"""
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
@spec utc_now(Calendar.calendar) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO) do
|
||||
System.os_time() |> from_unix!(:native, calendar)
|
||||
System.os_time |> from_unix!(:native, calendar)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -113,27 +91,14 @@ defmodule DateTime do
|
||||
Negative Unix times are supported, up to -62167219200 seconds,
|
||||
which is equivalent to "0000-01-01T00:00:00Z" or 0 Gregorian seconds.
|
||||
"""
|
||||
@spec from_unix(integer, :native | System.time_unit(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
@spec from_unix(integer, :native | System.time_unit, Calendar.calendar) :: {:ok, t} | {:error, atom}
|
||||
def from_unix(integer, unit \\ :second, calendar \\ Calendar.ISO) when is_integer(integer) do
|
||||
case Calendar.ISO.from_unix(integer, unit) do
|
||||
{:ok, {year, month, day}, {hour, minute, second}, microsecond} ->
|
||||
iso_datetime = %DateTime{
|
||||
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"
|
||||
}
|
||||
|
||||
iso_datetime = %DateTime{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"}
|
||||
convert(iso_datetime, calendar)
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
@@ -162,12 +127,11 @@ defmodule DateTime do
|
||||
#DateTime<2015-05-25 13:26:08.868569Z>
|
||||
|
||||
"""
|
||||
@spec from_unix!(integer, :native | System.time_unit(), Calendar.calendar()) :: t
|
||||
@spec from_unix!(integer, :native | System.time_unit, Calendar.calendar) :: t
|
||||
def from_unix!(integer, unit \\ :second, calendar \\ Calendar.ISO) when is_atom(unit) do
|
||||
case from_unix(integer, unit, calendar) do
|
||||
{:ok, datetime} ->
|
||||
datetime
|
||||
|
||||
{:error, :invalid_unix_time} ->
|
||||
raise ArgumentError, "invalid Unix time #{integer}"
|
||||
end
|
||||
@@ -186,37 +150,15 @@ defmodule DateTime do
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
|
||||
"""
|
||||
@spec from_naive(NaiveDateTime.t(), Calendar.time_zone()) :: {:ok, t}
|
||||
@spec from_naive(NaiveDateTime.t, Calendar.time_zone) :: {:ok, t}
|
||||
def from_naive(naive_datetime, time_zone)
|
||||
|
||||
def from_naive(%NaiveDateTime{} = naive_datetime, "Etc/UTC") do
|
||||
%{
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day
|
||||
} = naive_datetime
|
||||
|
||||
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}
|
||||
def from_naive(%NaiveDateTime{calendar: calendar,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
year: year, month: month, day: day}, "Etc/UTC") do
|
||||
{:ok, %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"}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -231,15 +173,13 @@ defmodule DateTime do
|
||||
#DateTime<2016-05-24 13:26:08.003Z>
|
||||
|
||||
"""
|
||||
@spec from_naive!(NaiveDateTime.t(), Calendar.time_zone()) :: t
|
||||
@spec from_naive!(NaiveDateTime.t, Calendar.time_zone) :: t
|
||||
def from_naive!(naive_datetime, time_zone) do
|
||||
case from_naive(naive_datetime, time_zone) do
|
||||
{:ok, datetime} ->
|
||||
datetime
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot parse #{inspect(naive_datetime)} to datetime, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot parse #{inspect naive_datetime} to datetime, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -270,7 +210,7 @@ defmodule DateTime do
|
||||
-17412508655
|
||||
|
||||
"""
|
||||
@spec to_unix(Calendar.datetime(), System.time_unit()) :: integer
|
||||
@spec to_unix(Calendar.datetime, System.time_unit) :: integer
|
||||
def to_unix(datetime, unit \\ :second)
|
||||
|
||||
def to_unix(%{utc_offset: utc_offset, std_offset: std_offset} = datetime, unit) do
|
||||
@@ -295,29 +235,11 @@ defmodule DateTime do
|
||||
~N[2000-02-29 23:00:07.0]
|
||||
|
||||
"""
|
||||
@spec to_naive(t) :: NaiveDateTime.t()
|
||||
def to_naive(%DateTime{} = datetime) do
|
||||
%DateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = datetime
|
||||
|
||||
%NaiveDateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
@spec to_naive(t) :: NaiveDateTime.t
|
||||
def to_naive(%DateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
%NaiveDateTime{year: year, month: month, day: day, calendar: calendar,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -335,9 +257,8 @@ defmodule DateTime do
|
||||
~D[2000-02-29]
|
||||
|
||||
"""
|
||||
@spec to_date(t) :: Date.t()
|
||||
def to_date(%DateTime{} = datetime) do
|
||||
%{year: year, month: month, day: day, calendar: calendar} = datetime
|
||||
@spec to_date(t) :: Date.t
|
||||
def to_date(%DateTime{year: year, month: month, day: day, calendar: calendar}) do
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
|
||||
@@ -356,18 +277,9 @@ defmodule DateTime do
|
||||
~T[23:00:07.0]
|
||||
|
||||
"""
|
||||
@spec to_time(t) :: Time.t()
|
||||
def to_time(%DateTime{} = datetime) do
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar} =
|
||||
datetime
|
||||
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
}
|
||||
@spec to_time(t) :: Time.t
|
||||
def to_time(%DateTime{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}) do
|
||||
%Time{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -411,56 +323,33 @@ defmodule DateTime do
|
||||
"20000229T230007-0400"
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.datetime(), :extended | :basic) :: String.t()
|
||||
@spec to_iso8601(Calendar.datetime, :extended | :basic ) :: String.t
|
||||
def to_iso8601(datetime, format \\ :extended)
|
||||
|
||||
def to_iso8601(_, format) when format not in [:extended, :basic] do
|
||||
raise ArgumentError,
|
||||
"DateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect(format)}"
|
||||
def to_iso8601(%{calendar: Calendar.ISO, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}, format) when format in [:extended, :basic] do
|
||||
Calendar.ISO.datetime_to_iso8601(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
|
||||
Calendar.ISO.datetime_to_iso8601(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset,
|
||||
format
|
||||
)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = datetime, format) do
|
||||
def to_iso8601(%{calendar: _, year: _, month: _, day: _,
|
||||
hour: _, minute: _, second: _, microsecond: _,
|
||||
time_zone: _, zone_abbr: _, utc_offset: _, std_offset: _} = datetime, format) when format in [:extended, :basic] do
|
||||
datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
def to_iso8601(_, format) do
|
||||
raise ArgumentError, "DateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect format}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the extended "Date and time of day" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
|
||||
Since ISO 8601 does not include the proper time zone, the given
|
||||
Since ISO8601 does not include the proper time zone, the given
|
||||
string will be converted to UTC and its offset in seconds will be
|
||||
returned as part of this function. Therefore offset information
|
||||
must be present in the string.
|
||||
@@ -470,7 +359,7 @@ defmodule DateTime do
|
||||
|
||||
Time representations with reduced accuracy are not supported.
|
||||
|
||||
Note that while ISO 8601 allows datetimes to specify 24:00:00 as the
|
||||
Note that while ISO8601 allows datetimes to specify 24:00:00 as the
|
||||
zero hour of the next day, this notation is not supported by Elixir.
|
||||
|
||||
## Examples
|
||||
@@ -504,13 +393,12 @@ defmodule DateTime do
|
||||
{:error, :invalid_format}
|
||||
|
||||
"""
|
||||
@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),
|
||||
@spec from_iso8601(String.t, Calendar.calendar) :: {:ok, t, Calendar.utc_offset} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep,
|
||||
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>, calendar) when sep in [?\s, ?T] do
|
||||
with {year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
@@ -525,15 +413,7 @@ defmodule DateTime do
|
||||
{_, precision} = microsecond
|
||||
|
||||
datetime =
|
||||
Calendar.ISO.naive_datetime_to_iso_days(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond
|
||||
)
|
||||
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)
|
||||
|
||||
@@ -544,6 +424,10 @@ defmodule DateTime do
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(_, _) do
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
|
||||
defp parse_offset(rest) do
|
||||
case Calendar.ISO.parse_offset(rest) do
|
||||
{offset, ""} when is_integer(offset) -> {:ok, offset}
|
||||
@@ -576,45 +460,46 @@ defmodule DateTime do
|
||||
"2000-02-29 23:00:07-04:00 AMT America/Manaus"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.datetime()) :: String.t()
|
||||
def to_string(%{calendar: calendar} = datetime) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
@spec to_string(Calendar.datetime) :: String.t
|
||||
def to_string(datetime)
|
||||
|
||||
calendar.datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
)
|
||||
def to_string(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}) do
|
||||
calendar.datetime_to_string(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset)
|
||||
end
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}) do
|
||||
calendar.datetime_to_string(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect do
|
||||
def inspect(%{calendar: Calendar.ISO, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}, _) do
|
||||
"#DateTime<" <> Calendar.ISO.datetime_to_string(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset) <> ">"
|
||||
end
|
||||
|
||||
def inspect(datetime, opts) do
|
||||
Inspect.Any.inspect(datetime, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compares two datetime structs.
|
||||
|
||||
Returns `:gt` if the first datetime is later than the second
|
||||
Returns `:gt` if first datetime is later than the second
|
||||
and `:lt` for vice versa. If the two datetimes are equal
|
||||
`:eq` is returned.
|
||||
|
||||
Note that both UTC and Standard offsets will be taken into
|
||||
Note that both utc and stc offsets will be taken into
|
||||
account when comparison is done.
|
||||
|
||||
## Examples
|
||||
@@ -629,11 +514,9 @@ defmodule DateTime do
|
||||
:gt
|
||||
|
||||
"""
|
||||
@spec compare(Calendar.datetime(), Calendar.datetime()) :: :lt | :eq | :gt
|
||||
def compare(
|
||||
%{calendar: _, utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
%{calendar: _, utc_offset: utc_offset2, std_offset: std_offset2} = datetime2
|
||||
) do
|
||||
@spec compare(Calendar.datetime, Calendar.datetime) :: :lt | :eq | :gt
|
||||
def compare(%DateTime{utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
%DateTime{utc_offset: utc_offset2, std_offset: std_offset2} = datetime2) do
|
||||
{days1, {parts1, ppd1}} =
|
||||
datetime1
|
||||
|> to_iso_days()
|
||||
@@ -648,7 +531,7 @@ defmodule DateTime do
|
||||
iso_days1 = {days1, parts1 * ppd2}
|
||||
iso_days2 = {days2, parts2 * ppd1}
|
||||
|
||||
case {iso_days1, iso_days2} do
|
||||
case {iso_days1, iso_days2} do
|
||||
{first, second} when first > second -> :gt
|
||||
{first, second} when first < second -> :lt
|
||||
_ -> :eq
|
||||
@@ -677,50 +560,17 @@ defmodule DateTime do
|
||||
-18000
|
||||
|
||||
"""
|
||||
@spec diff(Calendar.datetime(), Calendar.datetime()) :: integer()
|
||||
def diff(
|
||||
%{utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
%{utc_offset: utc_offset2, std_offset: std_offset2} = datetime2,
|
||||
unit \\ :second
|
||||
) do
|
||||
@spec diff(Calendar.datetime, Calendar.datetime) :: integer()
|
||||
def diff(%{utc_offset: utc_offset1, std_offset: std_offset1} = datetime1,
|
||||
%{utc_offset: utc_offset2, std_offset: std_offset2} = datetime2, unit \\ :second) do
|
||||
naive_diff =
|
||||
(datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)) -
|
||||
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit))
|
||||
|
||||
offset_diff = utc_offset2 + std_offset2 - (utc_offset1 + std_offset1)
|
||||
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit))
|
||||
offset_diff =
|
||||
(utc_offset2 + std_offset2) - (utc_offset1 + std_offset1)
|
||||
naive_diff + System.convert_time_unit(offset_diff, :second, unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given datetime with the microsecond field truncated to the given
|
||||
precision (`:microsecond`, `millisecond` or `:second`).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dt1 = %DateTime{year: 2017, month: 11, day: 7, zone_abbr: "CET",
|
||||
...> hour: 11, minute: 45, second: 18, microsecond: {123456, 6},
|
||||
...> utc_offset: 3600, std_offset: 0, time_zone: "Europe/Paris"}
|
||||
iex> DateTime.truncate(dt1, :microsecond)
|
||||
#DateTime<2017-11-07 11:45:18.123456+01:00 CET Europe/Paris>
|
||||
|
||||
iex> dt2 = %DateTime{year: 2017, month: 11, day: 7, zone_abbr: "CET",
|
||||
...> hour: 11, minute: 45, second: 18, microsecond: {123456, 6},
|
||||
...> utc_offset: 3600, std_offset: 0, time_zone: "Europe/Paris"}
|
||||
iex> DateTime.truncate(dt2, :millisecond)
|
||||
#DateTime<2017-11-07 11:45:18.123+01:00 CET Europe/Paris>
|
||||
|
||||
iex> dt3 = %DateTime{year: 2017, month: 11, day: 7, zone_abbr: "CET",
|
||||
...> hour: 11, minute: 45, second: 18, microsecond: {123456, 6},
|
||||
...> utc_offset: 3600, std_offset: 0, time_zone: "Europe/Paris"}
|
||||
iex> DateTime.truncate(dt3, :second)
|
||||
#DateTime<2017-11-07 11:45:18+01:00 CET Europe/Paris>
|
||||
|
||||
"""
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%DateTime{microsecond: microsecond} = datetime, precision) do
|
||||
%{datetime | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a given `datetime` from one calendar to another.
|
||||
|
||||
@@ -744,43 +594,13 @@ defmodule DateTime do
|
||||
zone_abbr: "AMT"}}
|
||||
|
||||
"""
|
||||
@spec convert(Calendar.datetime(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
def convert(
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
},
|
||||
calendar
|
||||
) do
|
||||
datetime = %DateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
}
|
||||
|
||||
{:ok, datetime}
|
||||
@spec convert(Calendar.datetime, Calendar.calendar) :: {:ok, t} | {:error, :incompatible_calendars}
|
||||
def convert(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}, calendar) do
|
||||
{:ok, %DateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond,
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}}
|
||||
end
|
||||
|
||||
def convert(%{calendar: dt_calendar, microsecond: {_, precision}} = datetime, calendar) do
|
||||
@@ -789,7 +609,6 @@ defmodule DateTime do
|
||||
datetime
|
||||
|> to_iso_days
|
||||
|> from_iso_days(datetime, calendar, precision)
|
||||
|
||||
{:ok, result_datetime}
|
||||
else
|
||||
{:error, :incompatible_calendars}
|
||||
@@ -818,132 +637,34 @@ defmodule DateTime do
|
||||
zone_abbr: "AMT"}
|
||||
|
||||
"""
|
||||
@spec convert!(Calendar.datetime(), Calendar.calendar()) :: t | no_return
|
||||
@spec convert!(Calendar.datetime, Calendar.calendar) :: t | no_return
|
||||
def convert!(datetime, calendar) do
|
||||
case convert(datetime, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, :incompatible_calendars} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(datetime)} to target calendar #{inspect(calendar)}, " <>
|
||||
"reason: #{inspect(datetime.calendar)} and #{inspect(calendar)} have different " <>
|
||||
"day rollover moments, making this conversion ambiguous"
|
||||
raise ArgumentError, "cannot convert #{inspect datetime} to target calendar #{inspect calendar}, reason: #{inspect datetime.calendar} and #{inspect calendar} have different day rollover moments, making this conversion ambiguous"
|
||||
end
|
||||
end
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
defp to_iso_days(%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}) do
|
||||
defp to_iso_days(%{calendar: calendar,year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
calendar.naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
defp from_iso_days(iso_days, datetime, calendar, precision) do
|
||||
%{time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset} =
|
||||
datetime
|
||||
|
||||
%{time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset} = datetime
|
||||
from_iso_days(iso_days, time_zone, zone_abbr, utc_offset, std_offset, calendar, precision)
|
||||
end
|
||||
|
||||
defp from_iso_days(iso_days, time_zone, zone_abbr, utc_offset, std_offset, calendar, precision) do
|
||||
{year, month, day, hour, minute, second, {microsecond, _}} =
|
||||
calendar.naive_datetime_from_iso_days(iso_days)
|
||||
|
||||
%DateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision},
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
}
|
||||
{year, month, day, hour, minute, second, {microsecond, _}} = calendar.naive_datetime_from_iso_days(iso_days)
|
||||
%DateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: {microsecond, precision},
|
||||
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}
|
||||
end
|
||||
|
||||
defp apply_tz_offset(iso_days, offset) do
|
||||
Calendar.ISO.add_day_fraction_to_iso_days(iso_days, -offset, 86400)
|
||||
end
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(datetime) do
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
|
||||
calendar.datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect do
|
||||
def inspect(%{calendar: Calendar.ISO} = datetime, _) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
time_zone: time_zone,
|
||||
zone_abbr: zone_abbr,
|
||||
utc_offset: utc_offset,
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
|
||||
"#DateTime<" <>
|
||||
Calendar.ISO.datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
) <> ">"
|
||||
end
|
||||
|
||||
def inspect(datetime, opts) do
|
||||
Inspect.Any.inspect(datetime, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+70
-274
@@ -1,6 +1,6 @@
|
||||
defmodule Calendar.ISO do
|
||||
@moduledoc """
|
||||
A calendar implementation that follows to ISO 8601.
|
||||
A calendar implementation that follows to ISO8601.
|
||||
|
||||
This calendar implements the proleptic Gregorian calendar and
|
||||
is therefore compatible with the calendar used in most countries
|
||||
@@ -8,16 +8,16 @@ defmodule Calendar.ISO do
|
||||
applied for all time, consequently the dates give different results
|
||||
before the year 1583 from when the Gregorian calendar was adopted.
|
||||
|
||||
Note that while ISO 8601 allows times and datetimes to specify
|
||||
Note that while ISO8601 allows times and datetimes to specify
|
||||
24:00:00 as the zero hour of the next day, this notation is not
|
||||
supported by Elixir.
|
||||
"""
|
||||
|
||||
@behaviour Calendar
|
||||
|
||||
@unix_epoch 62_167_219_200
|
||||
@unix_epoch 62167219200
|
||||
@unix_start 1_000_000 * -@unix_epoch
|
||||
@unix_end 315_569_519_999_999_999 - @unix_epoch * 1_000_000
|
||||
@unix_end 1_000_000 * (315569519999 - @unix_epoch)
|
||||
@unix_range_microseconds @unix_start..@unix_end
|
||||
|
||||
@type year :: 0..9999
|
||||
@@ -26,13 +26,8 @@ defmodule Calendar.ISO do
|
||||
|
||||
@seconds_per_minute 60
|
||||
@seconds_per_hour 60 * 60
|
||||
# Note that this does _not_ handle leap seconds.
|
||||
@seconds_per_day 24 * 60 * 60
|
||||
@seconds_per_day 24 * 60 * 60 # Note that this does _not_ handle leap seconds.
|
||||
@microseconds_per_second 1_000_000
|
||||
@parts_per_day @seconds_per_day * @microseconds_per_second
|
||||
|
||||
@days_per_nonleap_year 365
|
||||
@days_per_leap_year 366
|
||||
|
||||
@doc """
|
||||
Returns the `t:Calendar.iso_days` format of the specified date.
|
||||
@@ -47,18 +42,12 @@ defmodule Calendar.ISO do
|
||||
{730485, {46800000000, 86400000000}}
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@spec naive_datetime_to_iso_days(
|
||||
Calendar.year(),
|
||||
Calendar.month(),
|
||||
Calendar.day(),
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
) :: Calendar.iso_days()
|
||||
@spec naive_datetime_to_iso_days(Calendar.year, Calendar.month, Calendar.day,
|
||||
Calendar.hour, Calendar.minute, Calendar.second,
|
||||
Calendar.microsecond) :: Calendar.iso_days
|
||||
def naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) do
|
||||
{date_to_iso_days(year, month, day), time_to_day_fraction(hour, minute, second, microsecond)}
|
||||
{date_to_iso_days_days(year, month, day),
|
||||
time_to_day_fraction(hour, minute, second, microsecond)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -74,18 +63,11 @@ defmodule Calendar.ISO do
|
||||
{2000, 1, 1, 12, 0, 0, {0, 6}}
|
||||
|
||||
"""
|
||||
@spec naive_datetime_from_iso_days(Calendar.iso_days()) :: {
|
||||
Calendar.year(),
|
||||
Calendar.month(),
|
||||
Calendar.day(),
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
}
|
||||
@impl true
|
||||
@spec naive_datetime_from_iso_days(Calendar.iso_days) ::
|
||||
{Calendar.year, Calendar.month, Calendar.day,
|
||||
Calendar.hour, Calendar.minute, Calendar.second, Calendar.microsecond}
|
||||
def naive_datetime_from_iso_days({days, day_fraction}) do
|
||||
{year, month, day} = date_from_iso_days(days)
|
||||
{year, month, day} = date_from_iso_days_days(days)
|
||||
{hour, minute, second, microsecond} = time_from_day_fraction(day_fraction)
|
||||
{year, month, day, hour, minute, second, microsecond}
|
||||
end
|
||||
@@ -101,20 +83,14 @@ defmodule Calendar.ISO do
|
||||
{45296000123, 86400000000}
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@spec time_to_day_fraction(
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond()
|
||||
) :: Calendar.day_fraction()
|
||||
@spec time_to_day_fraction(Calendar.hour, Calendar.minute,
|
||||
Calendar.second, Calendar.microsecond) :: Calendar.day_fraction
|
||||
def time_to_day_fraction(0, 0, 0, {0, _}) do
|
||||
{0, @parts_per_day}
|
||||
{0, 86400000000}
|
||||
end
|
||||
|
||||
def time_to_day_fraction(hour, minute, second, {microsecond, _}) do
|
||||
combined_seconds = hour * @seconds_per_hour + minute * @seconds_per_minute + second
|
||||
{combined_seconds * @microseconds_per_second + microsecond, @parts_per_day}
|
||||
{combined_seconds * @microseconds_per_second + microsecond, @seconds_per_day * @microseconds_per_second}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -128,51 +104,37 @@ defmodule Calendar.ISO do
|
||||
{13, 0, 0, {0, 6}}
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@spec time_from_day_fraction(Calendar.day_fraction()) ::
|
||||
{Calendar.hour(), Calendar.minute(), Calendar.second(), Calendar.microsecond()}
|
||||
@spec time_from_day_fraction(Calendar.day_fraction) ::
|
||||
{Calendar.hour, Calendar.minute, Calendar.second, Calendar.microsecond}
|
||||
def time_from_day_fraction({parts_in_day, parts_per_day}) do
|
||||
total_microseconds = div(parts_in_day * @parts_per_day, parts_per_day)
|
||||
|
||||
{hours, rest_microseconds1} =
|
||||
div_mod(total_microseconds, @seconds_per_hour * @microseconds_per_second)
|
||||
|
||||
{minutes, rest_microseconds2} =
|
||||
div_mod(rest_microseconds1, @seconds_per_minute * @microseconds_per_second)
|
||||
|
||||
total_microseconds = div(parts_in_day * @seconds_per_day * @microseconds_per_second, parts_per_day)
|
||||
{hours, rest_microseconds1} = div_mod(total_microseconds, @seconds_per_hour * @microseconds_per_second)
|
||||
{minutes, rest_microseconds2} = div_mod(rest_microseconds1, @seconds_per_minute * @microseconds_per_second)
|
||||
{seconds, microseconds} = div_mod(rest_microseconds2, @microseconds_per_second)
|
||||
{hours, minutes, seconds, {microseconds, 6}}
|
||||
end
|
||||
|
||||
# Converts year, month, day to count of days since 0000-01-01.
|
||||
@doc false
|
||||
def date_to_iso_days(0, 1, 1) do
|
||||
def date_to_iso_days_days(0, 1, 1) do
|
||||
0
|
||||
end
|
||||
|
||||
def date_to_iso_days(1970, 1, 1) do
|
||||
719_528
|
||||
def date_to_iso_days_days(1970, 1, 1) do
|
||||
719528
|
||||
end
|
||||
|
||||
def date_to_iso_days(year, month, day) when year in 0..9999 do
|
||||
true = day <= days_in_month(year, month)
|
||||
|
||||
days_in_previous_years(year) + days_before_month(month) + leap_day_offset(year, month) + day -
|
||||
1
|
||||
def date_to_iso_days_days(year, month, day) do
|
||||
:calendar.date_to_gregorian_days(year, month, day)
|
||||
end
|
||||
|
||||
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
|
||||
@doc false
|
||||
def date_from_iso_days(days) when days in 0..3_652_424 do
|
||||
{year, day_of_year} = days_to_year(days)
|
||||
extra_day = if leap_year?(year), do: 1, else: 0
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{year, month, day_in_month + 1}
|
||||
def date_from_iso_days_days(days) do
|
||||
:calendar.gregorian_days_to_date(days)
|
||||
end
|
||||
|
||||
defp div_mod(int1, int2) do
|
||||
div = div(int1, int2)
|
||||
mod = int1 - div * int2
|
||||
mod = int1 - (div * int2)
|
||||
{div, mod}
|
||||
end
|
||||
|
||||
@@ -196,13 +158,11 @@ defmodule Calendar.ISO do
|
||||
|
||||
"""
|
||||
@spec days_in_month(year, month) :: 28..31
|
||||
@impl true
|
||||
def days_in_month(year, month)
|
||||
|
||||
def days_in_month(year, 2) do
|
||||
if leap_year?(year), do: 29, else: 28
|
||||
end
|
||||
|
||||
def days_in_month(_, month) when month in [4, 6, 9, 11], do: 30
|
||||
def days_in_month(_, month) when month in 1..12, do: 31
|
||||
|
||||
@@ -222,7 +182,6 @@ defmodule Calendar.ISO do
|
||||
|
||||
"""
|
||||
@spec leap_year?(year) :: boolean()
|
||||
@impl true
|
||||
def leap_year?(year) when is_integer(year) and year >= 0 do
|
||||
rem(year, 4) === 0 and (rem(year, 100) > 0 or rem(year, 400) === 0)
|
||||
end
|
||||
@@ -250,19 +209,15 @@ defmodule Calendar.ISO do
|
||||
7
|
||||
"""
|
||||
@spec day_of_week(year, month, day) :: 1..7
|
||||
@impl true
|
||||
def day_of_week(year, month, day)
|
||||
when is_integer(year) and is_integer(month) and is_integer(day) do
|
||||
Integer.mod(date_to_iso_days(year, month, day) + 5, 7) + 1
|
||||
:calendar.day_of_the_week(year, month, day)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given time into a string.
|
||||
"""
|
||||
@impl true
|
||||
def time_to_string(hour, minute, second, microsecond) do
|
||||
time_to_string(hour, minute, second, microsecond, :extended)
|
||||
end
|
||||
def time_to_string(hour, minute, second, microsecond, format \\ :extended)
|
||||
|
||||
def time_to_string(hour, minute, second, {_, 0}, format) do
|
||||
time_to_string_format(hour, minute, second, format)
|
||||
@@ -284,15 +239,11 @@ defmodule Calendar.ISO do
|
||||
@doc """
|
||||
Converts the given date into a string.
|
||||
"""
|
||||
@impl true
|
||||
def date_to_string(year, month, day) do
|
||||
date_to_string(year, month, day, :extended)
|
||||
end
|
||||
|
||||
defp date_to_string(year, month, day, :extended) do
|
||||
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
|
||||
end
|
||||
|
||||
defp date_to_string(year, month, day, :extended), do: date_to_string(year, month, day)
|
||||
defp date_to_string(year, month, day, :basic) do
|
||||
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
|
||||
end
|
||||
@@ -300,7 +251,6 @@ defmodule Calendar.ISO do
|
||||
@doc """
|
||||
Converts the datetime (without time zone) into a string.
|
||||
"""
|
||||
@impl true
|
||||
def naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) do
|
||||
date_to_string(year, month, day) <> " " <> time_to_string(hour, minute, second, microsecond)
|
||||
end
|
||||
@@ -308,46 +258,29 @@ defmodule Calendar.ISO do
|
||||
@doc """
|
||||
Convers the datetime (with time zone) into a string.
|
||||
"""
|
||||
@impl true
|
||||
def datetime_to_string(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset
|
||||
) do
|
||||
date_to_string(year, month, day) <>
|
||||
" " <>
|
||||
def datetime_to_string(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, zone_abbr, utc_offset, std_offset) do
|
||||
date_to_string(year, month, day) <> " " <>
|
||||
time_to_string(hour, minute, second, microsecond) <>
|
||||
offset_to_string(utc_offset, std_offset, time_zone) <>
|
||||
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def valid_date?(year, month, day) do
|
||||
month in 1..12 and year in 0..9999 and day in 1..days_in_month(year, month)
|
||||
year <= 9999 and :calendar.valid_date(year, month, day)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def valid_time?(hour, minute, second, {microsecond, precision}) do
|
||||
hour in 0..23 and minute in 0..59 and second in 0..60 and microsecond in 0..999_999 and
|
||||
precision in 0..6
|
||||
hour in 0..23 and minute in 0..59 and second in 0..60 and
|
||||
microsecond in 0..999_999 and precision in 0..6
|
||||
end
|
||||
|
||||
@impl true
|
||||
def day_rollover_relative_to_midnight_utc() do
|
||||
{0, 1}
|
||||
end
|
||||
|
||||
defp offset_to_string(utc, std, zone, format \\ :extended)
|
||||
defp offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
|
||||
|
||||
defp offset_to_string(utc, std, _zone, format) do
|
||||
total = utc + std
|
||||
second = abs(total)
|
||||
@@ -380,26 +313,25 @@ defmodule Calendar.ISO do
|
||||
@doc false
|
||||
def from_unix(integer, unit) when is_integer(integer) do
|
||||
total = System.convert_time_unit(integer, unit, :microsecond)
|
||||
|
||||
if total in @unix_range_microseconds do
|
||||
microseconds = Integer.mod(total, @microseconds_per_second)
|
||||
seconds = @unix_epoch + Integer.floor_div(total, @microseconds_per_second)
|
||||
microsecond = rem(total, 1_000_000)
|
||||
precision = precision_for_unit(unit)
|
||||
{date, time} = iso_seconds_to_datetime(seconds)
|
||||
{:ok, date, time, {microseconds, precision}}
|
||||
{date, time} = :calendar.gregorian_seconds_to_datetime(@unix_epoch + div(total, 1_000_000))
|
||||
{:ok, date, time, {microsecond, precision}}
|
||||
else
|
||||
{:error, :invalid_unix_time}
|
||||
end
|
||||
end
|
||||
|
||||
defp precision_for_unit(unit) do
|
||||
subsecond = div(System.convert_time_unit(1, :second, unit), 10)
|
||||
subsecond = div System.convert_time_unit(1, :second, unit), 10
|
||||
precision_for_unit(subsecond, 0)
|
||||
end
|
||||
|
||||
defp precision_for_unit(0, precision), do: precision
|
||||
defp precision_for_unit(_, 6), do: 6
|
||||
|
||||
defp precision_for_unit(0, precision),
|
||||
do: precision
|
||||
defp precision_for_unit(_, 6),
|
||||
do: 6
|
||||
defp precision_for_unit(number, precision),
|
||||
do: precision_for_unit(div(number, 10), precision + 1)
|
||||
|
||||
@@ -414,37 +346,14 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def naive_datetime_to_iso8601(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format \\ :extended
|
||||
) do
|
||||
date_to_string(year, month, day, format) <>
|
||||
"T" <> time_to_string(hour, minute, second, microsecond, format)
|
||||
def naive_datetime_to_iso8601(year, month, day, hour, minute, second, microsecond, format \\ :extended) do
|
||||
date_to_string(year, month, day, format) <> "T" <> time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def datetime_to_iso8601(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
_zone_abbr,
|
||||
utc_offset,
|
||||
std_offset,
|
||||
format \\ :extended
|
||||
) do
|
||||
date_to_string(year, month, day, format) <>
|
||||
"T" <>
|
||||
def datetime_to_iso8601(year, month, day, hour, minute, second, microsecond,
|
||||
time_zone, _zone_abbr, utc_offset, std_offset, format \\ :extended) do
|
||||
date_to_string(year, month, day, format) <> "T" <>
|
||||
time_to_string(hour, minute, second, microsecond, format) <>
|
||||
offset_to_string(utc_offset, std_offset, time_zone, format)
|
||||
end
|
||||
@@ -454,11 +363,9 @@ defmodule Calendar.ISO do
|
||||
case parse_microsecond(rest, 0, "") do
|
||||
{"", 0, _} ->
|
||||
:error
|
||||
|
||||
{microsecond, precision, rest} when precision in 1..6 ->
|
||||
pad = String.duplicate("0", 6 - byte_size(microsecond))
|
||||
{{String.to_integer(microsecond <> pad), precision}, rest}
|
||||
|
||||
{microsecond, _precision, rest} ->
|
||||
{{String.to_integer(binary_part(microsecond, 0, 6)), 6}, rest}
|
||||
end
|
||||
@@ -474,34 +381,35 @@ defmodule Calendar.ISO do
|
||||
|
||||
defp parse_microsecond(<<head, tail::binary>>, precision, acc) when head in ?0..?9,
|
||||
do: parse_microsecond(tail, precision + 1, <<acc::binary, head>>)
|
||||
|
||||
defp parse_microsecond(rest, precision, acc), do: {acc, precision, rest}
|
||||
defp parse_microsecond(rest, precision, acc),
|
||||
do: {acc, precision, rest}
|
||||
|
||||
@doc false
|
||||
def parse_offset(""), do: {nil, ""}
|
||||
def parse_offset("Z"), do: {0, ""}
|
||||
def parse_offset("-00:00"), do: :error
|
||||
|
||||
def parse_offset(""),
|
||||
do: {nil, ""}
|
||||
def parse_offset("Z"),
|
||||
do: {0, ""}
|
||||
def parse_offset("-00:00"),
|
||||
do: :error
|
||||
def parse_offset(<<?+, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(1, hour, min, rest)
|
||||
|
||||
def parse_offset(<<?-, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(-1, hour, min, rest)
|
||||
|
||||
def parse_offset(<<?+, hour::2-bytes, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(1, hour, min, rest)
|
||||
|
||||
def parse_offset(<<?-, hour::2-bytes, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(-1, hour, min, rest)
|
||||
|
||||
def parse_offset(<<?+, hour::2-bytes, rest::binary>>), do: parse_offset(1, hour, "00", rest)
|
||||
def parse_offset(<<?-, hour::2-bytes, rest::binary>>), do: parse_offset(-1, hour, "00", rest)
|
||||
def parse_offset(_), do: :error
|
||||
def parse_offset(<<?+, hour::2-bytes, rest::binary>>),
|
||||
do: parse_offset(1, hour, "00", rest)
|
||||
def parse_offset(<<?-, hour::2-bytes, rest::binary>>),
|
||||
do: parse_offset(-1, hour, "00", rest)
|
||||
def parse_offset(_),
|
||||
do: :error
|
||||
|
||||
defp parse_offset(sign, hour, min, rest) do
|
||||
with {hour, ""} when hour < 24 <- Integer.parse(hour),
|
||||
{min, ""} when min < 60 <- Integer.parse(min) do
|
||||
{(hour * 60 + min) * 60 * sign, rest}
|
||||
{((hour * 60) + min) * 60 * sign, rest}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
@@ -509,8 +417,8 @@ defmodule Calendar.ISO do
|
||||
|
||||
@doc false
|
||||
def iso_days_to_unit({days, {parts, ppd}}, unit) do
|
||||
day_microseconds = days * @parts_per_day
|
||||
microseconds = div(parts * @parts_per_day, ppd)
|
||||
day_microseconds = days * @seconds_per_day * @microseconds_per_second
|
||||
microseconds = div(parts * @seconds_per_day * @microseconds_per_second, ppd)
|
||||
System.convert_time_unit(day_microseconds + microseconds, :microsecond, unit)
|
||||
end
|
||||
|
||||
@@ -518,7 +426,6 @@ defmodule Calendar.ISO do
|
||||
def add_day_fraction_to_iso_days({days, {parts, ppd}}, add, ppd) do
|
||||
normalize_iso_days(days, parts + add, ppd)
|
||||
end
|
||||
|
||||
def add_day_fraction_to_iso_days({days, {parts, ppd}}, add, add_ppd) do
|
||||
parts = parts * add_ppd
|
||||
add = add * ppd
|
||||
@@ -531,121 +438,10 @@ defmodule Calendar.ISO do
|
||||
defp normalize_iso_days(days, parts, ppd) do
|
||||
days_offset = div(parts, ppd)
|
||||
parts = rem(parts, ppd)
|
||||
|
||||
if parts < 0 do
|
||||
{days + days_offset - 1, {parts + ppd, ppd}}
|
||||
else
|
||||
{days + days_offset, {parts, ppd}}
|
||||
end
|
||||
end
|
||||
|
||||
# Note that this function does not add the extra leap day for a leap year.
|
||||
# If you want to add that leap day when appropriate,
|
||||
# add the result of leap_day_offset/2 to the result of days_before_month/1.
|
||||
defp days_before_month(1), do: 0
|
||||
defp days_before_month(2), do: 31
|
||||
defp days_before_month(3), do: 59
|
||||
defp days_before_month(4), do: 90
|
||||
defp days_before_month(5), do: 120
|
||||
defp days_before_month(6), do: 151
|
||||
defp days_before_month(7), do: 181
|
||||
defp days_before_month(8), do: 212
|
||||
defp days_before_month(9), do: 243
|
||||
defp days_before_month(10), do: 273
|
||||
defp days_before_month(11), do: 304
|
||||
defp days_before_month(12), do: 334
|
||||
|
||||
defp leap_day_offset(_year, month) when month < 3, do: 0
|
||||
|
||||
defp leap_day_offset(year, _month) do
|
||||
if leap_year?(year), do: 1, else: 0
|
||||
end
|
||||
|
||||
defp days_to_year(days) do
|
||||
year = Integer.floor_div(days, @days_per_nonleap_year)
|
||||
{year, days_before_year} = days_to_year(year, days, days_in_previous_years(year))
|
||||
{year, days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when days1 < days2 do
|
||||
days_to_year(year - 1, days1, days_in_previous_years(year - 1))
|
||||
end
|
||||
|
||||
defp days_to_year(year, _days1, days2) do
|
||||
{year, days2}
|
||||
end
|
||||
|
||||
defp days_in_previous_years(0), do: 0
|
||||
|
||||
defp days_in_previous_years(year) do
|
||||
previous_year = year - 1
|
||||
|
||||
Integer.floor_div(previous_year, 4) - Integer.floor_div(previous_year, 100) +
|
||||
Integer.floor_div(previous_year, 400) + previous_year * @days_per_nonleap_year +
|
||||
@days_per_leap_year
|
||||
end
|
||||
|
||||
# Note that 0 is the first day of the month.
|
||||
defp year_day_to_year_date(_extra_day, day_of_year) when day_of_year < 31 do
|
||||
{1, day_of_year}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 59 + extra_day do
|
||||
{2, day_of_year - 31}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 90 + extra_day do
|
||||
{3, day_of_year - (59 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 120 + extra_day do
|
||||
{4, day_of_year - (90 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 151 + extra_day do
|
||||
{5, day_of_year - (120 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 181 + extra_day do
|
||||
{6, day_of_year - (151 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 212 + extra_day do
|
||||
{7, day_of_year - (181 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 243 + extra_day do
|
||||
{8, day_of_year - (212 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 273 + extra_day do
|
||||
{9, day_of_year - (243 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 304 + extra_day do
|
||||
{10, day_of_year - (273 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 334 + extra_day do
|
||||
{11, day_of_year - (304 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) do
|
||||
{12, day_of_year - (334 + extra_day)}
|
||||
end
|
||||
|
||||
defp iso_seconds_to_datetime(seconds) do
|
||||
{days, rest_seconds} = div_mod(seconds, @seconds_per_day)
|
||||
|
||||
date = date_from_iso_days(days)
|
||||
time = seconds_to_time(rest_seconds)
|
||||
{date, time}
|
||||
end
|
||||
|
||||
defp seconds_to_time(seconds) when seconds in 0..(@seconds_per_day - 1) do
|
||||
{hour, rest_seconds} = div_mod(seconds, @seconds_per_hour)
|
||||
{minute, second} = div_mod(rest_seconds, @seconds_per_minute)
|
||||
|
||||
{hour, minute, second}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,8 +45,8 @@ defmodule NaiveDateTime do
|
||||
## Using epochs
|
||||
|
||||
The `add/3` and `diff/3` functions can be used for computing with
|
||||
date times or retrieving the number of seconds between instants.
|
||||
For example, if there is an interest in computing the number of
|
||||
date times or retrieving the amount of seconds betweens instants.
|
||||
For example, if there is an interest in computing the amount of
|
||||
seconds from the Unix epoch (1970-01-01 00:00:00):
|
||||
|
||||
iex> NaiveDateTime.diff(~N[2010-04-17 14:00:00], ~N[1970-01-01 00:00:00])
|
||||
@@ -60,27 +60,11 @@ defmodule NaiveDateTime do
|
||||
"""
|
||||
|
||||
@enforce_keys [:year, :month, :day, :hour, :minute, :second]
|
||||
defstruct [
|
||||
:year,
|
||||
:month,
|
||||
:day,
|
||||
:hour,
|
||||
:minute,
|
||||
:second,
|
||||
microsecond: {0, 0},
|
||||
calendar: Calendar.ISO
|
||||
]
|
||||
defstruct [:year, :month, :day, :hour, :minute, :second, microsecond: {0, 0}, calendar: Calendar.ISO]
|
||||
|
||||
@type t :: %NaiveDateTime{
|
||||
year: Calendar.year(),
|
||||
month: Calendar.month(),
|
||||
day: Calendar.day(),
|
||||
calendar: Calendar.calendar(),
|
||||
hour: Calendar.hour(),
|
||||
minute: Calendar.minute(),
|
||||
second: Calendar.second(),
|
||||
microsecond: Calendar.microsecond()
|
||||
}
|
||||
@type t :: %NaiveDateTime{year: Calendar.year, month: Calendar.month, day: Calendar.day,
|
||||
calendar: Calendar.calendar, hour: Calendar.hour, minute: Calendar.minute,
|
||||
second: Calendar.second, microsecond: Calendar.microsecond}
|
||||
|
||||
@doc """
|
||||
Returns the current naive datetime in UTC.
|
||||
@@ -95,29 +79,21 @@ defmodule NaiveDateTime do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
@spec utc_now(Calendar.calendar) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO)
|
||||
|
||||
def utc_now(Calendar.ISO) do
|
||||
{:ok, {year, month, day}, {hour, minute, second}, microsecond} =
|
||||
Calendar.ISO.from_unix(:os.system_time(), :native)
|
||||
|
||||
%NaiveDateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: Calendar.ISO
|
||||
}
|
||||
Calendar.ISO.from_unix(:os.system_time, :native)
|
||||
%NaiveDateTime{year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second,
|
||||
microsecond: microsecond, calendar: Calendar.ISO}
|
||||
end
|
||||
|
||||
def utc_now(calendar) do
|
||||
calendar
|
||||
|> DateTime.utc_now()
|
||||
|> DateTime.to_naive()
|
||||
|> DateTime.utc_now
|
||||
|> DateTime.to_naive
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -156,16 +132,9 @@ defmodule NaiveDateTime do
|
||||
{:error, :invalid_time}
|
||||
|
||||
"""
|
||||
@spec new(
|
||||
Calendar.year(),
|
||||
Calendar.month(),
|
||||
Calendar.day(),
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond(),
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
@spec new(Calendar.year, Calendar.month, Calendar.day,
|
||||
Calendar.hour, Calendar.minute, Calendar.second, 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),
|
||||
@@ -181,25 +150,13 @@ defmodule NaiveDateTime do
|
||||
{:ok, ~N[2010-01-13 23:00:07.005]}
|
||||
|
||||
"""
|
||||
@spec new(Date.t(), Time.t()) :: {:ok, t}
|
||||
@spec new(Date.t, Time.t) :: {:ok, t}
|
||||
def new(date, time)
|
||||
|
||||
def new(%Date{calendar: calendar} = date, %Time{calendar: calendar} = time) do
|
||||
%{year: year, month: month, day: day} = date
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
|
||||
|
||||
naive_datetime = %NaiveDateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
|
||||
{:ok, naive_datetime}
|
||||
def new(%Date{calendar: calendar, year: year, month: month, day: day},
|
||||
%Time{calendar: calendar, hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
{:ok, %NaiveDateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -238,12 +195,10 @@ defmodule NaiveDateTime do
|
||||
~N[2014-10-02 00:29:10]
|
||||
|
||||
"""
|
||||
@spec add(t, integer, System.time_unit()) :: t
|
||||
def add(%NaiveDateTime{} = naive_datetime, integer, unit \\ :second)
|
||||
when is_integer(integer) do
|
||||
%{microsecond: {_, precision}, calendar: calendar} = naive_datetime
|
||||
@spec add(t, integer, System.time_unit) :: t
|
||||
def add(%NaiveDateTime{microsecond: {_, precision}, calendar: calendar} = naive_datetime,
|
||||
integer, unit \\ :second) when is_integer(integer) do
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
|
||||
naive_datetime
|
||||
|> to_iso_days()
|
||||
|> Calendar.ISO.add_day_fraction_to_iso_days(integer, ppd)
|
||||
@@ -274,41 +229,19 @@ defmodule NaiveDateTime do
|
||||
63579428950
|
||||
|
||||
"""
|
||||
@spec diff(t, t, System.time_unit()) :: integer
|
||||
def diff(%NaiveDateTime{} = ndatetime1, %NaiveDateTime{} = ndatetime2, unit \\ :second) do
|
||||
if not Calendar.compatible_calendars?(ndatetime1.calendar, ndatetime2.calendar) do
|
||||
raise ArgumentError,
|
||||
"cannot calculate the difference between #{inspect(ndatetime1)} and " <>
|
||||
"#{inspect(ndatetime2)} because their calendars are not compatible " <>
|
||||
"and thus the result would be ambiguous"
|
||||
@spec diff(t, t, System.time_unit) :: integer
|
||||
def diff(%NaiveDateTime{} = naive_datetime1,
|
||||
%NaiveDateTime{} = naive_datetime2,
|
||||
unit \\ :second) do
|
||||
if not Calendar.compatible_calendars?(naive_datetime1.calendar, naive_datetime2.calendar) do
|
||||
raise ArgumentError, "cannot calculate the difference between #{inspect naive_datetime1} and #{inspect naive_datetime2} because their calendars are not compatible and thus the result would be ambiguous"
|
||||
end
|
||||
|
||||
units1 = ndatetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units2 = ndatetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units1 = naive_datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units2 = naive_datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
|
||||
units1 - units2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given naive datetime with the microsecond field truncated to the
|
||||
given precision (`:microsecond`, `millisecond` or `:second`).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :microsecond)
|
||||
~N[2017-11-06 00:23:51.123456]
|
||||
|
||||
iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :millisecond)
|
||||
~N[2017-11-06 00:23:51.123]
|
||||
|
||||
iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :second)
|
||||
~N[2017-11-06 00:23:51]
|
||||
|
||||
"""
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%NaiveDateTime{microsecond: microsecond} = ndatetime, precision) do
|
||||
%{ndatetime | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `NaiveDateTime` into a `Date`.
|
||||
|
||||
@@ -321,7 +254,7 @@ defmodule NaiveDateTime do
|
||||
~D[2002-01-13]
|
||||
|
||||
"""
|
||||
@spec to_date(t) :: Date.t()
|
||||
@spec to_date(t) :: Date.t
|
||||
def to_date(%NaiveDateTime{year: year, month: month, day: day, calendar: calendar}) do
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
@@ -338,23 +271,9 @@ defmodule NaiveDateTime do
|
||||
~T[23:00:07]
|
||||
|
||||
"""
|
||||
@spec to_time(t) :: Time.t()
|
||||
def to_time(%NaiveDateTime{} = naive_datetime) do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
} = naive_datetime
|
||||
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
}
|
||||
@spec to_time(t) :: Time.t
|
||||
def to_time(%NaiveDateTime{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}) do
|
||||
%Time{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -377,18 +296,11 @@ defmodule NaiveDateTime do
|
||||
"2000-02-29 23:00:07"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.naive_datetime()) :: String.t()
|
||||
def to_string(%{calendar: calendar} = naive_datetime) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = naive_datetime
|
||||
@spec to_string(Calendar.naive_datetime) :: String.t
|
||||
def to_string(naive_datetime)
|
||||
|
||||
def to_string(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
calendar.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
@@ -405,7 +317,7 @@ defmodule NaiveDateTime do
|
||||
|
||||
Time representations with reduced accuracy are not supported.
|
||||
|
||||
Note that while ISO 8601 allows datetimes to specify 24:00:00 as the
|
||||
Note that while ISO8601 allows datetimes to specify 24:00:00 as the
|
||||
zero hour of the next day, this notation is not supported by Elixir.
|
||||
|
||||
## Examples
|
||||
@@ -451,12 +363,12 @@ defmodule NaiveDateTime do
|
||||
{:error, :invalid_format}
|
||||
|
||||
"""
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {: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),
|
||||
@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, sep,
|
||||
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>, calendar) when sep in [?\s, ?T] do
|
||||
with {year, ""} <- Integer.parse(year),
|
||||
{month, ""} <- Integer.parse(month),
|
||||
{day, ""} <- Integer.parse(day),
|
||||
{hour, ""} <- Integer.parse(hour),
|
||||
@@ -471,6 +383,10 @@ defmodule NaiveDateTime do
|
||||
end
|
||||
end
|
||||
|
||||
def from_iso8601(<<_::binary>>, _calendar) do
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the extended "Date and time of day" format described by
|
||||
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
@@ -487,15 +403,13 @@ defmodule NaiveDateTime do
|
||||
** (ArgumentError) cannot parse "2015-01-23P23:50:07" as naive datetime, reason: :invalid_format
|
||||
|
||||
"""
|
||||
@spec from_iso8601!(String.t(), Calendar.calendar()) :: t | no_return
|
||||
@spec from_iso8601!(String.t, Calendar.calendar) :: t | no_return
|
||||
def from_iso8601!(string, calendar \\ Calendar.ISO) do
|
||||
case from_iso8601(string, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot parse #{inspect(string)} as naive datetime, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot parse #{inspect string} as naive datetime, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -520,7 +434,7 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.to_iso8601(~N[2000-02-28 23:00:13.001], :basic)
|
||||
"20000228T230013.001"
|
||||
|
||||
This function can also be used to convert a DateTime to ISO 8601 without
|
||||
This function can also be used to convert a DateTime to ISO8601 without
|
||||
the time zone information:
|
||||
|
||||
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
|
||||
@@ -530,43 +444,24 @@ defmodule NaiveDateTime do
|
||||
"2000-02-29T23:00:07"
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.naive_datetime(), :basic | :extended) :: String.t()
|
||||
@spec to_iso8601(Calendar.naive_datetime, :basic | :extended) :: String.t
|
||||
def to_iso8601(naive_datetime, format \\ :extended)
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = naive_datetime, format)
|
||||
when format in [:basic, :extended] do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = naive_datetime
|
||||
|
||||
Calendar.ISO.naive_datetime_to_iso8601(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format
|
||||
)
|
||||
def to_iso8601(%{year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond, calendar:
|
||||
Calendar.ISO}, format) when format in [:basic, :extended] do
|
||||
Calendar.ISO.naive_datetime_to_iso8601(year, month, day, hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = naive_datetime, format) when format in [:basic, :extended] do
|
||||
def to_iso8601(%{year: _, month: _, day: _,
|
||||
hour: _, minute: _, second: _, microsecond: _, calendar: _} = naive_datetime, format) when format in [:basic, :extended] do
|
||||
naive_datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
def to_iso8601(_date, format) do
|
||||
raise ArgumentError,
|
||||
"NaiveDateTime.to_iso8601/2 expects format to be :extended or :basic, " <>
|
||||
"got: #{inspect(format)}"
|
||||
raise ArgumentError, "NaiveDateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect format}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -593,11 +488,14 @@ defmodule NaiveDateTime do
|
||||
{{2000, 2, 29}, {23, 00, 07}}
|
||||
|
||||
"""
|
||||
@spec to_erl(Calendar.naive_datetime()) :: :calendar.datetime()
|
||||
def to_erl(%{calendar: _} = naive_datetime) do
|
||||
%{year: year, month: month, day: day, hour: hour, minute: minute, second: second} =
|
||||
convert!(naive_datetime, Calendar.ISO)
|
||||
@spec to_erl(t) :: :calendar.datetime
|
||||
def to_erl(naive_datetime)
|
||||
|
||||
@spec to_erl(Calendar.time) :: :calendar.time
|
||||
def to_erl(%{calendar: _, year: _, month: _, day: _,
|
||||
hour: _, minute: _, second: _} = naive_datetime) do
|
||||
%{year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second} = convert!(naive_datetime, Calendar.ISO)
|
||||
{{year, month, day}, {hour, minute, second}}
|
||||
end
|
||||
|
||||
@@ -618,8 +516,7 @@ defmodule NaiveDateTime do
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@spec from_erl(:calendar.datetime(), Calendar.microsecond(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
@spec from_erl(:calendar.datetime, Calendar.microsecond) :: {:ok, t} | {:error, atom}
|
||||
def from_erl(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_erl({{year, month, day}, {hour, minute, second}}, microsecond, calendar) do
|
||||
@@ -627,7 +524,7 @@ defmodule NaiveDateTime do
|
||||
do: convert(utc_date, calendar)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc """
|
||||
Converts an Erlang datetime tuple to a `NaiveDateTime` struct.
|
||||
|
||||
Raises if the datetime is invalid.
|
||||
@@ -643,16 +540,13 @@ defmodule NaiveDateTime do
|
||||
** (ArgumentError) cannot convert {{2000, 13, 1}, {13, 30, 15}} to naive datetime, reason: :invalid_date
|
||||
|
||||
"""
|
||||
@spec from_erl!(:calendar.datetime(), Calendar.microsecond(), Calendar.calendar()) ::
|
||||
t | no_return
|
||||
def from_erl!(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO) do
|
||||
case from_erl(tuple, microsecond, calendar) do
|
||||
@spec from_erl!(:calendar.datetime, Calendar.microsecond) :: t | no_return
|
||||
def from_erl!(tuple, microsecond \\ {0, 0}) do
|
||||
case from_erl(tuple, microsecond) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(tuple)} to naive datetime, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot convert #{inspect tuple} to naive datetime, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -684,7 +578,7 @@ defmodule NaiveDateTime do
|
||||
:lt
|
||||
|
||||
"""
|
||||
@spec compare(Calendar.naive_datetime(), Calendar.naive_datetime()) :: :lt | :eq | :gt
|
||||
@spec compare(Calendar.naive_datetime, Calendar.naive_datetime) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar1} = naive_datetime1, %{calendar: calendar2} = naive_datetime2) do
|
||||
if Calendar.compatible_calendars?(calendar1, calendar2) do
|
||||
case {to_iso_days(naive_datetime1), to_iso_days(naive_datetime2)} do
|
||||
@@ -694,7 +588,7 @@ defmodule NaiveDateTime do
|
||||
end
|
||||
else
|
||||
raise ArgumentError, """
|
||||
cannot compare #{inspect(naive_datetime1)} with #{inspect(naive_datetime2)}.
|
||||
cannot compare #{inspect naive_datetime1} with #{inspect naive_datetime2}.
|
||||
|
||||
This comparison would be ambiguous as their calendars have incompatible day rollover moments.
|
||||
Specify an exact time of day (using `DateTime`s) to resolve this ambiguity
|
||||
@@ -720,35 +614,11 @@ defmodule NaiveDateTime do
|
||||
hour: 13, minute: 30, second: 15, microsecond: {0, 0}}}
|
||||
|
||||
"""
|
||||
@spec convert(Calendar.naive_datetime(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
def convert(
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
},
|
||||
calendar
|
||||
) do
|
||||
naive_datetime = %NaiveDateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
|
||||
{:ok, naive_datetime}
|
||||
@spec convert(Calendar.naive_datetime, Calendar.calendar) :: {:ok, t} | {:error, :incompatible_calendars}
|
||||
def convert(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}, calendar) do
|
||||
{:ok, %NaiveDateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}}
|
||||
end
|
||||
|
||||
def convert(%{calendar: ndt_calendar, microsecond: {_, precision}} = naive_datetime, calendar) do
|
||||
@@ -757,7 +627,6 @@ defmodule NaiveDateTime do
|
||||
naive_datetime
|
||||
|> to_iso_days
|
||||
|> from_iso_days(calendar, precision)
|
||||
|
||||
{:ok, result_naive_datetime}
|
||||
else
|
||||
{:error, :incompatible_calendars}
|
||||
@@ -781,84 +650,41 @@ defmodule NaiveDateTime do
|
||||
hour: 13, minute: 30, second: 15, microsecond: {0, 0}}
|
||||
|
||||
"""
|
||||
@spec convert!(Calendar.naive_datetime(), Calendar.calendar()) :: t
|
||||
@spec convert!(Calendar.naive_datetime, Calendar.calendar) :: t
|
||||
def convert!(naive_datetime, calendar) do
|
||||
case convert(naive_datetime, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, :incompatible_calendars} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(naive_datetime)} to target calendar #{inspect(calendar)}, " <>
|
||||
"reason: #{inspect(naive_datetime.calendar)} and #{inspect(calendar)} " <>
|
||||
"have different day rollover moments, making this conversion ambiguous"
|
||||
raise ArgumentError, "cannot convert #{inspect naive_datetime} to target calendar #{inspect calendar}, reason: #{inspect naive_datetime.calendar} and #{inspect calendar} have different day rollover moments, making this conversion ambiguous"
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
defp to_iso_days(%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}) do
|
||||
defp to_iso_days(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
calendar.naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
defp from_iso_days(iso_days, calendar, precision) do
|
||||
{year, month, day, hour, minute, second, {microsecond, _}} =
|
||||
calendar.naive_datetime_from_iso_days(iso_days)
|
||||
|
||||
%NaiveDateTime{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision}
|
||||
}
|
||||
%NaiveDateTime{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: {microsecond, precision}}
|
||||
end
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(naive_datetime) do
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = naive_datetime
|
||||
|
||||
def to_string(%{calendar: calendar, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}) do
|
||||
calendar.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect do
|
||||
def inspect(%{calendar: Calendar.ISO} = naive_datetime, _) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = naive_datetime
|
||||
|
||||
formatted =
|
||||
Calendar.ISO.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
|
||||
def inspect(%{calendar: Calendar.ISO, year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second, microsecond: microsecond}, _) do
|
||||
formatted = Calendar.ISO.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
"~N[" <> formatted <> "]"
|
||||
end
|
||||
|
||||
|
||||
+38
-200
@@ -37,13 +37,8 @@ defmodule Time do
|
||||
@enforce_keys [:hour, :minute, :second]
|
||||
defstruct [:hour, :minute, :second, microsecond: {0, 0}, calendar: Calendar.ISO]
|
||||
|
||||
@type t :: %Time{
|
||||
hour: Calendar.hour(),
|
||||
minute: Calendar.minute(),
|
||||
second: Calendar.second(),
|
||||
microsecond: Calendar.microsecond(),
|
||||
calendar: Calendar.calendar()
|
||||
}
|
||||
@type t :: %Time{hour: Calendar.hour, minute: Calendar.minute,
|
||||
second: Calendar.second, microsecond: Calendar.microsecond, calendar: Calendar.calendar}
|
||||
|
||||
@doc """
|
||||
Returns the current time in UTC.
|
||||
@@ -55,19 +50,10 @@ defmodule Time do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
@spec utc_now(Calendar.calendar) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO) do
|
||||
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(:os.system_time(), :native)
|
||||
{hour, minute, second} = time
|
||||
|
||||
iso_time = %Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: Calendar.ISO
|
||||
}
|
||||
|
||||
{:ok, _, {hour, minute, second}, microsecond} = Calendar.ISO.from_unix(:os.system_time, :native)
|
||||
iso_time = %Time{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: Calendar.ISO}
|
||||
convert!(iso_time, calendar)
|
||||
end
|
||||
|
||||
@@ -108,13 +94,8 @@ defmodule Time do
|
||||
{:error, :invalid_time}
|
||||
|
||||
"""
|
||||
@spec new(
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond() | integer,
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
@spec new(Calendar.hour, Calendar.minute, Calendar.second, Calendar.microsecond, Calendar.calendar) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
def new(hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
def new(hour, minute, second, microsecond, calendar) when is_integer(microsecond) do
|
||||
@@ -123,19 +104,10 @@ defmodule Time do
|
||||
|
||||
def new(hour, minute, second, {microsecond, precision}, calendar)
|
||||
when is_integer(hour) and is_integer(minute) and is_integer(second) and
|
||||
is_integer(microsecond) and is_integer(precision) do
|
||||
is_integer(microsecond) and is_integer(precision) do
|
||||
case calendar.valid_time?(hour, minute, second, {microsecond, precision}) do
|
||||
true ->
|
||||
time = %Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision},
|
||||
calendar: calendar
|
||||
}
|
||||
|
||||
{:ok, time}
|
||||
|
||||
{:ok, %Time{hour: hour, minute: minute, second: second, microsecond: {microsecond, precision}, calendar: calendar}}
|
||||
false ->
|
||||
{:error, :invalid_time}
|
||||
end
|
||||
@@ -159,16 +131,10 @@ defmodule Time do
|
||||
"23:00:00.123456"
|
||||
|
||||
"""
|
||||
@spec to_string(Calendar.time()) :: String.t()
|
||||
@spec to_string(Calendar.time) :: String.t
|
||||
def to_string(time)
|
||||
|
||||
def to_string(%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
}) do
|
||||
def to_string(%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}) do
|
||||
calendar.time_to_string(hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
@@ -184,7 +150,7 @@ defmodule Time do
|
||||
|
||||
Time representations with reduced accuracy are not supported.
|
||||
|
||||
Note that while ISO 8601 allows times to specify 24:00:00 as the
|
||||
Note that while ISO8601 allows times to specify 24:00:00 as the
|
||||
zero hour of the next day, this notation is not supported by Elixir.
|
||||
|
||||
## Examples
|
||||
@@ -213,7 +179,7 @@ defmodule Time do
|
||||
{:error, :invalid_time}
|
||||
|
||||
"""
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
@spec from_iso8601(String.t) :: {: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
|
||||
@@ -252,14 +218,13 @@ defmodule Time do
|
||||
iex> Time.from_iso8601!("2015:01:23 23-50-07")
|
||||
** (ArgumentError) cannot parse "2015:01:23 23-50-07" as time, reason: :invalid_format
|
||||
"""
|
||||
@spec from_iso8601!(String.t(), Calendar.calendar()) :: t
|
||||
def from_iso8601!(string, calendar \\ Calendar.ISO) do
|
||||
case from_iso8601(string, calendar) do
|
||||
@spec from_iso8601!(String.t) :: t
|
||||
def from_iso8601!(string) do
|
||||
case from_iso8601(string) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError, "cannot parse #{inspect(string)} as time, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot parse #{inspect string} as time, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -286,15 +251,9 @@ defmodule Time do
|
||||
"23:00:13"
|
||||
|
||||
"""
|
||||
@spec to_iso8601(Calendar.time(), :extended | :basic) :: String.t()
|
||||
@spec to_iso8601(Calendar.time, :extended | :basic) :: String.t
|
||||
def to_iso8601(time, format \\ :extended) when format in [:extended, :basic] do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = convert!(time, Calendar.ISO)
|
||||
|
||||
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = convert!(time, Calendar.ISO)
|
||||
Calendar.ISO.time_to_iso8601(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
@@ -313,7 +272,7 @@ defmodule Time do
|
||||
{23, 30, 15}
|
||||
|
||||
"""
|
||||
@spec to_erl(Calendar.time()) :: :calendar.time()
|
||||
@spec to_erl(Calendar.time) :: :calendar.time
|
||||
def to_erl(time) do
|
||||
%{hour: hour, minute: minute, second: second} = convert!(time, Calendar.ISO)
|
||||
{hour, minute, second}
|
||||
@@ -330,8 +289,7 @@ defmodule Time do
|
||||
{:error, :invalid_time}
|
||||
|
||||
"""
|
||||
@spec from_erl(:calendar.time(), Calendar.microsecond(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
@spec from_erl(:calendar.time, Calendar.microsecond, Calendar.calendar) :: {:ok, t} | {:error, atom}
|
||||
def from_erl(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_erl({hour, minute, second}, microsecond, calendar) do
|
||||
@@ -352,60 +310,16 @@ defmodule Time do
|
||||
** (ArgumentError) cannot convert {24, 30, 15} to time, reason: :invalid_time
|
||||
|
||||
"""
|
||||
@spec from_erl!(:calendar.time(), Calendar.microsecond(), Calendar.calendar()) :: t
|
||||
@spec from_erl!(:calendar.time, Calendar.microsecond, Calendar.calendar) :: t
|
||||
def from_erl!(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO) do
|
||||
case from_erl(tuple, microsecond, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(tuple)} to time, reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot convert #{inspect tuple} to time, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds the `number` of `unit`s to the given `time`.
|
||||
|
||||
This function accepts the `number` measured according to `Calendar.ISO`.
|
||||
The time is returned in the same calendar as it was given in.
|
||||
|
||||
Note the result value represents the time of day, meaning that it is cyclic,
|
||||
for instance, it will never go over 24 hours for the ISO calendar.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Time.add(~T[10:00:00], 27000)
|
||||
~T[17:30:00.000000]
|
||||
iex> Time.add(~T[11:00:00.005], 2400)
|
||||
~T[11:40:00.005000]
|
||||
iex> Time.add(~T[00:00:00], 86399999, :millisecond)
|
||||
~T[23:59:59.999000]
|
||||
iex> Time.add(~T[17:10:05], 86400)
|
||||
~T[17:10:05.000000]
|
||||
iex> Time.add(~T[23:00:00], -60)
|
||||
~T[22:59:00.000000]
|
||||
|
||||
"""
|
||||
@spec add(Calendar.time(), integer, System.time_unit()) :: t
|
||||
def add(%{calendar: calendar} = time, number, unit \\ :second) when is_integer(number) do
|
||||
number = System.convert_time_unit(number, unit, :microsecond)
|
||||
iso_days = {0, to_day_fraction(time)}
|
||||
total = Calendar.ISO.iso_days_to_unit(iso_days, :microsecond) + number
|
||||
iso_ppd = 86_400_000_000
|
||||
parts = Integer.mod(total, iso_ppd)
|
||||
|
||||
{hour, minute, second, microsecond} = calendar.time_from_day_fraction({parts, iso_ppd})
|
||||
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compares two time structs.
|
||||
|
||||
@@ -433,11 +347,9 @@ defmodule Time do
|
||||
:gt
|
||||
|
||||
"""
|
||||
@spec compare(Calendar.time(), Calendar.time()) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar} = time1, %{calendar: calendar} = time2) do
|
||||
%{hour: hour1, minute: minute1, second: second1, microsecond: {microsecond1, _}} = time1
|
||||
%{hour: hour2, minute: minute2, second: second2, microsecond: {microsecond2, _}} = time2
|
||||
|
||||
@spec compare(Calendar.time, Calendar.time) :: :lt | :eq | :gt
|
||||
def compare(%{calendar: calendar, hour: hour1, minute: minute1, second: second1, microsecond: {microsecond1, _}},
|
||||
%{calendar: calendar, hour: hour2, minute: minute2, second: second2, microsecond: {microsecond2, _}}) do
|
||||
case {{hour1, minute1, second1, microsecond1}, {hour2, minute2, second2, microsecond2}} do
|
||||
{first, second} when first > second -> :gt
|
||||
{first, second} when first < second -> :lt
|
||||
@@ -472,45 +384,18 @@ defmodule Time do
|
||||
{:ok, %Time{calendar: Calendar.Holocene, hour: 13, minute: 30, second: 15, microsecond: {0, 0}}}
|
||||
|
||||
"""
|
||||
@spec convert(Calendar.time(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
def convert(
|
||||
%{
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
},
|
||||
calendar
|
||||
) do
|
||||
time = %Time{
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}
|
||||
|
||||
{:ok, time}
|
||||
@spec convert(Calendar.time, Calendar.calendar) :: {:ok, t} | {:error, atom}
|
||||
def convert(%{calendar: calendar, hour: hour, minute: minute, second: second, microsecond: microsecond}, calendar) do
|
||||
{:ok, %Time{calendar: calendar, hour: hour, minute: minute, second: second, microsecond: microsecond}}
|
||||
end
|
||||
|
||||
def convert(%{microsecond: {_, precision}} = time, calendar) do
|
||||
{hour, minute, second, {microsecond, _}} =
|
||||
time
|
||||
|> to_day_fraction()
|
||||
|> calendar.time_from_day_fraction()
|
||||
|
||||
time = %Time{
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision}
|
||||
}
|
||||
|
||||
{:ok, time}
|
||||
|> calendar.time_from_day_fraction
|
||||
{:ok, %Time{calendar: calendar, hour: hour, minute: minute, second: second,
|
||||
microsecond: {microsecond, precision}}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -527,16 +412,13 @@ defmodule Time do
|
||||
%Time{calendar: Calendar.Holocene, hour: 13, minute: 30, second: 15, microsecond: {0, 0}}
|
||||
|
||||
"""
|
||||
@spec convert!(Calendar.time(), Calendar.calendar()) :: t
|
||||
@spec convert!(Calendar.time, Calendar.calendar) :: t
|
||||
def convert!(time, calendar) do
|
||||
case convert(time, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(time)} to target calendar #{inspect(calendar)}, " <>
|
||||
"reason: #{inspect(reason)}"
|
||||
raise ArgumentError, "cannot convert #{inspect time} to target calendar #{inspect calendar}, reason: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -577,72 +459,28 @@ defmodule Time do
|
||||
-2_000_000
|
||||
|
||||
"""
|
||||
@spec diff(Calendar.time(), Calendar.time(), System.time_unit()) :: integer
|
||||
@spec diff(Calendar.time, Calendar.time, System.time_unit) :: integer
|
||||
def diff(time1, time2, unit \\ :second) do
|
||||
fraction1 = to_day_fraction(time1)
|
||||
fraction2 = to_day_fraction(time2)
|
||||
|
||||
Calendar.ISO.iso_days_to_unit({0, fraction1}, unit) -
|
||||
Calendar.ISO.iso_days_to_unit({0, fraction2}, unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given time with the microsecond field truncated to the given
|
||||
precision (`:microsecond`, `millisecond` or `:second`).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Time.truncate(~T[01:01:01.123456], :microsecond)
|
||||
~T[01:01:01.123456]
|
||||
|
||||
iex> Time.truncate(~T[01:01:01.123456], :millisecond)
|
||||
~T[01:01:01.123]
|
||||
|
||||
iex> Time.truncate(~T[01:01:01.123456], :second)
|
||||
~T[01:01:01]
|
||||
|
||||
"""
|
||||
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
|
||||
def truncate(%Time{microsecond: microsecond} = time, precision) do
|
||||
%{time | microsecond: Calendar.truncate(microsecond, precision)}
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp to_day_fraction(%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {_, _} = microsecond,
|
||||
calendar: calendar
|
||||
}) do
|
||||
defp to_day_fraction(%{hour: hour, minute: minute, second: second, microsecond: {_, _} = microsecond, calendar: calendar}) do
|
||||
calendar.time_to_day_fraction(hour, minute, second, microsecond)
|
||||
end
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(time) do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: calendar
|
||||
} = time
|
||||
|
||||
def to_string(%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar}) do
|
||||
calendar.time_to_string(hour, minute, second, microsecond)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect do
|
||||
def inspect(%{calendar: Calendar.ISO} = time, _) do
|
||||
%{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: Calendar.ISO
|
||||
} = time
|
||||
|
||||
def inspect(%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: Calendar.ISO}, _) do
|
||||
"~T[" <> Calendar.ISO.time_to_string(hour, minute, second, microsecond) <> "]"
|
||||
end
|
||||
|
||||
|
||||
+128
-459
@@ -1,6 +1,6 @@
|
||||
defmodule Code do
|
||||
@moduledoc """
|
||||
Utilities for managing code compilation, code evaluation, and code loading.
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
This module complements Erlang's [`:code` module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behaviour which is specific to Elixir. Almost all of the functions in this module
|
||||
@@ -13,13 +13,11 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
Code.require_file("../eex/test/eex_test.exs")
|
||||
List.first(Code.loaded_files()) =~ "eex_test.exs"
|
||||
#=> true
|
||||
List.first(Code.loaded_files) =~ "eex_test.exs" #=> true
|
||||
|
||||
"""
|
||||
@spec loaded_files() :: [binary]
|
||||
def loaded_files do
|
||||
:elixir_code_server.call(:loaded)
|
||||
:elixir_code_server.call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -33,15 +31,12 @@ defmodule Code do
|
||||
|
||||
# Load EEx test code, unload file, check for functions still available
|
||||
Code.load_file("../eex/test/eex_test.exs")
|
||||
|
||||
Code.unload_files(Code.loaded_files())
|
||||
function_exported?(EExTest.Compiled, :before_compile, 0)
|
||||
#=> true
|
||||
Code.unload_files(Code.loaded_files)
|
||||
function_exported?(EExTest.Compiled, :before_compile, 0) #=> true
|
||||
|
||||
"""
|
||||
@spec unload_files([binary]) :: :ok
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast({:unload_files, files})
|
||||
:elixir_code_server.cast {:unload_files, files}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -55,16 +50,13 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.append_path(".")
|
||||
#=> true
|
||||
Code.append_path(".") #=> true
|
||||
|
||||
Code.append_path("/does_not_exist")
|
||||
#=> {:error, :bad_directory}
|
||||
Code.append_path("/does_not_exist") #=> {:error, :bad_directory}
|
||||
|
||||
"""
|
||||
@spec append_path(Path.t()) :: true | {:error, :bad_directory}
|
||||
def append_path(path) do
|
||||
:code.add_pathz(to_charlist(Path.expand(path)))
|
||||
:code.add_pathz(to_charlist(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -78,16 +70,13 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.prepend_path(".")
|
||||
#=> true
|
||||
Code.prepend_path(".") #=> true
|
||||
|
||||
Code.prepend_path("/does_not_exist")
|
||||
#=> {:error, :bad_directory}
|
||||
Code.prepend_path("/does_not_exist") #=> {:error, :bad_directory}
|
||||
|
||||
"""
|
||||
@spec prepend_path(Path.t()) :: true | {:error, :bad_directory}
|
||||
def prepend_path(path) do
|
||||
:code.add_patha(to_charlist(Path.expand(path)))
|
||||
:code.add_patha(to_charlist(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -95,21 +84,18 @@ defmodule Code do
|
||||
directories the Erlang VM uses for finding module code.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being deleted. If the
|
||||
path does not exist, this function returns `false`.
|
||||
path does not exist it returns `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
Code.prepend_path(".")
|
||||
Code.delete_path(".")
|
||||
#=> true
|
||||
Code.delete_path(".") #=> true
|
||||
|
||||
Code.delete_path("/does_not_exist")
|
||||
#=> false
|
||||
Code.delete_path("/does_not_exist") #=> false
|
||||
|
||||
"""
|
||||
@spec delete_path(Path.t()) :: boolean
|
||||
def delete_path(path) do
|
||||
:code.del_path(to_charlist(Path.expand(path)))
|
||||
:code.del_path(to_charlist(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -129,7 +115,6 @@ defmodule Code do
|
||||
Options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
|
||||
* `:line` - the line on which the script starts
|
||||
|
||||
Additionally, the following scope values can be configured:
|
||||
@@ -147,10 +132,10 @@ defmodule Code do
|
||||
of function names and arity must be sorted
|
||||
|
||||
Notice that setting any of the values above overrides Elixir's default
|
||||
values. For example, setting `:requires` to `[]` will no longer
|
||||
automatically require the `Kernel` module. In the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros like `Kernel.if/2`,
|
||||
`Kernel.SpecialForms.case/2`, and so on.
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
automatically require the `Kernel` module; in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros like `if/2`, `case/2`,
|
||||
etc.
|
||||
|
||||
Returns a tuple of the form `{value, binding}`,
|
||||
where `value` is the value returned from evaluating `string`.
|
||||
@@ -179,283 +164,19 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
@spec eval_string(List.Chars.t(), list, Macro.Env.t() | keyword) :: {term, binding :: list}
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval(to_charlist(string), binding, Map.to_list(env))
|
||||
{value, binding, _env, _scope} = :elixir.eval to_charlist(string), binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
{value, binding, _env, _scope} = :elixir.eval(to_charlist(string), binding, opts)
|
||||
{value, binding, _env, _scope} = :elixir.eval to_charlist(string), binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Formats the given code `string`.
|
||||
|
||||
The formatter receives a string representing Elixir code and
|
||||
returns iodata representing the formatted code according to
|
||||
pre-defined rules.
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - the file which contains the string, used for error
|
||||
reporting
|
||||
|
||||
* `:line` - the line the string starts, used for error reporting
|
||||
|
||||
* `:line_length` - the line length to aim for when formatting
|
||||
the document. Defaults to 98.
|
||||
|
||||
* `:locals_without_parens` - a keyword list of name and arity
|
||||
pairs that should be kept without parens whenever possible.
|
||||
The arity may be the atom `:*`, which implies all arities of
|
||||
that name. The formatter already includes a list of functions
|
||||
and this option augments this list.
|
||||
|
||||
* `:rename_deprecated_at` - rename all known deprecated functions
|
||||
at the given version to their non-deprecated equivalent. It
|
||||
expects a valid `Version` which is usually the minimum Elixir
|
||||
version supported by the project.
|
||||
|
||||
## Design principles
|
||||
|
||||
The formatter was designed under three principles.
|
||||
|
||||
First, the formatter never changes the semantics of the code by
|
||||
default. This means the input AST and the output AST are equivalent.
|
||||
Optional behaviour, such as `:rename_deprecated_at`, is allowed to
|
||||
break this guarantee.
|
||||
|
||||
The second principle is to provide as little configuration as possible.
|
||||
This eases the formatter adoption by removing contention points while
|
||||
making sure a single style is followed consistently by the community as
|
||||
a whole.
|
||||
|
||||
The formatter does not hard code names. The formatter will not behave
|
||||
specially because a function is named `defmodule`, `def`, etc. This
|
||||
principle mirrors Elixir's goal of being an extensible language where
|
||||
developers can extend the language with new constructs as if they were
|
||||
part of the language. When it is absolutely necessary to change behaviour
|
||||
based on the name, this behaviour should be configurable, such as the
|
||||
`:locals_without_parens` option.
|
||||
|
||||
## Keeping user's formatting
|
||||
|
||||
The formatter respects the input format in some cases. Those are
|
||||
listed below:
|
||||
|
||||
* Insignificant digits in numbers are kept as is. The formatter
|
||||
however always inserts underscores for decimal numbers with more
|
||||
than 5 digits and converts hexadecimal digits to uppercase
|
||||
|
||||
* Strings, charlists, atoms and sigils are kept as is. No character
|
||||
is automatically escaped or unescaped. The choice of delimiter is
|
||||
also respected from the input
|
||||
|
||||
* Newlines inside blocks are kept as in the input except for:
|
||||
1) expressions that take multiple lines will always have an empty
|
||||
line before and after and 2) empty lines are always squeezed
|
||||
together into a single empty line
|
||||
|
||||
* The choice between `:do` keyword and `do/end` blocks is left
|
||||
to the user
|
||||
|
||||
* Lists, tuples, bitstrings, maps, structs and function calls will be
|
||||
broken into multiple lines if they are followed by a newline in the
|
||||
opening bracket and preceded by a new line in the closing bracket
|
||||
|
||||
* Pipeline operators, like `|>` and others with the same precedence,
|
||||
will span multiple lines if they spanned multiple lines in the input
|
||||
|
||||
The behaviours above are not guaranteed. We may remove or add new
|
||||
rules in the future. The goal of documenting them is to provide better
|
||||
understanding on what to expect from the formatter.
|
||||
|
||||
## Adjusting formatted output
|
||||
|
||||
The formatter attempts to the fit the most it can on a single line.
|
||||
When the code does not fit a single line, the formatter introduces
|
||||
line breaks in the code.
|
||||
|
||||
In some rare situations, this may lead to undesired formatting.
|
||||
For example, the code below:
|
||||
|
||||
"this is a very long string ... #{inspect(some_value)}"
|
||||
|
||||
may be formatted as:
|
||||
|
||||
"this is a very long string ... #{
|
||||
inspect(some_value)
|
||||
}"
|
||||
|
||||
This happens because the only place the formatter can introduce a
|
||||
new line without changing the code semantics is in the interpolation.
|
||||
In those scenarios, we recommend developers to directly adjust the
|
||||
code. Here we can use the binary concatenation operator `<>`:
|
||||
|
||||
"this is a very long string " <>
|
||||
"... #{inspect(some_value)}"
|
||||
|
||||
The string concatenation makes the code fit on a single line and also
|
||||
gives more options to the formatter.
|
||||
|
||||
A similar example is when the formatter breaks a function definition
|
||||
over multiple clauses:
|
||||
|
||||
def my_function(
|
||||
%User{name: name, age: age, ...},
|
||||
arg1,
|
||||
arg2
|
||||
) do
|
||||
|
||||
While the code above is completely valid, you may prefer to match on
|
||||
the struct variables inside the function body in order to keep the
|
||||
definition on a single line:
|
||||
|
||||
def my_function(%User{} = user, arg1, arg2) do
|
||||
%{name: name, age: age, ...} = user
|
||||
|
||||
Since the formatter cannot change the semantics of your code,
|
||||
sometimes it is necessary to tweak the code to get optimal formatting.
|
||||
|
||||
### Multi-line lists, maps, tuples, etc
|
||||
|
||||
You can force lists, tuples, bitstrings, maps, structs and function
|
||||
calls to have one entry per line by adding a newline after the opening
|
||||
bracket and a new line before the closing bracket lines. For example:
|
||||
|
||||
[
|
||||
foo,
|
||||
bar
|
||||
]
|
||||
|
||||
If there are no newlines around the brackets, then the formatter will
|
||||
try to fit everything on a single line, such that the snippet below
|
||||
|
||||
[foo,
|
||||
bar]
|
||||
|
||||
will be formatted as
|
||||
|
||||
[foo, bar]
|
||||
|
||||
You can also force keywords to be rendered on multiple lines by
|
||||
having each entry on its own line:
|
||||
|
||||
defstruct name: nil,
|
||||
age: 0
|
||||
|
||||
The code above will be kept with one keyword entry per line by the
|
||||
formatter. To avoid that, just keep everything on a single line.
|
||||
|
||||
### Parens and no parens in function calls
|
||||
|
||||
Elixir has two syntaxes for function calls. With parens and no parens.
|
||||
By default, Elixir will add parens to all calls except for:
|
||||
|
||||
1. calls that have do/end blocks
|
||||
2. local calls without parens where the name and arity of the local
|
||||
call is also listed under `:locals_without_parens`
|
||||
|
||||
The choice of parens and no parens also affects indentation. When a
|
||||
function call with parens doesn't fit on the same line, the formatter
|
||||
introduces a newline around parens and indents the arguments with two
|
||||
spaces:
|
||||
|
||||
some_call(
|
||||
arg1,
|
||||
arg2,
|
||||
arg3
|
||||
)
|
||||
|
||||
On the other hand, function calls without parens are always indented
|
||||
by the function call length itself, like this:
|
||||
|
||||
some_call arg1,
|
||||
arg2,
|
||||
arg3
|
||||
|
||||
If the last argument is a data structure, such as maps and lists, and
|
||||
the beginning of the data structure fits on the same line as the function
|
||||
call, then no indentation happens, this allows code like this:
|
||||
|
||||
Enum.reduce(some_collection, initial_value, fn element, acc ->
|
||||
# code
|
||||
end)
|
||||
|
||||
some_funtion_without_parens %{
|
||||
foo: :bar,
|
||||
baz: :bat
|
||||
}
|
||||
|
||||
## Code comments
|
||||
|
||||
The formatter also handles code comments in a way to guarantee a space
|
||||
is always added between the beginning of the comment (#) and the next
|
||||
character.
|
||||
|
||||
The formatter also extracts all trailing comments to their previous line.
|
||||
For example, the code below
|
||||
|
||||
hello # world
|
||||
|
||||
will be rewritten to
|
||||
|
||||
# world
|
||||
hello
|
||||
|
||||
Because code comments are handled apart from the code representation (AST),
|
||||
there are some situations where code comments are seen as ambiguous by the
|
||||
code formatter. For example, the comment in the anonymous function below
|
||||
|
||||
fn
|
||||
arg1 ->
|
||||
body1
|
||||
# comment
|
||||
|
||||
arg2 ->
|
||||
body2
|
||||
end
|
||||
|
||||
and in this one
|
||||
|
||||
fn
|
||||
arg1 ->
|
||||
body1
|
||||
|
||||
# comment
|
||||
arg2 ->
|
||||
body2
|
||||
end
|
||||
|
||||
are considered equivalent (the nesting is discarded alongside most of
|
||||
user formatting). In such cases, the code formatter will always format to
|
||||
the latter.
|
||||
"""
|
||||
@spec format_string!(binary, keyword) :: iodata
|
||||
def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do
|
||||
line_length = Keyword.get(opts, :line_length, 98)
|
||||
algebra = Code.Formatter.to_algebra!(string, opts)
|
||||
Inspect.Algebra.format(algebra, line_length)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a file.
|
||||
|
||||
See `format_string!/2` for more information on code formatting and
|
||||
available options.
|
||||
"""
|
||||
@spec format_file!(binary, keyword) :: iodata
|
||||
def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
string = File.read!(file)
|
||||
formatted = format_string!(string, [file: file, line: 1] ++ opts)
|
||||
[formatted, ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents.
|
||||
|
||||
@@ -480,25 +201,24 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
@spec eval_quoted(Macro.t(), list, Macro.Env.t() | keyword) :: {term, binding :: list}
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted(quoted, binding, Map.to_list(env))
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted(quoted, binding, opts)
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
if f = opts[:functions], do: validate_imports(:functions, f)
|
||||
if m = opts[:macros], do: validate_imports(:macros, m)
|
||||
if a = opts[:aliases], do: validate_aliases(:aliases, a)
|
||||
if r = opts[:requires], do: validate_requires(:requires, r)
|
||||
if m = opts[:macros], do: validate_imports(:macros, m)
|
||||
if a = opts[:aliases], do: validate_aliases(:aliases, a)
|
||||
if r = opts[:requires], do: validate_requires(:requires, r)
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
@@ -510,64 +230,53 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn {k, v} -> is_atom(k) and is_atom(v) end)
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn {k, v} ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError,
|
||||
"expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_imports(kind, imports) do
|
||||
valid =
|
||||
is_list(imports) and
|
||||
Enum.all?(imports, fn {k, v} ->
|
||||
is_atom(k) and is_list(v) and
|
||||
Enum.all?(v, fn {name, arity} -> is_atom(name) and is_integer(arity) end)
|
||||
end)
|
||||
valid = is_list(imports) and Enum.all?(imports, fn {k, v} ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn {name, arity} ->
|
||||
is_atom(name) and is_integer(arity)
|
||||
end)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError,
|
||||
"expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to its quoted form.
|
||||
|
||||
Returns `{:ok, quoted_form}` if it succeeds,
|
||||
`{:error, {line, error, token}}` otherwise.
|
||||
Returns `{:ok, quoted_form}`
|
||||
if it succeeds, `{:error, {line, error, token}}` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - the filename to be reported in case of parsing errors.
|
||||
Defaults to "nofile".
|
||||
* `:file` - the filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__/0` macro
|
||||
|
||||
* `:line` - the starting line of the string being parsed.
|
||||
Defaults to 1.
|
||||
|
||||
* `:columns` - when `true`, attach a `:column` key to the quoted
|
||||
metadata. Defaults to `false`.
|
||||
* `:line` - the line reported in the `__ENV__/0` macro
|
||||
|
||||
* `:existing_atoms_only` - when `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
Defaults to `false`.
|
||||
when non-existing atoms are found by the tokenizer
|
||||
|
||||
## `Macro.to_string/2`
|
||||
## Macro.to_string/2
|
||||
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
||||
{:ok, Macro.t()} | {:error, {line :: pos_integer, term, term}}
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
|
||||
with {:ok, tokens} <- :elixir.string_to_tokens(to_charlist(string), line, file, opts) do
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
end
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
:elixir.string_to_quoted(to_charlist(string), line, file, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -580,10 +289,9 @@ defmodule Code do
|
||||
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
@spec string_to_quoted!(List.Chars.t(), keyword) :: Macro.t()
|
||||
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
:elixir.string_to_quoted!(to_charlist(string), line, file, opts)
|
||||
end
|
||||
|
||||
@@ -592,14 +300,13 @@ defmodule Code do
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file/2` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file/2` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings (exactly the same return value as `eval_string/3`).
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
@spec eval_file(binary, nil | binary) :: {term, binding :: list}
|
||||
def eval_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string(File.read!(file), [], file: file, line: 1)
|
||||
eval_string File.read!(file), [], [file: file, line: 1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -608,26 +315,24 @@ defmodule Code do
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ModuleName, bytecode}`, one tuple for
|
||||
It returns a list of tuples `{ModuleName, <<byte_code>>}`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file/2` is invoked by different processes concurrently,
|
||||
Notice that if `load_file` is invoked by different processes concurrently,
|
||||
the target file will be loaded concurrently many times. Check `require_file/2`
|
||||
if you don't want a file to be loaded concurrently.
|
||||
|
||||
## Examples
|
||||
|
||||
modules = Code.load_file("eex_test.exs", "../eex/test")
|
||||
List.first(modules)
|
||||
Code.load_file("eex_test.exs", "../eex/test") |> List.first
|
||||
#=> {EExTest.Compiled, <<70, 79, 82, 49, ...>>}
|
||||
|
||||
"""
|
||||
@spec load_file(binary, nil | binary) :: [{module, binary}]
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call({:acquire, file})
|
||||
loaded = :elixir_compiler.file(file)
|
||||
:elixir_code_server.cast({:loaded, file})
|
||||
:elixir_code_server.call {:acquire, file}
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
|
||||
@@ -636,46 +341,40 @@ defmodule Code do
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
The return value is the same as that of `load_file/2`. If the file was already
|
||||
required or loaded, `require_file/2` doesn't do anything and returns `nil`.
|
||||
required/loaded, `require_file` doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file/2` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file/2` acquires a lock and the remaining
|
||||
ones will block until the file is available. This means that if `require_file/2` is called
|
||||
more than one times with a given file, that file will be loaded only once. The first process to
|
||||
call `require_file/2` will get the list of loaded modules, others will get `nil`.
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
ones will block until the file is available. I.e., if `require_file` is called
|
||||
N times with a given file, it will be loaded only once. The first process to
|
||||
call `require_file` will get the list of loaded modules, others will get `nil`.
|
||||
|
||||
Check `load_file/2` if you want to load a file multiple times. See also `unload_files/1`.
|
||||
Check `load_file/2` if you want a file to be loaded multiple times. See also
|
||||
`unload_files/1`
|
||||
|
||||
## Examples
|
||||
|
||||
If the code is already loaded, it returns `nil`:
|
||||
|
||||
Code.require_file("eex_test.exs", "../eex/test")
|
||||
#=> nil
|
||||
Code.require_file("eex_test.exs", "../eex/test") #=> nil
|
||||
|
||||
If the code is not loaded yet, it returns the same as `load_file/2`:
|
||||
|
||||
modules = Code.require_file("eex_test.exs", "../eex/test")
|
||||
List.first(modules)
|
||||
Code.require_file("eex_test.exs", "../eex/test") |> List.first
|
||||
#=> {EExTest.Compiled, <<70, 79, 82, 49, ...>>}
|
||||
|
||||
"""
|
||||
@spec require_file(binary, nil | binary) :: [{module, binary}] | nil
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case :elixir_code_server.call({:acquire, file}) do
|
||||
:loaded ->
|
||||
:loaded ->
|
||||
nil
|
||||
|
||||
{:queued, ref} ->
|
||||
receive do
|
||||
{:elixir_code_server, ^ref, :loaded} -> nil
|
||||
end
|
||||
|
||||
{:queued, ref} ->
|
||||
receive do {:elixir_code_server, ^ref, :loaded} -> nil end
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file(file)
|
||||
:elixir_code_server.cast({:loaded, file})
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
end
|
||||
@@ -687,20 +386,19 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.compiler_options()
|
||||
Code.compiler_options
|
||||
#=> %{debug_info: true, docs: true,
|
||||
#=> warnings_as_errors: false, ignore_module_conflict: false}
|
||||
warnings_as_errors: false, ignore_module_conflict: false}
|
||||
|
||||
"""
|
||||
@spec compiler_options() :: %{optional(atom) => boolean}
|
||||
def compiler_options do
|
||||
:elixir_config.get(:compiler_options)
|
||||
:elixir_config.get :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the available compiler options.
|
||||
|
||||
See `compiler_options/1` for more info.
|
||||
See `Code.compiler_options/1` for more info.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -708,7 +406,6 @@ defmodule Code do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
|
||||
"""
|
||||
@spec available_compiler_options() :: [atom]
|
||||
def available_compiler_options do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
|
||||
end
|
||||
@@ -720,78 +417,69 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retain documentation in the compiled module.
|
||||
Defaults to `true`.
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default
|
||||
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled
|
||||
module. This allows a developer to reconstruct the original source
|
||||
code. Defaults to `false`.
|
||||
module; this allows a developer to reconstruct the original source
|
||||
code, `false` by default
|
||||
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were
|
||||
already defined without raising errors. Defaults to `false`.
|
||||
already defined without raising errors, `false` by default
|
||||
|
||||
* `:relative_paths` - when `true`, use relative paths in quoted nodes,
|
||||
warnings and errors generated by the compiler. Note disabling this option
|
||||
won't affect runtime warnings and errors. Defaults to `true`.
|
||||
warnings and errors generated by the compiler, `true` by default.
|
||||
Note disabling this option won't affect runtime warnings and errors.
|
||||
|
||||
* `:warnings_as_errors` - causes compilation to fail when warnings are
|
||||
generated. Defaults to `false`.
|
||||
generated
|
||||
|
||||
It returns the new map of compiler options.
|
||||
It returns the new list of compiler options.
|
||||
|
||||
## Examples
|
||||
|
||||
Code.compiler_options(debug_info: true)
|
||||
#=> %{debug_info: true, docs: true,
|
||||
#=> warnings_as_errors: false, ignore_module_conflict: false}
|
||||
warnings_as_errors: false, ignore_module_conflict: false}
|
||||
|
||||
"""
|
||||
@spec compiler_options(Enumerable.t()) :: %{optional(atom) => boolean}
|
||||
def compiler_options(opts) do
|
||||
available = available_compiler_options()
|
||||
|
||||
Enum.each(opts, fn {key, value} ->
|
||||
Enum.each(opts, fn({key, value}) ->
|
||||
cond do
|
||||
key not in available ->
|
||||
raise "unknown compiler option: #{inspect(key)}"
|
||||
|
||||
not is_boolean(value) ->
|
||||
raise "compiler option #{inspect(key)} should be a boolean, got: #{inspect(value)}"
|
||||
|
||||
true ->
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
:elixir_config.update(:compiler_options, &Enum.into(opts, &1))
|
||||
:elixir_config.update :compiler_options, &Enum.into(opts, &1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given string.
|
||||
|
||||
Returns a list of tuples where the first element is the module name
|
||||
and the second one is its bytecode (as a binary). A `file` can be
|
||||
given as second argument which will be used for reporting warnings
|
||||
and errors.
|
||||
and the second one is its byte code (as a binary).
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.compile/2`.
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
@spec compile_string(List.Chars.t(), binary) :: [{module, binary}]
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string(to_charlist(string), file)
|
||||
:elixir_compiler.string to_charlist(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the quoted expression.
|
||||
|
||||
Returns a list of tuples where the first element is the module name and
|
||||
the second one is its bytecode (as a binary). A `file` can be
|
||||
given as second argument which will be used for reporting warnings
|
||||
and errors.
|
||||
the second one is its byte code (as a binary).
|
||||
"""
|
||||
@spec compile_quoted(Macro.t(), binary) :: [{module, binary}]
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted(quoted, file)
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -800,7 +488,7 @@ defmodule Code do
|
||||
If the module is already loaded, this works as no-op. If the module
|
||||
was not yet loaded, it tries to load it.
|
||||
|
||||
If it succeeds in loading the module, it returns `{:module, module}`.
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
@@ -847,7 +535,7 @@ defmodule Code do
|
||||
|
||||
"""
|
||||
@spec ensure_loaded(module) ::
|
||||
{:module, module} | {:error, :embedded | :badfile | :nofile | :on_load_failure}
|
||||
{:module, module} | {:error, :embedded | :badfile | :nofile | :on_load_failure}
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
@@ -865,7 +553,6 @@ defmodule Code do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec ensure_loaded?(module) :: boolean
|
||||
def ensure_loaded?(module) when is_atom(module) do
|
||||
match?({:module, ^module}, ensure_loaded(module))
|
||||
end
|
||||
@@ -877,26 +564,24 @@ defmodule Code do
|
||||
not loaded yet, it checks if it needs to be compiled first and then
|
||||
tries to load it.
|
||||
|
||||
If it succeeds in loading the module, it returns `{:module, module}`.
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
"""
|
||||
@spec ensure_compiled(module) ::
|
||||
{:module, module} | {:error, :embedded | :badfile | :nofile | :on_load_failure}
|
||||
{:module, module} | {:error, :embedded | :badfile | :nofile | :on_load_failure}
|
||||
def ensure_compiled(module) when is_atom(module) do
|
||||
case :code.ensure_loaded(module) do
|
||||
{:error, :nofile} = error ->
|
||||
if is_pid(:erlang.get(:elixir_compiler_pid)) and
|
||||
Kernel.ErrorHandler.ensure_compiled(module, :module) do
|
||||
Kernel.ErrorHandler.ensure_compiled(module, :module) do
|
||||
{:module, module}
|
||||
else
|
||||
error
|
||||
end
|
||||
|
||||
other ->
|
||||
other
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@@ -917,64 +602,48 @@ defmodule Code do
|
||||
|
||||
When given a module name, it finds its BEAM code and reads the docs from it.
|
||||
|
||||
When given a path to a `.beam` file, it will load the docs directly from that
|
||||
When given a path to a .beam file, it will load the docs directly from that
|
||||
file.
|
||||
|
||||
The return value depends on the `kind` value:
|
||||
|
||||
* `:moduledoc` - tuple `{line, doc}` where `line` is the line on
|
||||
which the module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute,
|
||||
`false` if `@moduledoc false` was used, or `nil` if no `@moduledoc`
|
||||
was used.
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, arguments, doc}`. `doc` can be either a
|
||||
string, `false` if `@doc false` was used, or `nil` if no doc was used.
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:callback_docs` - list of all docstrings attached to
|
||||
`@callbacks` using the `@doc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, doc}`. `doc` can be either a string or
|
||||
`nil` if no `@doc` was set.
|
||||
`@callbacks` using the `@doc` attribute
|
||||
|
||||
* `:type_docs` - list of all docstrings attached to `@type` callbacks
|
||||
using the `@typedoc` attribute. Each tuple has the form
|
||||
`{{name, arity}, line, kind, doc}`. `doc` can be either a string or
|
||||
`nil` if no `@typedoc` was used.
|
||||
* `:type_docs` - list of all docstrings attached to
|
||||
`@type` callbacks using the `@typedoc` attribute
|
||||
|
||||
* `:all` - a keyword list with `:docs`, `:moduledoc`, `:callback_docs`,
|
||||
* `:all` - a keyword list with `:docs` and `:moduledoc`, `:callback_docs`,
|
||||
and `:type_docs`.
|
||||
|
||||
If the module cannot be found, it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
# Module documentation of an existing module
|
||||
# Get the module documentation
|
||||
iex> {_line, text} = Code.get_docs(Atom, :moduledoc)
|
||||
iex> text |> String.split("\n") |> Enum.at(0)
|
||||
iex> String.split(text, "\n") |> Enum.at(0)
|
||||
"Convenience functions for working with atoms."
|
||||
|
||||
# A module that doesn't exist
|
||||
# Module doesn't exist
|
||||
iex> Code.get_docs(ModuleNotGood, :all)
|
||||
nil
|
||||
|
||||
"""
|
||||
@doc_kinds [:docs, :moduledoc, :callback_docs, :type_docs, :all]
|
||||
|
||||
@spec get_docs(module, :moduledoc) :: {line :: pos_integer, doc :: false | binary} | nil
|
||||
@spec get_docs(module, :docs) :: [{function, line, kind, list, doc}] | nil
|
||||
when function: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :callback_docs) :: [{callback, line, kind, doc}] | nil
|
||||
when callback: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :type_docs) :: [{type, line, kind, doc}] | nil
|
||||
when type: {atom, arity}, line: pos_integer, kind: atom, doc: nil | false | binary
|
||||
@spec get_docs(module, :all) :: keyword | nil
|
||||
def get_docs(module, kind)
|
||||
|
||||
def get_docs(module, kind) when is_atom(module) and kind in @doc_kinds do
|
||||
case :code.get_object_code(module) do
|
||||
{_module, bin, _beam_path} -> do_get_docs(bin, kind)
|
||||
{_module, bin, _beam_path} ->
|
||||
do_get_docs(bin, kind)
|
||||
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
@@ -990,18 +659,19 @@ defmodule Code do
|
||||
{:ok, {_module, [{@docs_chunk, bin}]}} ->
|
||||
lookup_docs(:erlang.binary_to_term(bin), kind)
|
||||
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} ->
|
||||
nil
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind), do: do_lookup_docs(docs, kind)
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind),
|
||||
do: do_lookup_docs(docs, kind)
|
||||
|
||||
# unsupported chunk version
|
||||
defp lookup_docs(_, _), do: nil
|
||||
|
||||
defp do_lookup_docs(docs, :all), do: docs
|
||||
defp do_lookup_docs(docs, kind), do: Keyword.get(docs, kind)
|
||||
defp do_lookup_docs(docs, kind),
|
||||
do: Keyword.get(docs, kind)
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -1009,12 +679,11 @@ defmodule Code do
|
||||
#
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file =
|
||||
if relative_to do
|
||||
Path.expand(file, relative_to)
|
||||
else
|
||||
Path.expand(file)
|
||||
end
|
||||
file = if relative_to do
|
||||
Path.expand(file, relative_to)
|
||||
else
|
||||
Path.expand(file)
|
||||
end
|
||||
|
||||
if File.regular?(file) do
|
||||
file
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,281 +0,0 @@
|
||||
defmodule Code.Identifier do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Checks if the given identifier is an unary op.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.Identifier.unary_op(:+)
|
||||
{:non_associative, 300}
|
||||
|
||||
"""
|
||||
@spec unary_op(atom) :: {:non_associative, precedence :: pos_integer} | :error
|
||||
def unary_op(op) do
|
||||
cond do
|
||||
op in [:&] -> {:non_associative, 90}
|
||||
op in [:!, :^, :not, :+, :-, :~~~] -> {:non_associative, 300}
|
||||
op in [:@] -> {:non_associative, 320}
|
||||
true -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given identifier is a binary op.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.Identifier.binary_op(:+)
|
||||
{:left, 210}
|
||||
|
||||
"""
|
||||
@spec binary_op(atom) :: {:left | :right, precedence :: pos_integer} | :error
|
||||
def binary_op(op) do
|
||||
cond do
|
||||
op in [:<-, :\\] -> {:left, 40}
|
||||
op in [:when] -> {:right, 50}
|
||||
op in [:::] -> {:right, 60}
|
||||
op in [:|] -> {:right, 70}
|
||||
op in [:=] -> {:right, 100}
|
||||
op in [:||, :|||, :or] -> {:left, 130}
|
||||
op in [:&&, :&&&, :and] -> {:left, 140}
|
||||
op in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
op in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
op in [:|>, :<<<, :>>>, :<~, :~>, :<<~, :~>>, :<~>, :<|>] -> {:left, 170}
|
||||
op in [:in] -> {:left, 180}
|
||||
op in [:^^^] -> {:left, 190}
|
||||
op in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
op in [:+, :-] -> {:left, 210}
|
||||
op in [:*, :/] -> {:left, 220}
|
||||
op in [:.] -> {:left, 310}
|
||||
true -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Classifies the given atom into one of the following categories:
|
||||
|
||||
* :alias - a valid Elixir alias, like Foo, Foo.Bar and so on
|
||||
|
||||
* :callable_local - an atom that can be used as a local call;
|
||||
this category includes identifiers like :foo
|
||||
|
||||
* :callable_operators - all callable operators, such as `:<>`. Note
|
||||
operators such as `:..` are not callable because of ambiguity
|
||||
|
||||
* :not_callable - an atom that cannot be used as a function call after the
|
||||
. operator (for example, :<<>> is not callable because Foo.<<>> is a
|
||||
syntax error); this category includes atoms like :Foo, since they are
|
||||
valid identifiers but they need quotes to be used in function calls
|
||||
(Foo."Bar")
|
||||
|
||||
* :other - any other atom (these are usually escaped when inspected, like
|
||||
:"foo and bar")
|
||||
|
||||
"""
|
||||
def classify(atom) when is_atom(atom) do
|
||||
charlist = Atom.to_charlist(atom)
|
||||
|
||||
cond do
|
||||
atom in [:%, :%{}, :{}, :<<>>, :..., :.., :., :->] ->
|
||||
:not_callable
|
||||
|
||||
unary_op(atom) != :error or binary_op(atom) != :error ->
|
||||
:callable_operator
|
||||
|
||||
valid_alias?(charlist) ->
|
||||
:alias
|
||||
|
||||
true ->
|
||||
case :elixir_config.safe_get(:identifier_tokenizer, String.Tokenizer).tokenize(charlist) do
|
||||
{kind, _acc, [], _, _, special} ->
|
||||
if kind == :identifier and not :lists.member(?@, special) do
|
||||
:callable_local
|
||||
else
|
||||
:not_callable
|
||||
end
|
||||
|
||||
_ ->
|
||||
:other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_alias?('Elixir' ++ rest), do: valid_alias_piece?(rest)
|
||||
defp valid_alias?(_other), do: false
|
||||
|
||||
defp valid_alias_piece?([?., char | rest]) when char >= ?A and char <= ?Z,
|
||||
do: valid_alias_piece?(trim_leading_while_valid_identifier(rest))
|
||||
|
||||
defp valid_alias_piece?([]), do: true
|
||||
defp valid_alias_piece?(_other), do: false
|
||||
|
||||
defp trim_leading_while_valid_identifier([char | rest])
|
||||
when char >= ?a and char <= ?z
|
||||
when char >= ?A and char <= ?Z
|
||||
when char >= ?0 and char <= ?9
|
||||
when char == ?_ do
|
||||
trim_leading_while_valid_identifier(rest)
|
||||
end
|
||||
|
||||
defp trim_leading_while_valid_identifier(other) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects the identifier as an atom.
|
||||
"""
|
||||
def inspect_as_atom(atom) when is_nil(atom) or is_boolean(atom) do
|
||||
Atom.to_string(atom)
|
||||
end
|
||||
|
||||
def inspect_as_atom(atom) when is_atom(atom) do
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
case classify(atom) do
|
||||
:alias ->
|
||||
case binary do
|
||||
binary when binary in ["Elixir", "Elixir.Elixir"] -> binary
|
||||
"Elixir.Elixir." <> _rest -> binary
|
||||
"Elixir." <> rest -> rest
|
||||
end
|
||||
|
||||
type when type in [:callable_local, :callable_operator, :not_callable] ->
|
||||
":" <> binary
|
||||
|
||||
:other ->
|
||||
{escaped, _} = escape(binary, ?")
|
||||
IO.iodata_to_binary([?:, ?", escaped, ?"])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects the given identifier as a key.
|
||||
"""
|
||||
def inspect_as_key(atom) when is_atom(atom) do
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
case classify(atom) do
|
||||
type when type in [:callable_local, :callable_operator, :not_callable] ->
|
||||
IO.iodata_to_binary([binary, ?:])
|
||||
|
||||
_ ->
|
||||
{escaped, _} = escape(binary, ?")
|
||||
IO.iodata_to_binary([?", escaped, ?", ?:])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects the given identifier as a function name.
|
||||
"""
|
||||
def inspect_as_function(atom) when is_atom(atom) do
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
case classify(atom) do
|
||||
type when type in [:callable_local, :callable_operator] ->
|
||||
binary
|
||||
|
||||
type ->
|
||||
escaped =
|
||||
if type in [:not_callable, :alias] do
|
||||
binary
|
||||
else
|
||||
elem(escape(binary, ?"), 0)
|
||||
end
|
||||
|
||||
IO.iodata_to_binary([?", escaped, ?"])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extracts the name and arity of the parent from the anonymous function identifier.
|
||||
"""
|
||||
# Example of this format: -NAME/ARITY-fun-COUNT-
|
||||
def extract_anonymous_fun_parent(atom) when is_atom(atom) do
|
||||
with "-" <> rest <- Atom.to_string(atom),
|
||||
[trailing | reversed] = rest |> String.split("/") |> Enum.reverse(),
|
||||
[arity, _inner, _count, ""] <- String.split(trailing, "-") do
|
||||
{reversed |> Enum.reverse() |> Enum.join("/") |> String.to_atom(), arity}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Escapes the given identifier.
|
||||
"""
|
||||
def escape(other, char, count \\ :infinity, fun \\ &escape_map/1) do
|
||||
escape(other, char, count, [], fun)
|
||||
end
|
||||
|
||||
defp escape(<<_, _::binary>> = binary, _char, 0, acc, _fun) do
|
||||
{acc, binary}
|
||||
end
|
||||
|
||||
defp escape(<<char, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), [acc | [?\\, char]], fun)
|
||||
end
|
||||
|
||||
defp escape(<<?#, ?{, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), [acc | '\\\#{'], fun)
|
||||
end
|
||||
|
||||
defp escape(<<h::utf8, t::binary>>, char, count, acc, fun) do
|
||||
escaped = if value = fun.(h), do: value, else: escape_char(h)
|
||||
escape(t, char, decrement(count), [acc | escaped], fun)
|
||||
end
|
||||
|
||||
defp escape(<<a::4, b::4, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), [acc | ['\\x', to_hex(a), to_hex(b)]], fun)
|
||||
end
|
||||
|
||||
defp escape(<<>>, _char, _count, acc, _fun) do
|
||||
{acc, <<>>}
|
||||
end
|
||||
|
||||
defp escape_char(0), do: '\\0'
|
||||
|
||||
defp escape_char(65279), do: '\\uFEFF'
|
||||
|
||||
defp escape_char(char)
|
||||
when char in 0x20..0x7E
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
when char in 0x10000..0x10FFFF do
|
||||
<<char::utf8>>
|
||||
end
|
||||
|
||||
defp escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::8>>
|
||||
['\\x', to_hex(a), to_hex(b)]
|
||||
end
|
||||
|
||||
defp escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::16>>
|
||||
['\\x{', to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}]
|
||||
end
|
||||
|
||||
defp escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
|
||||
['\\x{', to_hex(a), to_hex(b), to_hex(c), to_hex(d), to_hex(e), to_hex(f), ?}]
|
||||
end
|
||||
|
||||
defp escape_map(?\a), do: '\\a'
|
||||
defp escape_map(?\b), do: '\\b'
|
||||
defp escape_map(?\d), do: '\\d'
|
||||
defp escape_map(?\e), do: '\\e'
|
||||
defp escape_map(?\f), do: '\\f'
|
||||
defp escape_map(?\n), do: '\\n'
|
||||
defp escape_map(?\r), do: '\\r'
|
||||
defp escape_map(?\t), do: '\\t'
|
||||
defp escape_map(?\v), do: '\\v'
|
||||
defp escape_map(?\\), do: '\\\\'
|
||||
defp escape_map(_), do: false
|
||||
|
||||
@compile {:inline, to_hex: 1, decrement: 1}
|
||||
defp to_hex(c) when c in 0..9, do: ?0 + c
|
||||
defp to_hex(c) when c in 10..15, do: ?A + c - 10
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
@@ -79,36 +79,30 @@ end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def into(original) do
|
||||
fun = fn
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x | list]
|
||||
list, :done -> original ++ :lists.reverse(list)
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{[], fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def into(original) do
|
||||
fun = fn
|
||||
{original, fn
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc | x]
|
||||
acc, :done -> IO.iodata_to_binary(acc)
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{original, fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def into(original) do
|
||||
fun = fn
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
map, :done -> map
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{original, fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+39
-63
@@ -49,19 +49,19 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match?({:ok, _}, fetch(dict, key))
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
true -> dict
|
||||
false -> put(dict, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
def put_new_lazy(dict, key, fun) when is_function(fun, 0) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
true -> dict
|
||||
false -> put(dict, key, fun.())
|
||||
end
|
||||
end
|
||||
@@ -80,21 +80,21 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn kv, acc -> {:cont, [kv | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv | acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn {k, _}, acc -> {:cont, [k | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k | acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn {_, v}, acc -> {:cont, [v | acc]} end)
|
||||
|> elem(1)
|
||||
|> :lists.reverse()
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v | acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
@@ -102,21 +102,18 @@ defmodule Dict do
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
case size(dict1) == size(dict2) do
|
||||
false ->
|
||||
false
|
||||
|
||||
true ->
|
||||
reduce(dict1, {:cont, true}, fn {k, v}, _acc ->
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn _k, _v1, v2 -> v2 end) do
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
@@ -128,15 +125,13 @@ defmodule Dict do
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &fun.(k, &1, v2))}
|
||||
end)
|
||||
end
|
||||
|> elem(1)
|
||||
end |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
@@ -146,7 +141,6 @@ defmodule Dict do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
@@ -156,7 +150,6 @@ defmodule Dict do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(dict, key)}
|
||||
|
||||
:error ->
|
||||
{default, dict}
|
||||
end
|
||||
@@ -166,7 +159,6 @@ defmodule Dict do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(dict, key)}
|
||||
|
||||
:error ->
|
||||
{fun.(), dict}
|
||||
end
|
||||
@@ -177,45 +169,31 @@ defmodule Dict do
|
||||
case fetch(exc, key) do
|
||||
{:ok, value} ->
|
||||
{put(inc, key, value), delete(exc, key)}
|
||||
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defoverridable merge: 2,
|
||||
merge: 3,
|
||||
equal?: 2,
|
||||
to_list: 1,
|
||||
keys: 1,
|
||||
values: 1,
|
||||
take: 2,
|
||||
drop: 2,
|
||||
get: 2,
|
||||
get: 3,
|
||||
fetch!: 2,
|
||||
has_key?: 2,
|
||||
put_new: 3,
|
||||
pop: 2,
|
||||
pop: 3,
|
||||
split: 2,
|
||||
update: 4,
|
||||
update!: 3,
|
||||
get_and_update: 3,
|
||||
get_lazy: 3,
|
||||
pop_lazy: 3,
|
||||
put_new_lazy: 3
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
|
||||
update: 4, update!: 3, get_and_update: 3, get_lazy: 3,
|
||||
pop_lazy: 3, put_new_lazy: 3
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
case unquote(dict) do
|
||||
%module{} -> module
|
||||
%{} -> Map
|
||||
dict when is_list(dict) -> Keyword
|
||||
dict -> unsupported_dict(dict)
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_list(x) ->
|
||||
Keyword
|
||||
x ->
|
||||
unsupported_dict(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -293,7 +271,7 @@ defmodule Dict do
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2)
|
||||
else
|
||||
do_merge(target1, dict1, dict2, fn _k, _v1, v2 -> v2 end)
|
||||
do_merge(target1, dict1, dict2, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -310,10 +288,9 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
defp do_merge(target1, dict1, dict2, fun) do
|
||||
Enumerable.reduce(dict2, {:cont, dict1}, fn {k, v}, acc ->
|
||||
{:cont, target1.update(acc, k, v, fn other -> fun.(k, other, v) end)}
|
||||
end)
|
||||
|> elem(1)
|
||||
Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
|
||||
{:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
@@ -366,13 +343,12 @@ defmodule Dict do
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
Enumerable.reduce(dict2, {:cont, true}, fn {k, v}, _acc ->
|
||||
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
|
||||
true ->
|
||||
false
|
||||
@@ -386,6 +362,6 @@ defmodule Dict do
|
||||
|
||||
@spec unsupported_dict(t) :: no_return
|
||||
defp unsupported_dict(dict) do
|
||||
raise ArgumentError, "unsupported dict: #{inspect(dict)}"
|
||||
raise ArgumentError, "unsupported dict: #{inspect dict}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,897 +0,0 @@
|
||||
defmodule DynamicSupervisor do
|
||||
@moduledoc ~S"""
|
||||
A supervisor that starts children dynamically.
|
||||
|
||||
The `Supervisor` module was designed to handle mostly static children
|
||||
that are started in the given order when the supervisor starts. A
|
||||
`DynamicSupervisor` starts with no children. Instead, children are
|
||||
started on demand via `start_child/2`. When a dynamic supervisor
|
||||
terminates, all children are shutdown at the same time, with no guarantee
|
||||
of ordering.
|
||||
|
||||
## Examples
|
||||
|
||||
A dynamic supervisor is started with no children, only with the
|
||||
supervision strategy (the only strategy currently supported is
|
||||
`:one_for_one`):
|
||||
|
||||
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
|
||||
|
||||
Once the dynamic supervisor is running, we can start children
|
||||
with `start_child/2`, which receives a child specification:
|
||||
|
||||
{:ok, agent1} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
|
||||
Agent.update(agent1, &Map.put(&1, :key, "value"))
|
||||
Agent.get(agent1, & &1)
|
||||
#=> %{key: "value"}
|
||||
|
||||
{:ok, agent2} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
|
||||
Agent.get(agent2, & &1)
|
||||
#=> %{}
|
||||
|
||||
DynamicSupervisor.count_children(sup)
|
||||
#=> %{active: 2, specs: 2, supervisors: 0, workers: 2}
|
||||
|
||||
## Module-based supervisors
|
||||
|
||||
Similar to `Supervisor`, dynamic supervisors also support module-based
|
||||
supervisors.
|
||||
|
||||
defmodule MyApp.DynamicSupervisor do
|
||||
# Automatically defines child_spec/1
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(_arg) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
See the `Supervisor` docs for a discussion of when you may want to use
|
||||
module-based supervisors.
|
||||
|
||||
## Name registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about these rules in the documentation for `GenServer`.
|
||||
"""
|
||||
|
||||
@behaviour GenServer
|
||||
|
||||
@doc """
|
||||
Callback invoked to start the supervisor and during hot code upgrades.
|
||||
|
||||
Developers typically invoke `DynamicSupervisor.init/1` at the end of
|
||||
their init callback to return the proper supervision flags.
|
||||
"""
|
||||
@callback init(args :: term) :: {:ok, sup_flags()} | :ignore
|
||||
|
||||
@opaque sup_flags() :: %{
|
||||
strategy: strategy(),
|
||||
intensity: non_neg_integer(),
|
||||
period: pos_integer(),
|
||||
max_children: non_neg_integer() | :infinity,
|
||||
extra_arguments: [term()]
|
||||
}
|
||||
|
||||
@typedoc "Option values used by the `start*` functions"
|
||||
@type option :: {:name, Supervisor.name()} | init_option()
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [option, ...]
|
||||
|
||||
@typedoc "Options given to `start_link/2` and `init/1`"
|
||||
@type init_option ::
|
||||
{:strategy, strategy()}
|
||||
| {:max_restarts, non_neg_integer()}
|
||||
| {:max_seconds, pos_integer()}
|
||||
| {:max_children, non_neg_integer() | :infinity}
|
||||
| {:extra_arguments, [term()]}
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :one_for_one
|
||||
|
||||
defstruct [
|
||||
:args,
|
||||
:extra_arguments,
|
||||
:mod,
|
||||
:name,
|
||||
:strategy,
|
||||
:max_children,
|
||||
:max_restarts,
|
||||
:max_seconds,
|
||||
children: %{},
|
||||
dynamic: 0,
|
||||
restarts: []
|
||||
]
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
@behaviour DynamicSupervisor
|
||||
@opts unquote(opts)
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]},
|
||||
type: :supervisor
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@doc false
|
||||
def init(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a supervisor with the given options.
|
||||
|
||||
The `:strategy` is a required option and the currently supported
|
||||
value is `:one_for_one`. The remaining options can be found in the
|
||||
`init/1` docs.
|
||||
|
||||
The `:name` option can also be used to register a supervisor name.
|
||||
The supported values are described under the "Name registration"
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the supervisor is successfully spawned, this function returns
|
||||
`{:ok, pid}`, where `pid` is the PID of the supervisor. If the supervisor
|
||||
is given a name and a process with the specified name already exists,
|
||||
the function returns `{:error, {:already_started, pid}}`, where `pid`
|
||||
is the PID of that process.
|
||||
|
||||
Note that a supervisor started with this function is linked to the parent
|
||||
process and exits not only on crashes but also if the parent process exits
|
||||
with `:normal` reason.
|
||||
"""
|
||||
@spec start_link(options) :: Supervisor.on_start()
|
||||
def start_link(options) when is_list(options) do
|
||||
keys = [:extra_arguments, :max_children, :max_seconds, :max_restarts, :strategy]
|
||||
{sup_opts, start_opts} = Keyword.split(options, keys)
|
||||
start_link(Supervisor.Default, init(sup_opts), start_opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a module-based supervisor process with the given `module` and `arg`.
|
||||
|
||||
To start the supervisor, the `c:init/1` callback will be invoked in the given
|
||||
`module`, with `arg` as its argument. The `c:init/1` callback must return a
|
||||
supervisor specification which can be created with the help of the `init/1`
|
||||
function.
|
||||
|
||||
If the `c:init/1` callback returns `:ignore`, this function returns
|
||||
`:ignore` as well and the supervisor terminates with reason `:normal`.
|
||||
If it fails or returns an incorrect value, this function returns
|
||||
`{:error, term}` where `term` is a term with information about the
|
||||
error, and the supervisor terminates with reason `term`.
|
||||
|
||||
The `:name` option can also be given in order to register a supervisor
|
||||
name, the supported values are described in the "Name registration"
|
||||
section in the `GenServer` module docs.
|
||||
"""
|
||||
@spec start_link(module, term, GenServer.options()) :: Supervisor.on_start()
|
||||
def start_link(mod, args, opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, {mod, args, opts[:name]}, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dynamically adds a child specification to `supervisor` and starts that child.
|
||||
|
||||
`child_spec` should be a valid child specification. The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or `{:ok, child,
|
||||
info}`, then child specification and PID are added to the supervisor and
|
||||
this function returns the same value.
|
||||
|
||||
If the child process start function returns `:ignore`, then no child is added
|
||||
to the supervision tree and this function returns `:ignore` too.
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous
|
||||
value, or if it fails, the child specification is discarded and this function
|
||||
returns `{:error, error}` where `error` is a term containing information about
|
||||
the error and child specification.
|
||||
|
||||
If the supervisor already has N children in a way that N exceeds the amount
|
||||
of `:max_children` set on the supervisor initialization (see `init/1`), then
|
||||
this function returns `{:error, :max_children}`.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor(), :supervisor.child_spec() | {module, term} | module) ::
|
||||
Supervisor.on_start_child()
|
||||
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
|
||||
validate_and_start_child(supervisor, child_spec)
|
||||
end
|
||||
|
||||
def start_child(supervisor, child_spec) do
|
||||
validate_and_start_child(supervisor, Supervisor.child_spec(child_spec, []))
|
||||
end
|
||||
|
||||
defp validate_and_start_child(supervisor, child_spec) do
|
||||
case validate_child(child_spec) do
|
||||
{:ok, child} -> call(supervisor, {:start_child, child})
|
||||
error -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_child(%{id: _, start: {mod, _, _} = start} = child) do
|
||||
restart = Map.get(child, :restart, :permanent)
|
||||
type = Map.get(child, :type, :worker)
|
||||
modules = Map.get(child, :modules, [mod])
|
||||
|
||||
shutdown =
|
||||
case type do
|
||||
:worker -> Map.get(child, :shutdown, 5_000)
|
||||
:supervisor -> Map.get(child, :shutdown, :infinity)
|
||||
end
|
||||
|
||||
validate_child(start, restart, shutdown, type, modules)
|
||||
end
|
||||
|
||||
defp validate_child({_, start, restart, shutdown, type, modules}) do
|
||||
validate_child(start, restart, shutdown, type, modules)
|
||||
end
|
||||
|
||||
defp validate_child(other) do
|
||||
{:invalid_child_spec, other}
|
||||
end
|
||||
|
||||
defp validate_child(start, restart, shutdown, type, modules) do
|
||||
with :ok <- validate_start(start),
|
||||
:ok <- validate_restart(restart),
|
||||
:ok <- validate_shutdown(shutdown),
|
||||
:ok <- validate_type(type),
|
||||
:ok <- validate_modules(modules) do
|
||||
{:ok, {start, restart, shutdown, type, modules}}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_start({m, f, args}) when is_atom(m) and is_atom(f) and is_list(args), do: :ok
|
||||
defp validate_start(mfa), do: {:invalid_mfa, mfa}
|
||||
|
||||
defp validate_type(type) when type in [:supervisor, :worker], do: :ok
|
||||
defp validate_type(type), do: {:invalid_child_type, type}
|
||||
|
||||
defp validate_restart(restart) when restart in [:permanent, :temporary, :transient], do: :ok
|
||||
defp validate_restart(restart), do: {:invalid_restart_type, restart}
|
||||
|
||||
defp validate_shutdown(shutdown) when is_integer(shutdown) and shutdown > 0, do: :ok
|
||||
defp validate_shutdown(shutdown) when shutdown in [:infinity, :brutal_kill], do: :ok
|
||||
defp validate_shutdown(shutdown), do: {:invalid_shutdown, shutdown}
|
||||
|
||||
defp validate_modules(:dynamic), do: :ok
|
||||
|
||||
defp validate_modules(mods) do
|
||||
if is_list(mods) and Enum.all?(mods, &is_atom/1) do
|
||||
:ok
|
||||
else
|
||||
{:invalid_modules, mods}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child identified by child id.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no process with
|
||||
the given PID, this function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor(), pid) :: :ok | {:error, :not_found}
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
call(supervisor, {:terminate_child, pid})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
|
||||
Note that calling this function when supervising a large number
|
||||
of children under low memory conditions can cause an out of memory
|
||||
exception.
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
* `id` - it is always `:undefined` for dynamic supervisors
|
||||
|
||||
* `child` - the pid of the corresponding child process or the
|
||||
atom `:restarting` if the process is about to be restarted
|
||||
|
||||
* `type` - `:worker` or `:supervisor` as defined in the child
|
||||
specification
|
||||
|
||||
* `modules` - as defined in the child specification
|
||||
|
||||
"""
|
||||
@spec which_children(Supervisor.supervisor()) :: [
|
||||
{:undefined, pid | :restarting, :worker | :supervisor, :supervisor.modules()}
|
||||
]
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - always 1 as dynamic supervisors have a single specification
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by
|
||||
this supervisor
|
||||
|
||||
* `:supervisors` - the count of all supervisors whether or not the child
|
||||
process is still alive
|
||||
|
||||
* `:workers` - the count of all workers, whether or not the child process
|
||||
is still alive
|
||||
|
||||
"""
|
||||
@spec count_children(Supervisor.supervisor()) :: %{
|
||||
specs: non_neg_integer,
|
||||
active: non_neg_integer,
|
||||
supervisors: non_neg_integer,
|
||||
workers: non_neg_integer
|
||||
}
|
||||
def count_children(supervisor) do
|
||||
call(supervisor, :count_children) |> :maps.from_list()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a set of options that initializes a dynamic supervisor.
|
||||
|
||||
This is typically invoked at the end of the `c:init/1` callback of
|
||||
module-based supervisors. See the sections "Module-based supervisors"
|
||||
in the module documentation for more information.
|
||||
|
||||
The options received by this function are also supported by `start_link/2`.
|
||||
|
||||
This function returns a tuple containing the supervisor options.
|
||||
|
||||
## Examples
|
||||
|
||||
def init(_arg) do
|
||||
DynamicSupervisor.init(max_children: 1000, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option. The only supported
|
||||
value is `:one_for_one` which means that no other child is
|
||||
terminate if a child process terminates. You can learn more
|
||||
about strategies in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
a time frame. Defaults to `3`.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to `5`.
|
||||
|
||||
* `:max_children` - the maximum amount of children to be running
|
||||
under this supervisor at the same time. When `:max_children` is
|
||||
exceeded, `start_child/2` returns `{:error, :dynamic}`. Defaults
|
||||
to `:infinity`.
|
||||
|
||||
* `:extra_arguments` - arguments that are prepended to the arguments
|
||||
specified in the child spec given to `start_child/2`. Defaults to
|
||||
an empty list.
|
||||
|
||||
"""
|
||||
@spec init([init_option]) :: {:ok, map()}
|
||||
def init(options) when is_list(options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
intensity = Keyword.get(options, :max_restarts, 3)
|
||||
period = Keyword.get(options, :max_seconds, 5)
|
||||
max_children = Keyword.get(options, :max_children, :infinity)
|
||||
extra_arguments = Keyword.get(options, :extra_arguments, [])
|
||||
|
||||
flags = %{
|
||||
strategy: strategy,
|
||||
intensity: intensity,
|
||||
period: period,
|
||||
max_children: max_children,
|
||||
extra_arguments: extra_arguments
|
||||
}
|
||||
|
||||
{:ok, flags}
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
@impl true
|
||||
def init({mod, args, name}) do
|
||||
Process.put(:"$initial_call", {:supervisor, mod, 1})
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
case mod.init(args) do
|
||||
{:ok, flags} when is_map(flags) ->
|
||||
state = %DynamicSupervisor{mod: mod, args: args, name: name || {self(), mod}}
|
||||
|
||||
case init(state, flags) do
|
||||
{:ok, state} -> {:ok, state}
|
||||
{:error, reason} -> {:stop, {:supervisor_data, reason}}
|
||||
end
|
||||
|
||||
:ignore ->
|
||||
:ignore
|
||||
|
||||
other ->
|
||||
{:stop, {:bad_return, {mod, :init, other}}}
|
||||
end
|
||||
end
|
||||
|
||||
defp init(state, flags) do
|
||||
extra_arguments = Map.get(flags, :extra_arguments, [])
|
||||
max_children = Map.get(flags, :max_children, :infinity)
|
||||
max_restarts = Map.get(flags, :intensity, 1)
|
||||
max_seconds = Map.get(flags, :period, 5)
|
||||
strategy = Map.get(flags, :strategy, :one_for_one)
|
||||
|
||||
with :ok <- validate_strategy(strategy),
|
||||
:ok <- validate_restarts(max_restarts),
|
||||
:ok <- validate_seconds(max_seconds),
|
||||
:ok <- validate_dynamic(max_children),
|
||||
:ok <- validate_extra_arguments(extra_arguments) do
|
||||
{:ok,
|
||||
%{
|
||||
state
|
||||
| extra_arguments: extra_arguments,
|
||||
max_children: max_children,
|
||||
max_restarts: max_restarts,
|
||||
max_seconds: max_seconds,
|
||||
strategy: strategy
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_strategy(strategy) when strategy in [:one_for_one], do: :ok
|
||||
defp validate_strategy(strategy), do: {:error, {:invalid_strategy, strategy}}
|
||||
|
||||
defp validate_restarts(restart) when is_integer(restart) and restart >= 0, do: :ok
|
||||
defp validate_restarts(restart), do: {:error, {:invalid_intensity, restart}}
|
||||
|
||||
defp validate_seconds(seconds) when is_integer(seconds) and seconds > 0, do: :ok
|
||||
defp validate_seconds(seconds), do: {:error, {:invalid_period, seconds}}
|
||||
|
||||
defp validate_dynamic(:infinity), do: :ok
|
||||
defp validate_dynamic(dynamic) when is_integer(dynamic) and dynamic >= 0, do: :ok
|
||||
defp validate_dynamic(dynamic), do: {:error, {:invalid_max_children, dynamic}}
|
||||
|
||||
defp validate_extra_arguments(list) when is_list(list), do: :ok
|
||||
defp validate_extra_arguments(extra), do: {:error, {:invalid_extra_arguments, extra}}
|
||||
|
||||
@impl true
|
||||
def handle_call(:which_children, _from, state) do
|
||||
%{children: children} = state
|
||||
|
||||
reply =
|
||||
for {pid, args} <- children do
|
||||
case args do
|
||||
{:restarting, {_, _, _, type, modules}} ->
|
||||
{:undefined, :restarting, type, modules}
|
||||
|
||||
{_, _, _, type, modules} ->
|
||||
{:undefined, pid, type, modules}
|
||||
end
|
||||
end
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call(:count_children, _from, state) do
|
||||
%{children: children} = state
|
||||
specs = map_size(children)
|
||||
|
||||
{active, workers, supervisors} =
|
||||
Enum.reduce(children, {0, 0, 0}, fn
|
||||
{_pid, {:restarting, {_, _, _, :worker, _}}}, {active, worker, supervisor} ->
|
||||
{active, worker + 1, supervisor}
|
||||
|
||||
{_pid, {:restarting, {_, _, _, :supervisor, _}}}, {active, worker, supervisor} ->
|
||||
{active, worker, supervisor + 1}
|
||||
|
||||
{_pid, {_, _, _, :worker, _}}, {active, worker, supervisor} ->
|
||||
{active + 1, worker + 1, supervisor}
|
||||
|
||||
{_pid, {_, _, _, :supervisor, _}}, {active, worker, supervisor} ->
|
||||
{active + 1, worker, supervisor + 1}
|
||||
end)
|
||||
|
||||
reply = [specs: specs, active: active, supervisors: supervisors, workers: workers]
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:terminate_child, pid}, _from, %{children: children} = state) do
|
||||
case children do
|
||||
%{^pid => info} ->
|
||||
:ok = terminate_children(%{pid => info}, state)
|
||||
{:reply, :ok, delete_child(pid, state)}
|
||||
|
||||
%{} ->
|
||||
{:reply, {:error, :not_found}, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({:start_task, args, restart, shutdown}, from, state) do
|
||||
{init_restart, init_shutdown} = Process.get(Task.Supervisor)
|
||||
restart = restart || init_restart
|
||||
shutdown = shutdown || init_shutdown
|
||||
child = {{Task.Supervised, :start_link, args}, restart, shutdown, :worker, [Task.Supervised]}
|
||||
handle_call({:start_child, child}, from, state)
|
||||
end
|
||||
|
||||
def handle_call({:start_child, child}, _from, state) do
|
||||
%{dynamic: dynamic, max_children: max_children} = state
|
||||
|
||||
if dynamic < max_children do
|
||||
handle_start_child(child, %{state | dynamic: dynamic + 1})
|
||||
else
|
||||
{:reply, {:error, :max_children}, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_start_child({{m, f, args} = mfa, restart, shutdown, type, modules}, state) do
|
||||
%{extra_arguments: extra} = state
|
||||
|
||||
case reply = start_child(m, f, extra ++ args) do
|
||||
{:ok, pid, _} ->
|
||||
{:reply, reply, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
|
||||
{:ok, pid} ->
|
||||
{:reply, reply, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
|
||||
_ ->
|
||||
{:reply, reply, update_in(state.dynamic, &(&1 - 1))}
|
||||
end
|
||||
end
|
||||
|
||||
defp start_child(m, f, a) do
|
||||
try do
|
||||
apply(m, f, a)
|
||||
catch
|
||||
kind, reason ->
|
||||
{:error, exit_reason(kind, reason, System.stacktrace())}
|
||||
else
|
||||
{:ok, pid, extra} when is_pid(pid) -> {:ok, pid, extra}
|
||||
{:ok, pid} when is_pid(pid) -> {:ok, pid}
|
||||
:ignore -> :ignore
|
||||
{:error, _} = error -> error
|
||||
other -> {:error, other}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_child(pid, {m, f, _}, :temporary, shutdown, type, modules, state) do
|
||||
put_in(state.children[pid], {{m, f, :undefined}, :temporary, shutdown, type, modules})
|
||||
end
|
||||
|
||||
defp save_child(pid, mfa, restart, shutdown, type, modules, state) do
|
||||
put_in(state.children[pid], {mfa, restart, shutdown, type, modules})
|
||||
end
|
||||
|
||||
defp exit_reason(:exit, reason, _), do: reason
|
||||
defp exit_reason(:error, reason, stack), do: {reason, stack}
|
||||
defp exit_reason(:throw, value, stack), do: {{:nocatch, value}, stack}
|
||||
|
||||
@impl true
|
||||
def handle_cast(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:EXIT, pid, reason}, state) do
|
||||
case maybe_restart_child(pid, reason, state) do
|
||||
{:ok, state} -> {:noreply, state}
|
||||
{:shutdown, state} -> {:stop, :shutdown, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:"$gen_restart", pid}, state) do
|
||||
%{children: children} = state
|
||||
|
||||
case children do
|
||||
%{^pid => restarting_args} ->
|
||||
{:restarting, child} = restarting_args
|
||||
|
||||
case restart_child(pid, child, state) do
|
||||
{:ok, state} -> {:noreply, state}
|
||||
{:shutdown, state} -> {:stop, :shutdown, state}
|
||||
end
|
||||
|
||||
# We may hit clause if we send $gen_restart and then
|
||||
# someone calls terminate_child, removing the child.
|
||||
%{} ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(msg, state) do
|
||||
:error_logger.error_msg('DynamicSupervisor received unexpected message: ~p~n', [msg])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def code_change(_, %{mod: mod, args: args} = state, _) do
|
||||
case mod.init(args) do
|
||||
{:ok, flags} when is_map(flags) ->
|
||||
case init(state, flags) do
|
||||
{:ok, state} -> {:ok, state}
|
||||
{:error, reason} -> {:error, {:supervisor_data, reason}}
|
||||
end
|
||||
|
||||
:ignore ->
|
||||
{:ok, state}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def terminate(_, %{children: children} = state) do
|
||||
:ok = terminate_children(children, state)
|
||||
end
|
||||
|
||||
defp terminate_children(children, state) do
|
||||
{pids, times, stacks} = monitor_children(children)
|
||||
size = map_size(pids)
|
||||
|
||||
timers =
|
||||
Enum.reduce(times, %{}, fn {time, pids}, acc ->
|
||||
Map.put(acc, :erlang.start_timer(time, self(), :kill), pids)
|
||||
end)
|
||||
|
||||
stacks = wait_children(pids, size, timers, stacks)
|
||||
|
||||
for {pid, {child, reason}} <- stacks do
|
||||
report_error(:shutdown_error, reason, pid, child, state)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp monitor_children(children) do
|
||||
Enum.reduce(children, {%{}, %{}, %{}}, fn
|
||||
{_, {:restarting, _}}, acc ->
|
||||
acc
|
||||
|
||||
{pid, {_, restart, _, _, _} = child}, {pids, times, stacks} ->
|
||||
case monitor_child(pid) do
|
||||
:ok ->
|
||||
times = exit_child(pid, child, times)
|
||||
{Map.put(pids, pid, child), times, stacks}
|
||||
|
||||
{:error, :normal} when restart != :permanent ->
|
||||
{pids, times, stacks}
|
||||
|
||||
{:error, reason} ->
|
||||
{pids, times, Map.put(stacks, pid, {child, reason})}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp monitor_child(pid) do
|
||||
ref = Process.monitor(pid)
|
||||
Process.unlink(pid)
|
||||
|
||||
receive do
|
||||
{:EXIT, ^pid, reason} ->
|
||||
receive do
|
||||
{:DOWN, ^ref, :process, ^pid, _} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp exit_child(pid, {_, _, shutdown, _, _}, times) do
|
||||
case shutdown do
|
||||
:brutal_kill ->
|
||||
Process.exit(pid, :kill)
|
||||
times
|
||||
|
||||
:infinity ->
|
||||
Process.exit(pid, :shutdown)
|
||||
times
|
||||
|
||||
time ->
|
||||
Process.exit(pid, :shutdown)
|
||||
Map.update(times, time, [pid], &[pid | &1])
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_children(_pids, 0, timers, stacks) do
|
||||
for {timer, _} <- timers do
|
||||
_ = :erlang.cancel_timer(timer)
|
||||
|
||||
receive do
|
||||
{:timeout, ^timer, :kill} -> :ok
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
stacks
|
||||
end
|
||||
|
||||
defp wait_children(pids, size, timers, stacks) do
|
||||
receive do
|
||||
{:DOWN, _ref, :process, pid, reason} ->
|
||||
case pids do
|
||||
%{^pid => child} ->
|
||||
stacks = wait_child(pid, child, reason, stacks)
|
||||
wait_children(pids, size - 1, timers, stacks)
|
||||
|
||||
%{} ->
|
||||
wait_children(pids, size, timers, stacks)
|
||||
end
|
||||
|
||||
{:timeout, timer, :kill} ->
|
||||
for pid <- Map.fetch!(timers, timer), do: Process.exit(pid, :kill)
|
||||
wait_children(pids, size, Map.delete(timers, timer), stacks)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_child(pid, {_, _, :brutal_kill, _, _} = child, reason, stacks) do
|
||||
case reason do
|
||||
:killed -> stacks
|
||||
_ -> Map.put(stacks, pid, {child, reason})
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_child(pid, {_, restart, _, _, _} = child, reason, stacks) do
|
||||
case reason do
|
||||
{:shutdown, _} -> stacks
|
||||
:shutdown -> stacks
|
||||
:normal when restart != :permanent -> stacks
|
||||
reason -> Map.put(stacks, pid, {child, reason})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_restart_child(pid, reason, %{children: children} = state) do
|
||||
case children do
|
||||
%{^pid => {_, restart, _, _, _} = child} ->
|
||||
maybe_restart_child(restart, reason, pid, child, state)
|
||||
|
||||
%{} ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_restart_child(:permanent, reason, pid, child, state) do
|
||||
report_error(:child_terminated, reason, pid, child, state)
|
||||
restart_child(pid, child, state)
|
||||
end
|
||||
|
||||
defp maybe_restart_child(_, :normal, pid, _child, state) do
|
||||
{:ok, delete_child(pid, state)}
|
||||
end
|
||||
|
||||
defp maybe_restart_child(_, :shutdown, pid, _child, state) do
|
||||
{:ok, delete_child(pid, state)}
|
||||
end
|
||||
|
||||
defp maybe_restart_child(_, {:shutdown, _}, pid, _child, state) do
|
||||
{:ok, delete_child(pid, state)}
|
||||
end
|
||||
|
||||
defp maybe_restart_child(:transient, reason, pid, child, state) do
|
||||
report_error(:child_terminated, reason, pid, child, state)
|
||||
restart_child(pid, child, state)
|
||||
end
|
||||
|
||||
defp maybe_restart_child(:temporary, reason, pid, child, state) do
|
||||
report_error(:child_terminated, reason, pid, child, state)
|
||||
{:ok, delete_child(pid, state)}
|
||||
end
|
||||
|
||||
defp delete_child(pid, state) do
|
||||
%{children: children, dynamic: dynamic} = state
|
||||
%{state | children: Map.delete(children, pid), dynamic: dynamic - 1}
|
||||
end
|
||||
|
||||
defp restart_child(pid, child, state) do
|
||||
case add_restart(state) do
|
||||
{:ok, %{strategy: strategy} = state} ->
|
||||
case restart_child(strategy, pid, child, state) do
|
||||
{:ok, state} ->
|
||||
{:ok, state}
|
||||
|
||||
{:try_again, state} ->
|
||||
send(self(), {:"$gen_restart", pid})
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
{:shutdown, state} ->
|
||||
report_error(:shutdown, :reached_max_restart_intensity, pid, child, state)
|
||||
{:shutdown, delete_child(pid, state)}
|
||||
end
|
||||
end
|
||||
|
||||
defp add_restart(state) do
|
||||
%{max_seconds: max_seconds, max_restarts: max_restarts, restarts: restarts} = state
|
||||
|
||||
# The below is equivalent to 1 second. We avoid
|
||||
# :second because of incompatibilties with OTP < 20
|
||||
now = :erlang.monotonic_time(1)
|
||||
restarts = add_restart([now | restarts], now, max_seconds)
|
||||
state = %{state | restarts: restarts}
|
||||
|
||||
if length(restarts) <= max_restarts do
|
||||
{:ok, state}
|
||||
else
|
||||
{:shutdown, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp add_restart(restarts, now, period) do
|
||||
for then <- restarts, now <= then + period, do: then
|
||||
end
|
||||
|
||||
defp restart_child(:one_for_one, current_pid, child, state) do
|
||||
{{m, f, args} = mfa, restart, shutdown, type, modules} = child
|
||||
|
||||
case start_child(m, f, args) do
|
||||
{:ok, pid, _} ->
|
||||
state = delete_child(current_pid, state)
|
||||
{:ok, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
|
||||
{:ok, pid} ->
|
||||
state = delete_child(current_pid, state)
|
||||
{:ok, save_child(pid, mfa, restart, shutdown, type, modules, state)}
|
||||
|
||||
:ignore ->
|
||||
{:ok, delete_child(current_pid, state)}
|
||||
|
||||
{:error, reason} ->
|
||||
report_error(:start_error, reason, {:restarting, current_pid}, child, state)
|
||||
state = put_in(state.children[current_pid], {:restarting, child})
|
||||
{:try_again, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp report_error(error, reason, pid, child, %{name: name}) do
|
||||
:error_logger.error_report(
|
||||
:supervision_report,
|
||||
supervisor: name,
|
||||
errorContext: error,
|
||||
reason: reason,
|
||||
offender: extract_child(pid, child)
|
||||
)
|
||||
end
|
||||
|
||||
defp extract_child(pid, {mfa, restart, shutdown, type, _modules}) do
|
||||
[
|
||||
pid: pid,
|
||||
id: :undefined,
|
||||
mfargs: mfa,
|
||||
restart_type: restart,
|
||||
shutdown: shutdown,
|
||||
child_type: type
|
||||
]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def format_status(:terminate, [_pdict, state]) do
|
||||
state
|
||||
end
|
||||
|
||||
def format_status(_, [_pdict, %{mod: mod} = state]) do
|
||||
[data: [{~c"State", state}], supervisor: [{~c"Callback", mod}]]
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
+567
-663
File diff suppressed because it is too large
Load Diff
+153
-245
@@ -16,10 +16,10 @@ defmodule Exception do
|
||||
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => true,
|
||||
optional(atom) => any
|
||||
}
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => true,
|
||||
atom => any
|
||||
}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | non_error_kind
|
||||
@@ -27,50 +27,41 @@ defmodule Exception do
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
@type stacktrace_entry ::
|
||||
{module, atom, arity_or_args, location}
|
||||
| {(... -> any), arity_or_args, location}
|
||||
{module, atom, arity_or_args, location} |
|
||||
{(... -> any), arity_or_args, location}
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: keyword
|
||||
|
||||
@callback exception(term) :: t
|
||||
@callback message(t) :: String.t()
|
||||
|
||||
@doc """
|
||||
Called from `Exception.blame/3` to augment the exception struct.
|
||||
|
||||
Can be used to collect additional information about the exception
|
||||
or do some additional expensive computation.
|
||||
"""
|
||||
@callback blame(t, stacktrace) :: {t, stacktrace}
|
||||
@optional_callbacks [blame: 2]
|
||||
@callback message(t) :: String.t
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given `term` is an exception.
|
||||
"""
|
||||
def exception?(term)
|
||||
def exception?(%_{__exception__: true}), do: true
|
||||
|
||||
def exception?(%{__struct__: struct, __exception__: true}) when is_atom(struct),
|
||||
do: true
|
||||
|
||||
def exception?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the message for an `exception`.
|
||||
"""
|
||||
def message(%module{__exception__: true} = exception) do
|
||||
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
|
||||
try do
|
||||
module.message(exception)
|
||||
rescue
|
||||
caught_exception ->
|
||||
"got #{inspect(caught_exception.__struct__)} with message " <>
|
||||
"#{inspect(message(caught_exception))} while retrieving Exception.message/1 " <>
|
||||
"for #{inspect(exception)}"
|
||||
e ->
|
||||
"got #{inspect e.__struct__} with message #{inspect message(e)} " <>
|
||||
"while retrieving Exception.message/1 for #{inspect(exception)}"
|
||||
else
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
result ->
|
||||
"got #{inspect(result)} " <>
|
||||
"while retrieving Exception.message/1 for #{inspect(exception)} " <>
|
||||
"(expected a string)"
|
||||
x when is_binary(x) -> x
|
||||
x ->
|
||||
"got #{inspect(x)} " <>
|
||||
"while retrieving Exception.message/1 for #{inspect(exception)} " <>
|
||||
"(expected a string)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -121,7 +112,7 @@ defmodule Exception do
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec format_banner(kind, any, stacktrace | nil) :: String.t()
|
||||
@spec format_banner(kind, any, stacktrace | nil) :: String.t
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
@@ -130,7 +121,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
def format_banner(:throw, reason, _stacktrace) do
|
||||
"** (throw) " <> inspect(reason)
|
||||
"** (throw) " <> inspect(reason)
|
||||
end
|
||||
|
||||
def format_banner(:exit, reason, _stacktrace) do
|
||||
@@ -138,7 +129,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
def format_banner({:EXIT, pid}, reason, _stacktrace) do
|
||||
"** (EXIT from #{inspect(pid)}) " <> format_exit(reason, <<"\n ">>)
|
||||
"** (EXIT from #{inspect pid}) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -150,7 +141,7 @@ defmodule Exception do
|
||||
Note that `{:EXIT, pid}` do not generate a stacktrace though
|
||||
(as they are retrieved as messages without stacktraces).
|
||||
"""
|
||||
@spec format(kind, any, stacktrace | nil) :: String.t()
|
||||
@spec format(kind, any, stacktrace | nil) :: String.t
|
||||
def format(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def format({:EXIT, _} = kind, any, _) do
|
||||
@@ -158,12 +149,11 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
def format(kind, payload, stacktrace) do
|
||||
stacktrace = stacktrace || System.stacktrace()
|
||||
stacktrace = stacktrace || System.stacktrace
|
||||
message = format_banner(kind, payload, stacktrace)
|
||||
|
||||
case stacktrace do
|
||||
[] -> message
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -172,22 +162,22 @@ defmodule Exception do
|
||||
|
||||
This operation is potentially expensive, as it reads data
|
||||
from the filesystem, parse beam files, evaluates code and
|
||||
so on.
|
||||
so on. Currently the following exceptions may be annotated:
|
||||
|
||||
* `FunctionClauseError` - annotated with the arguments
|
||||
used on the call and available clauses
|
||||
|
||||
If the exception module implements the optional `c:blame/2`
|
||||
callbak, it will be invoked to perform the computation.
|
||||
"""
|
||||
@spec blame(:error, any, stacktrace) :: {t, stacktrace}
|
||||
@spec blame(non_error_kind, payload, stacktrace) :: {payload, stacktrace} when payload: var
|
||||
def blame(kind, error, stacktrace)
|
||||
|
||||
def blame(:error, error, stacktrace) do
|
||||
%module{} = struct = normalize(:error, error, stacktrace)
|
||||
|
||||
if Code.ensure_loaded?(module) and function_exported?(module, :blame, 2) do
|
||||
module.blame(struct, stacktrace)
|
||||
else
|
||||
{struct, stacktrace}
|
||||
case normalize(:error, error, stacktrace) do
|
||||
%{__struct__: FunctionClauseError} = struct ->
|
||||
blame_function_clause_error(struct, stacktrace)
|
||||
_ ->
|
||||
{error, stacktrace}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -195,6 +185,20 @@ defmodule Exception do
|
||||
{reason, stacktrace}
|
||||
end
|
||||
|
||||
defp blame_function_clause_error(%{module: module, function: function, arity: arity} = exception,
|
||||
[{module, function, args, meta} | rest])
|
||||
when length(args) == arity do
|
||||
exception =
|
||||
case blame_mfa(module, function, args) do
|
||||
{:ok, kind, clauses} -> %{exception | args: args, kind: kind, clauses: clauses}
|
||||
:error -> %{exception | args: args}
|
||||
end
|
||||
{exception, [{module, function, arity, meta} | rest]}
|
||||
end
|
||||
defp blame_function_clause_error(exception, stacktrace) do
|
||||
{exception, stacktrace}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Blames the invocation of the given module, function and arguments.
|
||||
|
||||
@@ -210,10 +214,8 @@ defmodule Exception do
|
||||
is always returned.
|
||||
"""
|
||||
@spec blame_mfa(module, function, args :: [term]) ::
|
||||
{:ok, :def | :defp | :defmacro | :defmacrop, [{args :: [term], guards :: [term]}]}
|
||||
| :error
|
||||
def blame_mfa(module, function, args)
|
||||
when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
{:ok, :def | :defp | :defmacro | :defmacrop, [{args :: [term], guards :: [term]}]} | :error
|
||||
def blame_mfa(module, function, args) when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
try do
|
||||
blame_mfa(module, function, length(args), args)
|
||||
rescue
|
||||
@@ -222,27 +224,22 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp blame_mfa(module, function, arity, call_args) do
|
||||
with [_ | _] = path <- :code.which(module),
|
||||
{:ok, {_, [debug_info: debug_info]}} <- :beam_lib.chunks(path, [:debug_info]),
|
||||
{:debug_info_v1, backend, data} <- debug_info,
|
||||
with path when is_list(path) <- :code.which(module),
|
||||
{:ok, {_, [debug_info: {:debug_info_v1, backend, data}]}} <- :beam_lib.chunks(path, [:debug_info]),
|
||||
{:ok, %{definitions: defs}} <- backend.debug_info(:elixir_v1, module, data, []),
|
||||
{_, kind, _, clauses} <- List.keyfind(defs, {function, arity}, 0) do
|
||||
clauses =
|
||||
for {meta, ex_args, guards, _block} <- clauses do
|
||||
scope = :elixir_erl.definition_scope(meta, "nofile")
|
||||
|
||||
{erl_args, scope} =
|
||||
:elixir_erl_clauses.match(&:elixir_erl_pass.translate_args/2, ex_args, scope)
|
||||
|
||||
{args, binding} =
|
||||
[call_args, ex_args, erl_args]
|
||||
|> Enum.zip()
|
||||
|> Enum.map_reduce([], &blame_arg/2)
|
||||
|
||||
guards = Enum.map(guards, &blame_guard(&1, scope, binding))
|
||||
{args, guards}
|
||||
end
|
||||
|
||||
{:ok, kind, clauses}
|
||||
else
|
||||
_ -> :error
|
||||
@@ -256,7 +253,6 @@ defmodule Exception do
|
||||
|
||||
defp blame_arg(erl_arg, call_arg, binding) do
|
||||
binding = :orddict.store(:VAR, call_arg, binding)
|
||||
|
||||
try do
|
||||
{:value, _, binding} = :erl_eval.expr({:match, 0, erl_arg, {:var, 0, :VAR}}, binding, :none)
|
||||
{true, binding}
|
||||
@@ -269,7 +265,6 @@ defmodule Exception do
|
||||
Macro.prewalk(arg, fn
|
||||
{:%{}, meta, [__struct__: Range, first: first, last: last]} ->
|
||||
{:.., meta, [first, last]}
|
||||
|
||||
other ->
|
||||
other
|
||||
end)
|
||||
@@ -277,17 +272,14 @@ defmodule Exception do
|
||||
|
||||
defp blame_guard({{:., _, [:erlang, op]}, meta, [left, right]}, scope, binding)
|
||||
when op == :andalso or op == :orelse do
|
||||
guards = [
|
||||
{rewrite_guard_call(op), meta, [
|
||||
blame_guard(left, scope, binding),
|
||||
blame_guard(right, scope, binding)
|
||||
]
|
||||
|
||||
{rewrite_guard_call(op), meta, guards}
|
||||
]}
|
||||
end
|
||||
|
||||
defp blame_guard(ex_guard, scope, binding) do
|
||||
{erl_guard, _} = :elixir_erl_pass.translate(ex_guard, scope)
|
||||
|
||||
match? =
|
||||
try do
|
||||
{:value, true, _} = :erl_eval.expr(erl_guard, binding, :none)
|
||||
@@ -295,28 +287,18 @@ defmodule Exception do
|
||||
rescue
|
||||
_ -> false
|
||||
end
|
||||
|
||||
blame_wrap(match?, rewrite_guard(ex_guard))
|
||||
end
|
||||
|
||||
defp rewrite_guard(guard) do
|
||||
Macro.prewalk(guard, fn
|
||||
{{:., _, [:erlang, :element]}, _, [{{:., _, [:erlang, :+]}, _, [int, 1]}, arg]} ->
|
||||
{:elem, [], [arg, int]}
|
||||
|
||||
{{:., _, [:erlang, :element]}, _, [int, arg]} when is_integer(int) ->
|
||||
{:elem, [], [arg, int - 1]}
|
||||
|
||||
{:., _, [:erlang, call]} ->
|
||||
rewrite_guard_call(call)
|
||||
|
||||
other ->
|
||||
other
|
||||
{:., _, [:erlang, call]} -> rewrite_guard_call(call)
|
||||
other -> other
|
||||
end)
|
||||
end
|
||||
|
||||
defp rewrite_guard_call(:orelse), do: :or
|
||||
defp rewrite_guard_call(:andalso), do: :and
|
||||
defp rewrite_guard_call(:"orelse"), do: :or
|
||||
defp rewrite_guard_call(:"andalso"), do: :and
|
||||
defp rewrite_guard_call(:"=<"), do: :<=
|
||||
defp rewrite_guard_call(:"/="), do: :!=
|
||||
defp rewrite_guard_call(:"=:="), do: :===
|
||||
@@ -324,9 +306,10 @@ defmodule Exception do
|
||||
|
||||
defp rewrite_guard_call(op) when op in [:band, :bor, :bnot, :bsl, :bsr, :bxor],
|
||||
do: {:., [], [Bitwise, op]}
|
||||
|
||||
defp rewrite_guard_call(op) when op in [:xor, :element, :size], do: {:., [], [:erlang, op]}
|
||||
defp rewrite_guard_call(op), do: op
|
||||
defp rewrite_guard_call(op) when op in [:xor, :element, :size],
|
||||
do: {:., [], [:erlang, op]}
|
||||
defp rewrite_guard_call(op),
|
||||
do: op
|
||||
|
||||
defp blame_wrap(match?, ast), do: %{match?: match?, node: ast}
|
||||
|
||||
@@ -338,60 +321,60 @@ defmodule Exception do
|
||||
formats exits in a way to nicely show the exit reason, caller
|
||||
and stacktrace.
|
||||
"""
|
||||
@spec format_exit(any) :: String.t()
|
||||
@spec format_exit(any) :: String.t
|
||||
def format_exit(reason) do
|
||||
format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
# 2-Tuple could be caused by an error if the second element is a stacktrace.
|
||||
defp format_exit({exception, maybe_stacktrace} = reason, joiner)
|
||||
when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
|
||||
when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
|
||||
try do
|
||||
Enum.map(maybe_stacktrace, &format_stacktrace_entry/1)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not a stacktrace, was an exit.
|
||||
format_exit_reason(reason)
|
||||
else
|
||||
formatted_stacktrace ->
|
||||
# Assume a non-empty list formattable as stacktrace is a
|
||||
# stacktrace, so exit was caused by an error.
|
||||
message =
|
||||
"an exception was raised:" <>
|
||||
joiner <> format_banner(:error, exception, maybe_stacktrace)
|
||||
|
||||
message = "an exception was raised:" <> joiner <>
|
||||
format_banner(:error, exception, maybe_stacktrace)
|
||||
Enum.join([message | formatted_stacktrace], joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not a stacktrace, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link raises.
|
||||
defp format_exit({:shutdown, {:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
|
||||
defp format_exit({:shutdown,
|
||||
{:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link returns {:error, reason}.
|
||||
defp format_exit({:shutdown, {:failed_to_start_child, child, reason}}, joiner) do
|
||||
defp format_exit({:shutdown, {:failed_to_start_child, child, reason}},
|
||||
joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# 2-Tuple could be an exit caused by mfa if second element is mfa, args
|
||||
# must be a list of arguments - max length 255 due to max arity.
|
||||
defp format_exit({reason2, {mod, fun, args}} = reason, joiner)
|
||||
when length(args) < 256 do
|
||||
when length(args) < 256 do
|
||||
try do
|
||||
format_mfa(mod, fun, args)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not an mfa, was an exit.
|
||||
format_exit_reason(reason)
|
||||
else
|
||||
mfa ->
|
||||
# Assume tuple formattable as an mfa is an mfa, so exit was caused by
|
||||
# failed mfa.
|
||||
"exited in: " <>
|
||||
mfa <> joiner <> "** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
|
||||
"exited in: " <> mfa <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not an mfa, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -441,7 +424,7 @@ defmodule Exception do
|
||||
|
||||
# If value is a list will be formatted by mfa exit in format_exit/1
|
||||
defp format_exit_reason({:bad_return, {mod, :init, value}})
|
||||
when is_atom(mod) do
|
||||
when is_atom(mod) do
|
||||
format_mfa(mod, :init, 1) <> " returned a bad value: " <> inspect(value)
|
||||
end
|
||||
|
||||
@@ -460,8 +443,8 @@ defmodule Exception do
|
||||
defp format_exit_reason(reason), do: inspect(reason)
|
||||
|
||||
defp format_start_child(child, reason, joiner) do
|
||||
"shutdown: failed to start child: " <>
|
||||
inspect(child) <> joiner <> "** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
|
||||
"shutdown: failed to start child: " <> inspect(child) <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_type, type}) do
|
||||
@@ -480,19 +463,11 @@ defmodule Exception do
|
||||
"invalid max_seconds (period): " <> inspect(period)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_max_children, max_children}) do
|
||||
"invalid max_children: " <> inspect(max_children)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_extra_arguments, extra}) do
|
||||
"invalid extra_arguments: " <> inspect(extra)
|
||||
end
|
||||
|
||||
defp format_sup_data(other), do: "got: #{inspect(other)}"
|
||||
defp format_sup_data(other), do: "got: #{inspect other}"
|
||||
|
||||
defp format_sup_spec({:duplicate_child_name, id}) do
|
||||
"""
|
||||
more than one child specification has the id: #{inspect(id)}.
|
||||
more than one child specification has the id: #{inspect id}.
|
||||
If using maps as child specifications, make sure the :id keys are unique.
|
||||
If using a module or {module, arg} as child, use Supervisor.child_spec/2 to change the :id, for example:
|
||||
|
||||
@@ -504,39 +479,39 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_child_spec, child_spec}) do
|
||||
"invalid child specification: #{inspect(child_spec)}"
|
||||
"invalid child specification: #{inspect child_spec}"
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_child_type, type}) do
|
||||
"invalid child type: #{inspect(type)}. Must be :worker or :supervisor."
|
||||
"invalid child type: #{inspect type}. Must be :worker or :supervisor."
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_mfa, mfa}) do
|
||||
"invalid mfa: #{inspect(mfa)}"
|
||||
"invalid mfa: #{inspect mfa}"
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_restart_type, restart}) do
|
||||
"invalid restart type: #{inspect(restart)}. Must be :permanent, :transient or :temporary."
|
||||
"invalid restart type: #{inspect restart}. Must be :permanent, :transient or :temporary."
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_shutdown, shutdown}) do
|
||||
"invalid shutdown: #{inspect(shutdown)}. Must be an integer >= 0, :infinity or :brutal_kill."
|
||||
"invalid shutdown: #{inspect shutdown}. Must be an integer >= 0, :infinity or :brutal_kill."
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_module, mod}) do
|
||||
"invalid module: #{inspect(mod)}. Must be an atom."
|
||||
"invalid module: #{inspect mod}. Must be an atom."
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_modules, modules}) do
|
||||
"invalid modules: #{inspect(modules)}. Must be a list of atoms or :dynamic."
|
||||
"invalid modules: #{inspect modules}. Must be a list of atoms or :dynamic."
|
||||
end
|
||||
|
||||
defp format_sup_spec(other), do: "got: #{inspect(other)}"
|
||||
defp format_sup_spec(other), do: "got: #{inspect other}"
|
||||
|
||||
@doc """
|
||||
Receives a stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t()
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
@@ -577,14 +552,9 @@ defmodule Exception do
|
||||
is retrieved from `Process.info/2`.
|
||||
"""
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace =
|
||||
if trace do
|
||||
trace
|
||||
else
|
||||
case Process.info(self(), :current_stacktrace) do
|
||||
{:current_stacktrace, t} -> Enum.drop(t, 3)
|
||||
end
|
||||
end
|
||||
trace = trace || case Process.info(self(), :current_stacktrace) do
|
||||
{:current_stacktrace, t} -> Enum.drop(t, 3)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
@@ -603,7 +573,7 @@ defmodule Exception do
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) when is_function(fun) do
|
||||
"#{inspect(fun)}#{format_arity(arity)}"
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -627,15 +597,12 @@ defmodule Exception do
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) when is_atom(module) and is_atom(fun) do
|
||||
case Code.Identifier.extract_anonymous_fun_parent(fun) do
|
||||
case Inspect.Function.extract_anonymous_fun_parent(Atom.to_string(fun)) do
|
||||
{outer_name, outer_arity} ->
|
||||
"anonymous fn#{format_arity(arity)} in " <>
|
||||
"#{Code.Identifier.inspect_as_atom(module)}." <>
|
||||
"#{Code.Identifier.inspect_as_function(outer_name)}/#{outer_arity}"
|
||||
|
||||
"#{inspect(module)}.#{Inspect.Function.escape_name(outer_name)}/#{outer_arity}"
|
||||
:error ->
|
||||
"#{Code.Identifier.inspect_as_atom(module)}." <>
|
||||
"#{Code.Identifier.inspect_as_function(fun)}#{format_arity(arity)}"
|
||||
"#{inspect(module)}.#{Inspect.Function.escape_name(fun)}#{format_arity(arity)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -677,7 +644,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_location(opts) when is_list(opts) do
|
||||
format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line), " ")
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
end
|
||||
|
||||
@@ -719,7 +686,8 @@ defmodule TokenMissingError do
|
||||
defexception [:file, :line, description: "expression is incomplete"]
|
||||
|
||||
def message(%{file: file, line: line, description: description}) do
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <> " " <> description
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <>
|
||||
" " <> description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -727,7 +695,8 @@ defmodule CompileError do
|
||||
defexception [:file, :line, description: "compile error"]
|
||||
|
||||
def message(%{file: file, line: line, description: description}) do
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <> " " <> description
|
||||
Exception.format_file_line(file && Path.relative_to_cwd(file), line) <>
|
||||
" " <> description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -807,7 +776,7 @@ defmodule BadArityError do
|
||||
defexception [:function, :args]
|
||||
|
||||
def message(exception) do
|
||||
fun = exception.function
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
@@ -815,8 +784,8 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
@@ -826,48 +795,29 @@ defmodule UndefinedFunctionError do
|
||||
cond do
|
||||
is_nil(function) or is_nil(arity) ->
|
||||
"undefined function"
|
||||
|
||||
not is_nil(module) and :code.is_loaded(module) == false ->
|
||||
not is_nil(module) and :code.is_loaded(module) === false ->
|
||||
message(%{e | reason: :"module could not be loaded"})
|
||||
|
||||
true ->
|
||||
message(%{e | reason: :"function not exported"})
|
||||
end
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"module could not be loaded",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
"function #{formatted_fun} is undefined (module #{inspect(module)} is not available)"
|
||||
def message(%{reason: :"module could not be loaded", module: module, function: function, arity: arity}) do
|
||||
"function " <> Exception.format_mfa(module, function, arity) <>
|
||||
" is undefined (module #{inspect module} is not available)"
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"function not exported",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
def message(%{reason: :"function not exported", module: module, function: function, arity: arity}) do
|
||||
IO.iodata_to_binary(function_not_exported(module, function, arity, nil))
|
||||
end
|
||||
|
||||
def message(%{
|
||||
reason: :"function not available",
|
||||
module: module,
|
||||
function: function,
|
||||
arity: arity
|
||||
}) do
|
||||
def message(%{reason: :"function not available", module: module, function: function, arity: arity}) do
|
||||
"nil." <> fa = Exception.format_mfa(nil, function, arity)
|
||||
|
||||
"function " <>
|
||||
Exception.format_mfa(module, function, arity) <>
|
||||
" is undefined (function #{fa} is not available)"
|
||||
"function " <> Exception.format_mfa(module, function, arity) <>
|
||||
" is undefined (function #{fa} is not available)"
|
||||
end
|
||||
|
||||
def message(%{reason: reason, module: module, function: function, arity: arity}) do
|
||||
def message(%{reason: reason, module: module, function: function, arity: arity}) do
|
||||
"function " <> Exception.format_mfa(module, function, arity) <> " is undefined (#{reason})"
|
||||
end
|
||||
|
||||
@@ -881,12 +831,7 @@ defmodule UndefinedFunctionError do
|
||||
did_you_mean(module, function, exports)
|
||||
end
|
||||
|
||||
[
|
||||
"function ",
|
||||
Exception.format_mfa(module, function, arity),
|
||||
" is undefined or private",
|
||||
suffix
|
||||
]
|
||||
["function ", Exception.format_mfa(module, function, arity), " is undefined or private", suffix]
|
||||
end
|
||||
|
||||
@function_threshold 0.77
|
||||
@@ -899,27 +844,25 @@ defmodule UndefinedFunctionError do
|
||||
case Keyword.take(exports, [function]) do
|
||||
[] ->
|
||||
base = Atom.to_string(function)
|
||||
|
||||
for {key, val} <- exports,
|
||||
dist = String.jaro_distance(base, Atom.to_string(key)),
|
||||
dist >= @function_threshold,
|
||||
do: {dist, key, val}
|
||||
|
||||
do: {dist, key, val}
|
||||
arities ->
|
||||
for {key, val} <- arities, do: {1.0, key, val}
|
||||
end
|
||||
|> Enum.sort(&(elem(&1, 0) >= elem(&2, 0)))
|
||||
|> Enum.sort(&elem(&1, 0) >= elem(&2, 0))
|
||||
|> Enum.take(@max_suggestions)
|
||||
|> Enum.sort(&(elem(&1, 1) <= elem(&2, 1)))
|
||||
|> Enum.sort(&elem(&1, 1) <= elem(&2, 1))
|
||||
|
||||
case result do
|
||||
[] -> []
|
||||
[] -> []
|
||||
suggestions -> [". Did you mean one of:\n\n" | Enum.map(suggestions, &format_fa/1)]
|
||||
end
|
||||
end
|
||||
|
||||
defp format_fa({_dist, fun, arity}) do
|
||||
[" * ", Code.Identifier.inspect_as_function(fun), ?/, Integer.to_string(arity), ?\n]
|
||||
[" * ", Inspect.Function.escape_name(fun), ?/, Integer.to_string(arity), ?\n]
|
||||
end
|
||||
|
||||
defp exports_for(module) do
|
||||
@@ -930,8 +873,7 @@ defmodule UndefinedFunctionError do
|
||||
end
|
||||
rescue
|
||||
# In case the module was removed while we are computing this
|
||||
UndefinedFunctionError ->
|
||||
[]
|
||||
UndefinedFunctionError -> []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -942,79 +884,49 @@ defmodule FunctionClauseError do
|
||||
case exception do
|
||||
%{function: nil} ->
|
||||
"no function clause matches"
|
||||
|
||||
%{module: module, function: function, arity: arity} ->
|
||||
formatted = Exception.format_mfa(module, function, arity)
|
||||
blamed = blame(exception, &inspect/1, &blame_match/2)
|
||||
"no function clause matching in #{formatted}" <> blamed
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
"no function clause matching in #{formatted}" <> blame(exception, &inspect/1, &blame_match/2)
|
||||
end
|
||||
end
|
||||
|
||||
def blame(%{module: module, function: function, arity: arity} = exception, stacktrace) do
|
||||
case stacktrace do
|
||||
[{^module, ^function, args, meta} | rest] when length(args) == arity ->
|
||||
exception =
|
||||
case Exception.blame_mfa(module, function, args) do
|
||||
{:ok, kind, clauses} -> %{exception | args: args, kind: kind, clauses: clauses}
|
||||
:error -> %{exception | args: args}
|
||||
end
|
||||
|
||||
{exception, [{module, function, arity, meta} | rest]}
|
||||
|
||||
stacktrace ->
|
||||
{exception, stacktrace}
|
||||
end
|
||||
end
|
||||
|
||||
defp blame_match(%{match?: true, node: node}, _), do: Macro.to_string(node)
|
||||
defp blame_match(%{match?: false, node: node}, _), do: "-" <> Macro.to_string(node) <> "-"
|
||||
defp blame_match(_, string), do: string
|
||||
defp blame_match(%{match?: true, node: node}, _),
|
||||
do: Macro.to_string(node)
|
||||
defp blame_match(%{match?: false, node: node}, _),
|
||||
do: "-" <> Macro.to_string(node) <> "-"
|
||||
defp blame_match(_, string),
|
||||
do: string
|
||||
|
||||
@doc false
|
||||
def blame(%{args: nil}, _, _) do
|
||||
""
|
||||
end
|
||||
|
||||
def blame(exception, inspect_fun, ast_fun) do
|
||||
%{module: module, function: function, arity: arity, kind: kind, args: args, clauses: clauses} =
|
||||
exception
|
||||
|
||||
def blame(%{module: module, function: function, arity: arity,
|
||||
kind: kind, args: args, clauses: clauses}, inspect_fun, ast_fun) do
|
||||
mfa = Exception.format_mfa(module, function, arity)
|
||||
|
||||
formatted_args =
|
||||
args
|
||||
|> Enum.with_index(1)
|
||||
|> Enum.map(fn {arg, i} ->
|
||||
["\n # ", Integer.to_string(i), "\n ", pad(inspect_fun.(arg)), "\n"]
|
||||
end)
|
||||
|> Enum.map(fn {arg, i} -> "\n # #{i}\n #{inspect_fun.(arg)}\n" end)
|
||||
|
||||
formatted_clauses =
|
||||
if clauses do
|
||||
format_clause_fun = fn {args, guards} ->
|
||||
code = Enum.reduce(guards, {function, [], args}, &{:when, [], [&2, &1]})
|
||||
" #{kind} " <> Macro.to_string(code, ast_fun) <> "\n"
|
||||
end
|
||||
|
||||
top_10 =
|
||||
clauses
|
||||
|> Enum.take(10)
|
||||
|> Enum.map(format_clause_fun)
|
||||
|> Enum.map(fn {args, guards} ->
|
||||
code = Enum.reduce(guards, {function, [], args}, &{:when, [], [&2, &1]})
|
||||
" #{kind} " <> Macro.to_string(code, ast_fun) <> "\n"
|
||||
end)
|
||||
|
||||
[
|
||||
"\nAttempted function clauses (showing #{length(top_10)} out of #{length(clauses)}):",
|
||||
"\n\n",
|
||||
top_10
|
||||
]
|
||||
"\nAttempted function clauses (showing #{length(top_10)} out of #{length(clauses)}):\n\n#{top_10}"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
"\n\nThe following arguments were given to #{mfa}:\n#{formatted_args}#{formatted_clauses}"
|
||||
end
|
||||
|
||||
defp pad(string) do
|
||||
String.replace(string, "\n", "\n ")
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Code.LoadError do
|
||||
@@ -1030,7 +942,7 @@ defmodule Protocol.UndefinedError do
|
||||
defexception [:protocol, :value, description: ""]
|
||||
|
||||
def message(%{protocol: protocol, value: value, description: description}) do
|
||||
"protocol #{inspect(protocol)} not implemented for #{inspect(value)}" <>
|
||||
"protocol #{inspect protocol} not implemented for #{inspect value}" <>
|
||||
maybe_description(description) <> maybe_available(protocol)
|
||||
end
|
||||
|
||||
@@ -1041,10 +953,8 @@ defmodule Protocol.UndefinedError do
|
||||
case protocol.__protocol__(:impls) do
|
||||
{:consolidated, []} ->
|
||||
". There are no implementations for this protocol."
|
||||
|
||||
{:consolidated, types} ->
|
||||
". This protocol is implemented for: #{Enum.map_join(types, ", ", &inspect/1)}"
|
||||
|
||||
:not_consolidated ->
|
||||
""
|
||||
end
|
||||
@@ -1055,10 +965,9 @@ defmodule KeyError do
|
||||
defexception [:key, :term]
|
||||
|
||||
def message(exception) do
|
||||
msg = "key #{inspect(exception.key)} not found"
|
||||
|
||||
msg = "key #{inspect exception.key} not found"
|
||||
if exception.term != nil do
|
||||
msg <> " in: #{inspect(exception.term)}"
|
||||
msg <> " in: #{inspect exception.term}"
|
||||
else
|
||||
msg
|
||||
end
|
||||
@@ -1071,12 +980,12 @@ defmodule UnicodeConversionError do
|
||||
def exception(opts) do
|
||||
%UnicodeConversionError{
|
||||
encoded: Keyword.fetch!(opts, :encoded),
|
||||
message: "#{Keyword.fetch!(opts, :kind)} #{detail(Keyword.fetch!(opts, :rest))}"
|
||||
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
|
||||
}
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect(rest)}"
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h | _]) when is_integer(h) do
|
||||
@@ -1104,7 +1013,6 @@ defmodule File.Error do
|
||||
case {action, reason} do
|
||||
{"remove directory", :eexist} ->
|
||||
"directory is not empty"
|
||||
|
||||
_ ->
|
||||
IO.iodata_to_binary(:file.format_error(reason))
|
||||
end
|
||||
@@ -1117,10 +1025,11 @@ defmodule File.CopyError do
|
||||
defexception [:reason, :source, :destination, on: "", action: ""]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
formatted =
|
||||
IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
location =
|
||||
case exception.on() do
|
||||
case exception.on do
|
||||
"" -> ""
|
||||
on -> ". #{on}"
|
||||
end
|
||||
@@ -1134,8 +1043,8 @@ defmodule File.LinkError do
|
||||
defexception [:reason, :existing, :new, action: ""]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
formatted =
|
||||
IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
"could not #{exception.action} from #{inspect(exception.existing)} to " <>
|
||||
"#{inspect(exception.new)}: #{formatted}"
|
||||
end
|
||||
@@ -1198,7 +1107,6 @@ defmodule ErlangError do
|
||||
[{:maps, :get, [_, map], _} | _] -> map
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
%KeyError{key: key, term: term}
|
||||
end
|
||||
|
||||
|
||||
+151
-313
File diff suppressed because it is too large
Load Diff
@@ -53,9 +53,9 @@ defmodule File.Stat do
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
@type t :: %__MODULE__{}
|
||||
@@ -71,7 +71,6 @@ defmodule File.Stat do
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record(file_info)
|
||||
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule File.Stream do
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given number of bytes
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
@@ -27,7 +27,6 @@ defmodule File.Stream do
|
||||
else
|
||||
[:raw, :read_ahead | modes]
|
||||
end
|
||||
|
||||
false ->
|
||||
modes
|
||||
end
|
||||
@@ -42,7 +41,6 @@ defmodule File.Stream do
|
||||
case :file.open(path, [:write | modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
@@ -52,16 +50,14 @@ defmodule File.Stream do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
|
||||
:ok, :done ->
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
:ok = :file.close(device)
|
||||
stream
|
||||
|
||||
:ok, :halt ->
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
@@ -74,19 +70,19 @@ defmodule File.Stream do
|
||||
@read_ahead_size 64 * 1024
|
||||
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
start_fun = fn ->
|
||||
case :file.open(path, read_modes(modes)) do
|
||||
{:ok, device} ->
|
||||
if :trim_bom in modes, do: trim_bom(device), else: device
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, read_modes(modes)) do
|
||||
{:ok, device} ->
|
||||
if :trim_bom in modes, do: trim_bom(device), else: device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
@@ -100,7 +96,6 @@ defmodule File.Stream do
|
||||
case File.open(path, read_modes(modes), counter) do
|
||||
{:ok, count} ->
|
||||
{:ok, count}
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
@@ -110,10 +105,8 @@ defmodule File.Stream do
|
||||
case File.stat(path) do
|
||||
{:ok, %{size: 0}} ->
|
||||
{:error, __MODULE__}
|
||||
|
||||
{:ok, %{size: size}} ->
|
||||
{:ok, div(size, bytes) + if(rem(size, bytes) == 0, do: 0, else: 1)}
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
@@ -123,22 +116,24 @@ defmodule File.Stream do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def slice(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp trim_bom(device) do
|
||||
header = IO.binread(device, 4)
|
||||
{:ok, _new_pos} = :file.position(device, bom_length(header))
|
||||
device
|
||||
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
|
||||
defp bom_length(<<0, 0, 254, 255, _rest::binary>>), do: 4
|
||||
defp bom_length(<<254, 255, 0, 0, _rest::binary>>), do: 4
|
||||
defp bom_length(_binary), do: 0
|
||||
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
|
||||
defp bom_length(<<0, 0, 254, 255, _rest::binary>>),
|
||||
do: 4
|
||||
defp bom_length(<<254, 255, 0, 0, _rest::binary>>),
|
||||
do: 4
|
||||
defp bom_length(_binary),
|
||||
do: 0
|
||||
|
||||
defp read_modes(modes) do
|
||||
for mode <- modes, mode not in [:write, :append, :trim_bom], do: mode
|
||||
@@ -148,10 +143,8 @@ defmodule File.Stream do
|
||||
case read.(device) do
|
||||
data when is_binary(data) ->
|
||||
count_lines(device, path, pattern, read, count + count_lines(data, pattern))
|
||||
|
||||
:eof ->
|
||||
count
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
|
||||
+30
-44
@@ -7,7 +7,7 @@ defmodule Float do
|
||||
|
||||
import Bitwise
|
||||
|
||||
@power_of_2_to_52 4_503_599_627_370_496
|
||||
@power_of_2_to_52 4503599627370496
|
||||
@precision_range 0..15
|
||||
@type precision_range :: 0..15
|
||||
|
||||
@@ -53,29 +53,28 @@ defmodule Float do
|
||||
parse_unsigned(binary)
|
||||
end
|
||||
|
||||
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, false, false, <<digit>>)
|
||||
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9, do:
|
||||
parse_unsigned(rest, false, false, <<digit>>)
|
||||
|
||||
defp parse_unsigned(binary) when is_binary(binary), do: :error
|
||||
defp parse_unsigned(binary) when is_binary(binary), do:
|
||||
:error
|
||||
|
||||
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
|
||||
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9, do:
|
||||
parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
|
||||
|
||||
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
|
||||
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc)
|
||||
when exp_marker in 'eE' and digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
|
||||
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc)
|
||||
when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
|
||||
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
|
||||
|
||||
defp parse_unsigned(rest, dot?, _e?, acc),
|
||||
do: {:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
||||
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
@@ -223,21 +222,18 @@ defmodule Float do
|
||||
count = count - exp + 1023
|
||||
|
||||
cond do
|
||||
# There is no decimal precision
|
||||
# zero or minus zero
|
||||
count <= 0 or (0 == exp and <<0::52>> == significant) ->
|
||||
count <= 0 or # There is no decimal precision
|
||||
(0 == exp and <<0::52>> == significant) -> #zero or minus zero
|
||||
float
|
||||
|
||||
# Precision beyond 15 digits
|
||||
count >= 104 ->
|
||||
count >= 104 -> # Precision beyond 15 digits
|
||||
case rounding do
|
||||
:ceil when sign === 0 -> 1 / power_of_10(precision)
|
||||
:floor when sign === 1 -> -1 / power_of_10(precision)
|
||||
_ -> 0.0
|
||||
end
|
||||
|
||||
# We are asking more precision than we have
|
||||
count <= precision ->
|
||||
count <= precision -> # We are asking more precision than we have
|
||||
float
|
||||
|
||||
true ->
|
||||
@@ -264,11 +260,9 @@ defmodule Float do
|
||||
cond do
|
||||
num == 0 ->
|
||||
0.0
|
||||
|
||||
num >= boundary ->
|
||||
{den, exp} = scale_down(num, boundary, 52)
|
||||
decimal_to_float(sign, num, den, exp)
|
||||
|
||||
true ->
|
||||
{num, exp} = scale_up(num, boundary, 52)
|
||||
decimal_to_float(sign, num, den, exp)
|
||||
@@ -281,7 +275,6 @@ defmodule Float do
|
||||
|
||||
defp scale_down(num, den, exp) do
|
||||
new_den = den <<< 1
|
||||
|
||||
if num < new_den do
|
||||
{den >>> 52, exp}
|
||||
else
|
||||
@@ -302,31 +295,29 @@ defmodule Float do
|
||||
end
|
||||
|
||||
tmp = tmp - @power_of_2_to_52
|
||||
<<tmp::float>> = <<sign::1, exp + 1023::11, tmp::52>>
|
||||
<<tmp::float>> = <<sign::1, (exp + 1023)::11, tmp::52>>
|
||||
tmp
|
||||
end
|
||||
|
||||
defp rounding(:floor, 1, _num, div), do: div + 1
|
||||
defp rounding(:ceil, 0, _num, div), do: div + 1
|
||||
|
||||
defp rounding(:half_up, _sign, num, div) do
|
||||
case rem(num, 10) do
|
||||
rem when rem < 5 -> div
|
||||
rem when rem >= 5 -> div + 1
|
||||
end
|
||||
end
|
||||
|
||||
defp rounding(_, _, _, div), do: div
|
||||
|
||||
Enum.reduce(0..104, 1, fn x, acc ->
|
||||
Enum.reduce 0..104, 1, fn x, acc ->
|
||||
defp power_of_10(unquote(x)), do: unquote(acc)
|
||||
acc * 10
|
||||
end)
|
||||
end
|
||||
|
||||
Enum.reduce(0..104, 1, fn x, acc ->
|
||||
Enum.reduce 0..104, 1, fn x, acc ->
|
||||
defp power_of_5(unquote(x)), do: unquote(acc)
|
||||
acc * 5
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a pair of integers whose ratio is exactly equal
|
||||
@@ -352,15 +343,12 @@ defmodule Float do
|
||||
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
|
||||
{num, _, den} = decompose(significant)
|
||||
num = sign(sign, num)
|
||||
|
||||
case exp - 1023 do
|
||||
exp when exp > 0 ->
|
||||
{den, exp} = shift_right(den, exp)
|
||||
{shift_left(num, exp), den}
|
||||
|
||||
exp when exp < 0 ->
|
||||
{num, shift_left(den, -exp)}
|
||||
|
||||
0 ->
|
||||
{num, den}
|
||||
end
|
||||
@@ -373,11 +361,9 @@ defmodule Float do
|
||||
defp decompose(<<1::1, bits::bitstring>>, count, last_count, power, _last_power, acc) do
|
||||
decompose(bits, count + 1, count, power <<< 1, power, shift_left(acc, count - last_count) + 1)
|
||||
end
|
||||
|
||||
defp decompose(<<0::1, bits::bitstring>>, count, last_count, power, last_power, acc) do
|
||||
decompose(bits, count + 1, last_count, power <<< 1, last_power, acc)
|
||||
end
|
||||
|
||||
defp decompose(<<>>, _count, last_count, _power, last_power, acc) do
|
||||
{acc, last_count, last_power}
|
||||
end
|
||||
@@ -427,7 +413,7 @@ defmodule Float do
|
||||
"7.0"
|
||||
|
||||
"""
|
||||
@spec to_string(float) :: String.t()
|
||||
@spec to_string(float) :: String.t
|
||||
def to_string(float) when is_float(float) do
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(float))
|
||||
end
|
||||
@@ -452,11 +438,11 @@ defmodule Float do
|
||||
end
|
||||
|
||||
defp invalid_precision_message(precision) do
|
||||
"precision #{precision} is out of valid range of #{inspect(@precision_range)}"
|
||||
"precision #{precision} is out of valid range of #{inspect @precision_range}"
|
||||
end
|
||||
|
||||
defp expand_compact([{:compact, false} | t]), do: expand_compact(t)
|
||||
defp expand_compact([{:compact, true} | t]), do: [:compact | expand_compact(t)]
|
||||
defp expand_compact([h | t]), do: [h | expand_compact(t)]
|
||||
defp expand_compact([]), do: []
|
||||
defp expand_compact([{:compact, true} | t]), do: [:compact | expand_compact(t)]
|
||||
defp expand_compact([h | t]), do: [h | expand_compact(t)]
|
||||
defp expand_compact([]), do: []
|
||||
end
|
||||
|
||||
+86
-171
@@ -32,7 +32,7 @@ defmodule GenEvent do
|
||||
If the use case where you were using GenEvent requires more complex logic,
|
||||
[GenStage](https://github.com/elixir-lang/gen_stage) provides a great
|
||||
alternative. GenStage is an external Elixir library maintained by the Elixir
|
||||
team; it provides a tool to implement systems that exchange events in a
|
||||
team; it provides tool to implement systems that exchange events in a
|
||||
demand-driven way with built-in support for backpressure. See the [GenStage
|
||||
documentation](https://hexdocs.pm/gen_stage) for more information.
|
||||
|
||||
@@ -44,34 +44,30 @@ defmodule GenEvent do
|
||||
"""
|
||||
|
||||
@callback init(args :: term) ::
|
||||
{:ok, state}
|
||||
| {:ok, state, :hibernate}
|
||||
| {:error, reason :: any}
|
||||
when state: any
|
||||
{:ok, state} |
|
||||
{:ok, state, :hibernate} |
|
||||
{:error, reason :: any} when state: any
|
||||
|
||||
@callback handle_event(event :: term, state :: term) ::
|
||||
{:ok, new_state}
|
||||
| {:ok, new_state, :hibernate}
|
||||
| :remove_handler
|
||||
when new_state: term
|
||||
{:ok, new_state} |
|
||||
{:ok, new_state, :hibernate} |
|
||||
:remove_handler when new_state: term
|
||||
|
||||
@callback handle_call(request :: term, state :: term) ::
|
||||
{:ok, reply, new_state}
|
||||
| {:ok, reply, new_state, :hibernate}
|
||||
| {:remove_handler, reply}
|
||||
when reply: term, new_state: term
|
||||
{:ok, reply, new_state} |
|
||||
{:ok, reply, new_state, :hibernate} |
|
||||
{:remove_handler, reply} when reply: term, new_state: term
|
||||
|
||||
@callback handle_info(msg :: term, state :: term) ::
|
||||
{:ok, new_state}
|
||||
| {:ok, new_state, :hibernate}
|
||||
| :remove_handler
|
||||
when new_state: term
|
||||
{:ok, new_state} |
|
||||
{:ok, new_state, :hibernate} |
|
||||
:remove_handler when new_state: term
|
||||
|
||||
@callback terminate(reason, state :: term) :: term
|
||||
when reason: :stop | {:stop, term} | :remove_handler | {:error, term} | term
|
||||
@callback terminate(reason, state :: term) ::
|
||||
term when reason: :stop | {:stop, term} | :remove_handler | {:error, term} | term
|
||||
|
||||
@callback code_change(old_vsn, state :: term, extra :: term) :: {:ok, new_state :: term}
|
||||
when old_vsn: term | {:down, term}
|
||||
@callback code_change(old_vsn, state :: term, extra :: term) ::
|
||||
{:ok, new_state :: term} when old_vsn: term | {:down, term}
|
||||
|
||||
@type on_start :: {:ok, pid} | {:error, {:already_started, pid}}
|
||||
|
||||
@@ -86,10 +82,7 @@ defmodule GenEvent do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
%{file: file, line: line} = __CALLER__
|
||||
|
||||
deprecation_message =
|
||||
"the GenEvent module is deprecated, see its documentation for alternatives"
|
||||
|
||||
deprecation_message = "the GenEvent module is deprecated, see its documentation for alternatives"
|
||||
:elixir_errors.warn(line, file, deprecation_message)
|
||||
|
||||
quote location: :keep do
|
||||
@@ -109,17 +102,14 @@ defmodule GenEvent do
|
||||
def handle_call(msg, state) do
|
||||
proc =
|
||||
case Process.info(self(), :registered_name) do
|
||||
{_, []} -> self()
|
||||
{_, []} -> self()
|
||||
{_, name} -> name
|
||||
end
|
||||
|
||||
# We do this to trick Dialyzer to not complain about non-local returns.
|
||||
case :erlang.phash2(1, 1) do
|
||||
0 ->
|
||||
raise "attempted to call GenEvent #{inspect(proc)} but no handle_call/2 clause was provided"
|
||||
|
||||
1 ->
|
||||
{:remove_handler, {:bad_call, msg}}
|
||||
0 -> raise "attempted to call GenEvent #{inspect proc} but no handle_call/2 clause was provided"
|
||||
1 -> {:remove_handler, {:bad_call, msg}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,12 +128,8 @@ defmodule GenEvent do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable init: 1,
|
||||
handle_event: 2,
|
||||
handle_call: 2,
|
||||
handle_info: 2,
|
||||
terminate: 2,
|
||||
code_change: 3
|
||||
defoverridable [init: 1, handle_event: 2, handle_call: 2,
|
||||
handle_info: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -165,16 +151,12 @@ defmodule GenEvent do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:gen.start(GenEvent, mode, @no_callback, [], [])
|
||||
|
||||
atom when is_atom(atom) ->
|
||||
:gen.start(GenEvent, mode, {:local, atom}, @no_callback, [], [])
|
||||
|
||||
{:global, _term} = tuple ->
|
||||
:gen.start(GenEvent, mode, tuple, @no_callback, [], [])
|
||||
|
||||
{:via, via_module, _term} = tuple when is_atom(via_module) ->
|
||||
:gen.start(GenEvent, mode, tuple, @no_callback, [], [])
|
||||
|
||||
other ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
@@ -190,9 +172,11 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec stream(manager, keyword) :: GenEvent.Stream.t()
|
||||
@spec stream(manager, keyword) :: GenEvent.Stream.t
|
||||
def stream(manager, options \\ []) do
|
||||
%GenEvent.Stream{manager: manager, timeout: Keyword.get(options, :timeout, :infinity)}
|
||||
%GenEvent.Stream{
|
||||
manager: manager,
|
||||
timeout: Keyword.get(options, :timeout, :infinity)}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -232,7 +216,8 @@ defmodule GenEvent do
|
||||
def notify(manager, msg)
|
||||
when is_pid(manager)
|
||||
when is_atom(manager)
|
||||
when tuple_size(manager) == 2 and is_atom(elem(manager, 0)) and is_atom(elem(manager, 1)) do
|
||||
when tuple_size(manager) == 2 and
|
||||
is_atom(elem(manager, 0)) and is_atom(elem(manager, 1)) do
|
||||
send(manager, {:notify, msg})
|
||||
:ok
|
||||
end
|
||||
@@ -250,7 +235,7 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec call(manager, handler, term, timeout) :: term | {:error, term}
|
||||
@spec call(manager, handler, term, timeout) :: term | {:error, term}
|
||||
def call(manager, handler, request, timeout \\ 5000) do
|
||||
try do
|
||||
:gen.call(manager, self(), {:call, handler, request}, timeout)
|
||||
@@ -300,7 +285,7 @@ defmodule GenEvent do
|
||||
## Init callbacks
|
||||
|
||||
require Record
|
||||
Record.defrecordp(:handler, [:module, :id, :state, :pid, :ref])
|
||||
Record.defrecordp :handler, [:module, :id, :state, :pid, :ref]
|
||||
|
||||
@doc false
|
||||
def init_it(starter, :self, name, mod, args, options) do
|
||||
@@ -309,14 +294,12 @@ defmodule GenEvent do
|
||||
|
||||
def init_it(starter, parent, name, _mod, _args, options) do
|
||||
Process.put(:"$initial_call", {__MODULE__, :init_it, 6})
|
||||
|
||||
debug =
|
||||
if function_exported?(:gen, :debug_options, 2) do
|
||||
:gen.debug_options(name, options)
|
||||
else
|
||||
:gen.debug_options(options)
|
||||
end
|
||||
|
||||
:proc_lib.init_ack(starter, {:ok, self()})
|
||||
loop(parent, name(name), [], debug, false)
|
||||
end
|
||||
@@ -326,7 +309,7 @@ defmodule GenEvent do
|
||||
fetch_msg(parent, name, handlers, debug, true)
|
||||
end
|
||||
|
||||
defp name({:local, name}), do: name
|
||||
defp name({:local, name}), do: name
|
||||
defp name({:global, name}), do: name
|
||||
defp name({:via, _, name}), do: name
|
||||
defp name(pid) when is_pid(pid), do: pid
|
||||
@@ -344,14 +327,12 @@ defmodule GenEvent do
|
||||
defp fetch_msg(parent, name, handlers, debug, hib) do
|
||||
receive do
|
||||
{:system, from, req} ->
|
||||
:sys.handle_system_msg(req, from, parent, __MODULE__, debug, [name, handlers, hib], hib)
|
||||
|
||||
:sys.handle_system_msg(req, from, parent, __MODULE__,
|
||||
debug, [name, handlers, hib], hib)
|
||||
{:EXIT, ^parent, reason} ->
|
||||
server_terminate(reason, parent, handlers, name)
|
||||
|
||||
msg when debug == [] ->
|
||||
handle_msg(msg, parent, name, handlers, [])
|
||||
|
||||
msg ->
|
||||
debug = :sys.handle_debug(debug, &print_event/3, name, {:in, msg})
|
||||
handle_msg(msg, parent, name, handlers, debug)
|
||||
@@ -363,78 +344,59 @@ defmodule GenEvent do
|
||||
{:notify, event} ->
|
||||
{hib, handlers} = server_event(:async, event, handlers, name)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, _tag, {:notify, event}} ->
|
||||
{hib, handlers} = server_event(:async, event, handlers, name)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:ack_notify, event}} ->
|
||||
reply(tag, :ok)
|
||||
{hib, handlers} = server_event(:ack, event, handlers, name)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:sync_notify, event}} ->
|
||||
{hib, handlers} = server_event(:sync, event, handlers, name)
|
||||
reply(tag, :ok)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{:DOWN, ref, :process, _pid, reason} = other ->
|
||||
case handle_down(ref, reason, handlers, name) do
|
||||
{:ok, handlers} ->
|
||||
loop(parent, name, handlers, debug, false)
|
||||
|
||||
:error ->
|
||||
{hib, handlers} = server_info(other, handlers, name)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
end
|
||||
|
||||
{_from, tag, {:call, handler, query}} ->
|
||||
{hib, reply, handlers} = server_call(handler, query, handlers, name)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:add_handler, handler, args}} ->
|
||||
{hib, reply, handlers} = server_add_handler(handler, args, handlers)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:add_mon_handler, handler, args, notify}} ->
|
||||
{hib, reply, handlers} = server_add_mon_handler(handler, args, handlers, notify)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:add_process_handler, pid, notify}} ->
|
||||
{hib, reply, handlers} = server_add_process_handler(pid, handlers, notify)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:delete_handler, handler, args}} ->
|
||||
{reply, handlers} = server_remove_handler(handler, args, handlers, name)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, false)
|
||||
|
||||
{_from, tag, {:swap_handler, handler1, args1, handler2, args2}} ->
|
||||
{hib, reply, handlers} =
|
||||
server_swap_handler(handler1, args1, handler2, args2, handlers, nil, name)
|
||||
|
||||
{hib, reply, handlers} = server_swap_handler(handler1, args1, handler2, args2, handlers, nil, name)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, {:swap_mon_handler, handler1, args1, handler2, args2, mon}} ->
|
||||
{hib, reply, handlers} =
|
||||
server_swap_handler(handler1, args1, handler2, args2, handlers, mon, name)
|
||||
|
||||
{hib, reply, handlers} = server_swap_handler(handler1, args1, handler2, args2, handlers, mon, name)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
|
||||
{_from, tag, :which_handlers} ->
|
||||
reply(tag, server_which_handlers(handlers))
|
||||
loop(parent, name, handlers, debug, false)
|
||||
|
||||
{_from, tag, :get_modules} ->
|
||||
reply(tag, server_get_modules(handlers))
|
||||
loop(parent, name, handlers, debug, false)
|
||||
|
||||
other ->
|
||||
{hib, handlers} = server_info(other, handlers, name)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
@@ -464,38 +426,31 @@ defmodule GenEvent do
|
||||
handler
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, [name, handlers, hib]}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def system_get_state([_name, handlers, _hib]) do
|
||||
tuples =
|
||||
for handler(module: mod, id: id, state: state) <- handlers do
|
||||
{mod, id, state}
|
||||
end
|
||||
|
||||
tuples = for handler(module: mod, id: id, state: state) <- handlers do
|
||||
{mod, id, state}
|
||||
end
|
||||
{:ok, tuples}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def system_replace_state(fun, [name, handlers, hib]) do
|
||||
{handlers, states} =
|
||||
:lists.unzip(
|
||||
for handler <- handlers do
|
||||
handler(module: mod, id: id, state: state) = handler
|
||||
cur = {mod, id, state}
|
||||
|
||||
try do
|
||||
new = {^mod, ^id, new_state} = fun.(cur)
|
||||
{handler(handler, state: new_state), new}
|
||||
catch
|
||||
_, _ ->
|
||||
{handler, cur}
|
||||
end
|
||||
:lists.unzip(for handler <- handlers do
|
||||
handler(module: mod, id: id, state: state) = handler
|
||||
cur = {mod, id, state}
|
||||
try do
|
||||
new = {^mod, ^id, new_state} = fun.(cur)
|
||||
{handler(handler, state: new_state), new}
|
||||
catch
|
||||
_, _ ->
|
||||
{handler, cur}
|
||||
end
|
||||
)
|
||||
|
||||
end)
|
||||
{:ok, states, [name, handlers, hib]}
|
||||
end
|
||||
|
||||
@@ -504,27 +459,23 @@ defmodule GenEvent do
|
||||
[pdict, sys_state, parent, _debug, [name, handlers, _hib]] = status_data
|
||||
header = :gen.format_status_header('Status for event handler', name)
|
||||
|
||||
formatted =
|
||||
for handler <- handlers do
|
||||
handler(module: module, state: state) = handler
|
||||
|
||||
if function_exported?(module, :format_status, 2) do
|
||||
try do
|
||||
state = module.format_status(opt, [pdict, state])
|
||||
handler(handler, state: state)
|
||||
catch
|
||||
_, _ -> handler
|
||||
end
|
||||
else
|
||||
handler
|
||||
formatted = for handler <- handlers do
|
||||
handler(module: module, state: state) = handler
|
||||
if function_exported?(module, :format_status, 2) do
|
||||
try do
|
||||
state = module.format_status(opt, [pdict, state])
|
||||
handler(handler, state: state)
|
||||
catch
|
||||
_, _ -> handler
|
||||
end
|
||||
else
|
||||
handler
|
||||
end
|
||||
end
|
||||
|
||||
[
|
||||
header: header,
|
||||
data: [{'Status', sys_state}, {'Parent', parent}],
|
||||
items: {'Installed handlers', formatted}
|
||||
]
|
||||
[header: header,
|
||||
data: [{'Status', sys_state}, {'Parent', parent}],
|
||||
items: {'Installed handlers', formatted}]
|
||||
end
|
||||
|
||||
## Loop helpers
|
||||
@@ -532,21 +483,16 @@ defmodule GenEvent do
|
||||
defp print_event(dev, {:in, msg}, name) do
|
||||
case msg do
|
||||
{:notify, event} ->
|
||||
IO.puts(dev, "*DBG* #{inspect(name)} got event #{inspect(event)}")
|
||||
|
||||
IO.puts dev, "*DBG* #{inspect name} got event #{inspect event}"
|
||||
{_, _, {:call, handler, query}} ->
|
||||
IO.puts(
|
||||
dev,
|
||||
"*DBG* #{inspect(name)} (handler #{inspect(handler)}) got call #{inspect(query)}"
|
||||
)
|
||||
|
||||
IO.puts dev, "*DBG* #{inspect name} (handler #{inspect handler}) got call #{inspect query}"
|
||||
_ ->
|
||||
IO.puts(dev, "*DBG* #{inspect(name)} got #{inspect(msg)}")
|
||||
IO.puts dev, "*DBG* #{inspect name} got #{inspect msg}"
|
||||
end
|
||||
end
|
||||
|
||||
defp print_event(dev, dbg, name) do
|
||||
IO.puts(dev, "*DBG* #{inspect(name)}: #{inspect(dbg)}")
|
||||
IO.puts dev, "*DBG* #{inspect name}: #{inspect dbg}"
|
||||
end
|
||||
|
||||
defp server_add_handler({module, id}, args, handlers) do
|
||||
@@ -573,7 +519,8 @@ defmodule GenEvent do
|
||||
|
||||
defp server_add_process_handler(pid, handlers, notify) do
|
||||
ref = Process.monitor(pid)
|
||||
handler = handler(module: GenEvent.Stream, id: {self(), ref}, pid: notify, ref: ref)
|
||||
handler = handler(module: GenEvent.Stream, id: {self(), ref},
|
||||
pid: notify, ref: ref)
|
||||
do_add_handler(GenEvent.Stream, handler, {pid, ref}, handlers, {self(), ref})
|
||||
end
|
||||
|
||||
@@ -608,7 +555,6 @@ defmodule GenEvent do
|
||||
{pid, _ref} when is_pid(pid) ->
|
||||
server_process_notify(mode, event, handler)
|
||||
server_split_process_handlers(mode, event, t, handlers, [handler | streams])
|
||||
|
||||
_ ->
|
||||
server_split_process_handlers(mode, event, t, [handler | handlers], streams)
|
||||
end
|
||||
@@ -619,18 +565,17 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
defp server_process_notify(mode, event, handler(state: {pid, ref})) do
|
||||
send(pid, {self(), {self(), ref}, {mode_to_tag(mode), event}})
|
||||
send pid, {self(), {self(), ref}, {mode_to_tag(mode), event}}
|
||||
end
|
||||
|
||||
defp mode_to_tag(:ack), do: :ack_notify
|
||||
defp mode_to_tag(:sync), do: :sync_notify
|
||||
defp mode_to_tag(:ack), do: :ack_notify
|
||||
defp mode_to_tag(:sync), do: :sync_notify
|
||||
defp mode_to_tag(:async), do: :notify
|
||||
|
||||
defp server_notify(event, fun, [handler | t], name, handlers, acc, hib) do
|
||||
case server_update(handler, fun, event, name, handlers) do
|
||||
{new_hib, handler} ->
|
||||
server_notify(event, fun, t, name, handlers, [handler | acc], hib or new_hib)
|
||||
|
||||
:error ->
|
||||
server_notify(event, fun, t, name, handlers, acc, hib)
|
||||
end
|
||||
@@ -648,20 +593,16 @@ defmodule GenEvent do
|
||||
case res do
|
||||
{:ok, state} ->
|
||||
{false, handler(handler, state: state)}
|
||||
|
||||
{:ok, state, :hibernate} ->
|
||||
{true, handler(handler, state: state)}
|
||||
|
||||
:remove_handler ->
|
||||
do_terminate(handler, :remove_handler, event, name, :normal)
|
||||
:error
|
||||
|
||||
other ->
|
||||
reason = {:bad_return_value, other}
|
||||
do_terminate(handler, {:error, reason}, event, name, reason)
|
||||
:error
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
do_terminate(handler, {:error, reason}, event, name, reason)
|
||||
:error
|
||||
@@ -672,19 +613,16 @@ defmodule GenEvent do
|
||||
server_collect_process_handlers(:async, event, t, [handler | handlers], name)
|
||||
end
|
||||
|
||||
defp server_collect_process_handlers(mode, event, [handler | t], handlers, name)
|
||||
when mode in [:sync, :ack] do
|
||||
defp server_collect_process_handlers(mode, event, [handler | t], handlers, name) when mode in [:sync, :ack] do
|
||||
handler(ref: ref, id: id) = handler
|
||||
|
||||
receive do
|
||||
{^ref, :ok} ->
|
||||
server_collect_process_handlers(mode, event, t, [handler | handlers], name)
|
||||
|
||||
{_from, tag, {:delete_handler, ^id, args}} ->
|
||||
do_terminate(handler, args, :remove, name, :normal)
|
||||
reply(tag, :ok)
|
||||
server_collect_process_handlers(mode, event, t, handlers, name)
|
||||
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
|
||||
server_collect_process_handlers(mode, event, t, handlers, name)
|
||||
@@ -699,12 +637,10 @@ defmodule GenEvent do
|
||||
case :lists.keyfind(module, handler(:id) + 1, handlers) do
|
||||
false ->
|
||||
{false, {:error, :not_found}, handlers}
|
||||
|
||||
handler ->
|
||||
case server_call_update(handler, query, name, handlers) do
|
||||
{{hib, handler}, reply} ->
|
||||
{hib, reply, :lists.keyreplace(module, handler(:id) + 1, handlers, handler)}
|
||||
|
||||
{:error, reply} ->
|
||||
{false, reply, :lists.keydelete(module, handler(:id) + 1, handlers)}
|
||||
end
|
||||
@@ -713,26 +649,21 @@ defmodule GenEvent do
|
||||
|
||||
defp server_call_update(handler, query, name, _handlers) do
|
||||
handler(module: module, state: state) = handler
|
||||
|
||||
case do_handler(module, :handle_call, [query, state]) do
|
||||
{:ok, res} ->
|
||||
case res do
|
||||
{:ok, reply, state} ->
|
||||
{{false, handler(handler, state: state)}, reply}
|
||||
|
||||
{:ok, reply, state, :hibernate} ->
|
||||
{{true, handler(handler, state: state)}, reply}
|
||||
|
||||
{:remove_handler, reply} ->
|
||||
do_terminate(handler, :remove_handler, query, name, :normal)
|
||||
{:error, reply}
|
||||
|
||||
other ->
|
||||
reason = {:bad_return_value, other}
|
||||
do_terminate(handler, {:error, reason}, query, name, reason)
|
||||
{:error, {:error, reason}}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
do_terminate(handler, {:error, reason}, query, name, reason)
|
||||
{:error, {:error, reason}}
|
||||
@@ -740,9 +671,9 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
defp server_get_modules(handlers) do
|
||||
for(handler(module: module) <- handlers, do: module)
|
||||
|> :ordsets.from_list()
|
||||
|> :ordsets.to_list()
|
||||
(for handler(module: module) <- handlers, do: module)
|
||||
|> :ordsets.from_list
|
||||
|> :ordsets.to_list
|
||||
end
|
||||
|
||||
defp server_which_handlers(handlers) do
|
||||
@@ -754,19 +685,16 @@ defmodule GenEvent do
|
||||
for handler <- handlers do
|
||||
do_terminate(handler, :stop, :stop, name, :shutdown)
|
||||
end
|
||||
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp reply({from, ref}, msg) do
|
||||
send(from, {ref, msg})
|
||||
send from, {ref, msg}
|
||||
end
|
||||
|
||||
defp handle_down(ref, reason, handlers, name) do
|
||||
case :lists.keyfind(ref, handler(:ref) + 1, handlers) do
|
||||
false ->
|
||||
:error
|
||||
|
||||
false -> :error
|
||||
handler ->
|
||||
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
|
||||
{:ok, :lists.keydelete(ref, handler(:ref) + 1, handlers)}
|
||||
@@ -781,21 +709,16 @@ defmodule GenEvent do
|
||||
case res do
|
||||
{:ok, state} ->
|
||||
{false, succ, [handler(handler, state: state) | handlers]}
|
||||
|
||||
{:ok, state, :hibernate} ->
|
||||
{true, succ, [handler(handler, state: state) | handlers]}
|
||||
|
||||
{:error, _} = error ->
|
||||
{false, error, handlers}
|
||||
|
||||
other ->
|
||||
{false, {:error, {:bad_return_value, other}}, handlers}
|
||||
end
|
||||
|
||||
{:error, _} = error ->
|
||||
{false, error, handlers}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{false, {:error, :already_present}, handlers}
|
||||
end
|
||||
@@ -805,7 +728,6 @@ defmodule GenEvent do
|
||||
case :lists.keytake(module, handler(:id) + 1, handlers) do
|
||||
{:value, handler, handlers} ->
|
||||
{do_terminate(handler, args, last_in, name, reason), handlers}
|
||||
|
||||
false ->
|
||||
{{:error, :not_found}, handlers}
|
||||
end
|
||||
@@ -819,7 +741,6 @@ defmodule GenEvent do
|
||||
{:ok, res} -> res
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
|
||||
report_terminate(handler, reason, state, last_in, name)
|
||||
res
|
||||
end
|
||||
@@ -829,8 +750,8 @@ defmodule GenEvent do
|
||||
apply(mod, fun, args)
|
||||
catch
|
||||
:throw, val -> {:ok, val}
|
||||
:error, val -> {:error, {val, System.stacktrace()}}
|
||||
:exit, val -> {:error, val}
|
||||
:error, val -> {:error, {val, System.stacktrace}}
|
||||
:exit, val -> {:error, val}
|
||||
else
|
||||
res -> {:ok, res}
|
||||
end
|
||||
@@ -838,35 +759,29 @@ defmodule GenEvent do
|
||||
|
||||
defp report_terminate(handler, reason, state, last_in, name) do
|
||||
report_error(handler, reason, state, last_in, name)
|
||||
|
||||
if ref = handler(handler, :ref) do
|
||||
Process.demonitor(ref, [:flush])
|
||||
end
|
||||
|
||||
if pid = handler(handler, :pid) do
|
||||
send(pid, {:gen_event_EXIT, handler(handler, :id), reason})
|
||||
send pid, {:gen_event_EXIT, handler(handler, :id), reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp report_error(_handler, :normal, _, _, _), do: :ok
|
||||
defp report_error(_handler, :shutdown, _, _, _), do: :ok
|
||||
defp report_error(_handler, {:swapped, _, _}, _, _, _), do: :ok
|
||||
|
||||
defp report_error(handler, reason, state, last_in, name) do
|
||||
reason =
|
||||
case reason do
|
||||
{:undef, [{m, f, a, _} | _] = mfas} ->
|
||||
cond do
|
||||
:code.is_loaded(m) == false ->
|
||||
:code.is_loaded(m) === false ->
|
||||
{:"module could not be loaded", mfas}
|
||||
|
||||
function_exported?(m, f, length(a)) ->
|
||||
reason
|
||||
|
||||
true ->
|
||||
{:"function not exported", mfas}
|
||||
end
|
||||
|
||||
_ ->
|
||||
reason
|
||||
end
|
||||
@@ -875,10 +790,10 @@ defmodule GenEvent do
|
||||
|
||||
:error_logger.error_msg(
|
||||
'** gen_event handler ~p crashed.~n' ++
|
||||
'** Was installed in ~p~n' ++
|
||||
'** Last event was: ~p~n' ++ '** When handler state == ~p~n' ++ '** Reason == ~p~n',
|
||||
[handler(handler, :id), name, last_in, formatted, reason]
|
||||
)
|
||||
'** Was installed in ~p~n' ++
|
||||
'** Last event was: ~p~n' ++
|
||||
'** When handler state == ~p~n' ++
|
||||
'** Reason == ~p~n', [handler(handler, :id), name, last_in, formatted, reason])
|
||||
end
|
||||
|
||||
defp report_status(handler(module: module), state) do
|
||||
|
||||
@@ -2,7 +2,9 @@ defmodule GenEvent.Stream do
|
||||
@moduledoc false
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{manager: GenEvent.manager(), timeout: timeout}
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
@@ -22,7 +24,6 @@ defmodule GenEvent.Stream do
|
||||
def handle_call(msg, _state) do
|
||||
# We do this to trick Dialyzer to not complain about non-local returns.
|
||||
reason = {:bad_call, msg}
|
||||
|
||||
case :erlang.phash2(1, 1) do
|
||||
0 -> exit(reason)
|
||||
1 -> {:remove_handler, reason}
|
||||
@@ -47,7 +48,7 @@ end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn -> start(stream) end
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
@@ -61,33 +62,26 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def slice(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send(manager, {ref, :ok})
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send(manager, {ref, :ok})
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} =
|
||||
:gen.call(manager, self(), {:add_process_handler, self(), self()}, :infinity)
|
||||
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(),
|
||||
{:add_process_handler, self(), self()}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
@@ -135,7 +129,6 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
@@ -153,7 +146,6 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
{:gen_event_EXIT, {^pid, ^ref}, _reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
+86
-142
@@ -67,7 +67,7 @@ defmodule GenServer do
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification id, defauts to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -220,28 +220,32 @@ defmodule GenServer do
|
||||
system events that happen during its execution, such as received messages,
|
||||
sent replies and state changes.
|
||||
|
||||
Let's explore the basic functions from the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) used for debugging:
|
||||
Let's explore the basic functions from the [`:sys` module](http://www.erlang.org/doc/man/sys.html) used for debugging:
|
||||
|
||||
* `:sys.get_state/2` - allows retrieval of the state of the process.
|
||||
In the case of a GenServer process, it will be the callback module state,
|
||||
as passed into the callback functions as last argument.
|
||||
* `:sys.get_status/2` - allows retrieval of the status of the process.
|
||||
This status includes the process dictionary, if the process is running
|
||||
or is suspended, the parent PID, the debugger state, and the state of
|
||||
the behaviour module, which includes the callback module state
|
||||
(as returned by `:sys.get_state/2`). It's possible to change how this
|
||||
status is represented by defining the optional `c:GenServer.format_status/2`
|
||||
callback.
|
||||
* `:sys.trace/3` - prints all the system events to `:stdio`.
|
||||
* `:sys.statistics/3` - manages collection of process statistics.
|
||||
* `:sys.no_debug/2` - turns off all debug handlers for the given process.
|
||||
It is very important to switch off debugging once we're done. Excessive
|
||||
debug handlers or those that should be turned off, but weren't, can
|
||||
seriously damage the performance of the system.
|
||||
* `:sys.suspend/2` - allows to suspend a process so that it only
|
||||
replies to system messages but no other messages. A suspended process
|
||||
can be reactivated via `:sys.resume/2`.
|
||||
* [`:sys.get_state/2`](http://erlang.org/doc/man/sys.html#get_state-2) -
|
||||
allows retrieval of the state of the process. In the case of
|
||||
a GenServer process, it will be the callback module state, as
|
||||
passed into the callback functions as last argument.
|
||||
* [`:sys.get_status/2`](http://erlang.org/doc/man/sys.html#get_status-2) -
|
||||
allows retrieval of the status of the process. This status includes
|
||||
the process dictionary, if the process is running or is suspended,
|
||||
the parent PID, the debugger state, and the state of the behaviour module,
|
||||
which includes the callback module state (as returned by `:sys.get_state/2`).
|
||||
It's possible to change how this status is represented by defining
|
||||
the optional `c:GenServer.format_status/2` callback.
|
||||
* [`:sys.trace/3`](http://erlang.org/doc/man/sys.html#trace-3) -
|
||||
prints all the system events to `:stdio`.
|
||||
* [`:sys.statistics/3`](http://erlang.org/doc/man/sys.html#statistics-3) -
|
||||
manages collection of process statistics.
|
||||
* [`:sys.no_debug/2`](http://erlang.org/doc/man/sys.html#no_debug-2) -
|
||||
turns off all debug handlers for the given process. It is very important
|
||||
to switch off debugging once we're done. Excessive debug handlers or
|
||||
those that should be turned off, but weren't, can seriously damage
|
||||
the performance of the system.
|
||||
* [`:sys.suspend/2`](http://erlang.org/doc/man/sys.html#suspend-2) - allows
|
||||
to suspend a process so that it only replies to system messages but no
|
||||
other messages. A suspended process can be reactivated via
|
||||
[`:sys.resume/2`](http://erlang.org/doc/man/sys.html#resume-2).
|
||||
|
||||
Let's see how we could use those functions for debugging the stack server
|
||||
we defined earlier.
|
||||
@@ -325,11 +329,10 @@ defmodule GenServer do
|
||||
entering the loop or calling `c:terminate/2`.
|
||||
"""
|
||||
@callback init(args :: term) ::
|
||||
{:ok, state}
|
||||
| {:ok, state, timeout | :hibernate}
|
||||
| :ignore
|
||||
| {:stop, reason :: any}
|
||||
when state: any
|
||||
{:ok, state} |
|
||||
{:ok, state, timeout | :hibernate} |
|
||||
:ignore |
|
||||
{:stop, reason :: any} when state: any
|
||||
|
||||
@doc """
|
||||
Invoked to handle synchronous `call/3` messages. `call/3` will block until a
|
||||
@@ -386,17 +389,15 @@ defmodule GenServer do
|
||||
`{:stop, reason, reply, new_state}` except a reply is not sent.
|
||||
|
||||
If this callback is not implemented, the default implementation by
|
||||
`use GenServer` will fail with a `RuntimeError` exception with a message:
|
||||
attempted to call `GenServer` but no `handle_call/3` clause was provided.
|
||||
`use GenServer` will return `{:stop, {:bad_call, request}, state}`.
|
||||
"""
|
||||
@callback handle_call(request :: term, from, state :: term) ::
|
||||
{:reply, reply, new_state}
|
||||
| {:reply, reply, new_state, timeout | :hibernate}
|
||||
| {:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:stop, reason, reply, new_state}
|
||||
| {:stop, reason, new_state}
|
||||
when reply: term, new_state: term, reason: term
|
||||
{:reply, reply, new_state} |
|
||||
{:reply, reply, new_state, timeout | :hibernate} |
|
||||
{:noreply, new_state} |
|
||||
{:noreply, new_state, timeout | :hibernate} |
|
||||
{:stop, reason, reply, new_state} |
|
||||
{:stop, reason, new_state} when reply: term, new_state: term, reason: term
|
||||
|
||||
@doc """
|
||||
Invoked to handle asynchronous `cast/2` messages.
|
||||
@@ -419,14 +420,12 @@ defmodule GenServer do
|
||||
reason `reason`.
|
||||
|
||||
If this callback is not implemented, the default implementation by
|
||||
`use GenServer` will fail with a `RuntimeError` exception with a message:
|
||||
attempted to call `GenServer` but no `handle_cast/2` clause was provided.
|
||||
`use GenServer` will return `{:stop, {:bad_cast, request}, state}`.
|
||||
"""
|
||||
@callback handle_cast(request :: term, state :: term) ::
|
||||
{:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:stop, reason :: term, new_state}
|
||||
when new_state: term
|
||||
{:noreply, new_state} |
|
||||
{:noreply, new_state, timeout | :hibernate} |
|
||||
{:stop, reason :: term, new_state} when new_state: term
|
||||
|
||||
@doc """
|
||||
Invoked to handle all other messages.
|
||||
@@ -440,10 +439,9 @@ defmodule GenServer do
|
||||
`use GenServer` will return `{:noreply, state}`.
|
||||
"""
|
||||
@callback handle_info(msg :: :timeout | term, state :: term) ::
|
||||
{:noreply, new_state}
|
||||
| {:noreply, new_state, timeout | :hibernate}
|
||||
| {:stop, reason :: term, new_state}
|
||||
when new_state: term
|
||||
{:noreply, new_state} |
|
||||
{:noreply, new_state, timeout | :hibernate} |
|
||||
{:stop, reason :: term, new_state} when new_state: term
|
||||
|
||||
@doc """
|
||||
Invoked when the server is about to exit. It should do any cleanup required.
|
||||
@@ -485,8 +483,8 @@ defmodule GenServer do
|
||||
If `reason` is not `:normal`, `:shutdown`, nor `{:shutdown, term}` an error is
|
||||
logged.
|
||||
"""
|
||||
@callback terminate(reason, state :: term) :: term
|
||||
when reason: :normal | :shutdown | {:shutdown, term}
|
||||
@callback terminate(reason, state :: term) ::
|
||||
term when reason: :normal | :shutdown | {:shutdown, term} | term
|
||||
|
||||
@doc """
|
||||
Invoked to change the state of the `GenServer` when a different version of a
|
||||
@@ -508,10 +506,8 @@ defmodule GenServer do
|
||||
with its previous state. Therefore this callback does not usually contain side effects.
|
||||
"""
|
||||
@callback code_change(old_vsn, state :: term, extra :: term) ::
|
||||
{:ok, new_state :: term}
|
||||
| {:error, reason :: term}
|
||||
| {:down, term}
|
||||
when old_vsn: term
|
||||
{:ok, new_state :: term} |
|
||||
{:error, reason :: term} when old_vsn: term | {:down, term}
|
||||
|
||||
@doc """
|
||||
Invoked in some cases to retrieve a formatted version of the `GenServer` status.
|
||||
@@ -530,8 +526,8 @@ defmodule GenServer do
|
||||
list of `{key, value}` tuples representing the current process dictionary of
|
||||
the `GenServer` and `state` is the current state of the `GenServer`.
|
||||
"""
|
||||
@callback format_status(reason, pdict_and_state :: list) :: term
|
||||
when reason: :normal | :terminate
|
||||
@callback format_status(reason, pdict_and_state :: list) ::
|
||||
term when reason: :normal | :terminate
|
||||
|
||||
@optional_callbacks format_status: 2
|
||||
|
||||
@@ -545,14 +541,13 @@ defmodule GenServer do
|
||||
@type options :: [option]
|
||||
|
||||
@typedoc "Option values used by the `start*` functions"
|
||||
@type option ::
|
||||
{:debug, debug}
|
||||
| {:name, name}
|
||||
| {:timeout, timeout}
|
||||
| {:spawn_opt, Process.spawn_opt()}
|
||||
@type option :: {:debug, debug} |
|
||||
{:name, name} |
|
||||
{:timeout, timeout} |
|
||||
{:spawn_opt, Process.spawn_opt}
|
||||
|
||||
@typedoc "Debug options supported by the `start*` functions"
|
||||
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t()}]
|
||||
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]
|
||||
|
||||
@typedoc "The server reference"
|
||||
@type server :: pid | name | {atom, node}
|
||||
@@ -567,40 +562,41 @@ defmodule GenServer do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour GenServer
|
||||
@opts unquote(opts)
|
||||
|
||||
spec = [
|
||||
id: opts[:id] || __MODULE__,
|
||||
start: Macro.escape(opts[:start]) || quote(do: {__MODULE__, :start_link, [arg]}),
|
||||
restart: opts[:restart] || :permanent,
|
||||
shutdown: opts[:shutdown] || 5000,
|
||||
type: :worker
|
||||
]
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]}
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
%{unquote_splicing(spec)}
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
# TODO: Remove this on Elixir v2.0
|
||||
@before_compile GenServer
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _from, state) do
|
||||
proc =
|
||||
case Process.info(self(), :registered_name) do
|
||||
{_, []} -> self()
|
||||
{_, []} -> self()
|
||||
{_, name} -> name
|
||||
end
|
||||
|
||||
# We do this to trick Dialyzer to not complain about non-local returns.
|
||||
case :erlang.phash2(1, 1) do
|
||||
0 ->
|
||||
raise "attempted to call GenServer #{inspect(proc)} but no handle_call/3 clause was provided"
|
||||
|
||||
1 ->
|
||||
{:stop, {:bad_call, msg}, state}
|
||||
0 -> raise "attempted to call GenServer #{inspect proc} but no handle_call/3 clause was provided"
|
||||
1 -> {:stop, {:bad_call, msg}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -608,12 +604,11 @@ defmodule GenServer do
|
||||
def handle_info(msg, state) do
|
||||
proc =
|
||||
case Process.info(self(), :registered_name) do
|
||||
{_, []} -> self()
|
||||
{_, []} -> self()
|
||||
{_, name} -> name
|
||||
end
|
||||
|
||||
pattern = '~p ~p received unexpected message in handle_info/2: ~p~n'
|
||||
:error_logger.error_msg(pattern, [__MODULE__, proc, msg])
|
||||
:error_logger.error_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
|
||||
[__MODULE__, proc, msg])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@@ -621,17 +616,14 @@ defmodule GenServer do
|
||||
def handle_cast(msg, state) do
|
||||
proc =
|
||||
case Process.info(self(), :registered_name) do
|
||||
{_, []} -> self()
|
||||
{_, []} -> self()
|
||||
{_, name} -> name
|
||||
end
|
||||
|
||||
# We do this to trick Dialyzer to not complain about non-local returns.
|
||||
case :erlang.phash2(1, 1) do
|
||||
0 ->
|
||||
raise "attempted to cast GenServer #{inspect(proc)} but no handle_cast/2 clause was provided"
|
||||
|
||||
1 ->
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
0 -> raise "attempted to cast GenServer #{inspect proc} but no handle_cast/2 clause was provided"
|
||||
1 -> {:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -649,35 +641,6 @@ defmodule GenServer do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro __before_compile__(env) do
|
||||
unless Module.defines?(env.module, {:init, 1}) do
|
||||
message = """
|
||||
function init/1 required by behaviour GenServer is not implemented \
|
||||
(in module #{inspect(env.module)}).
|
||||
|
||||
We will inject a default implementation for now:
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
But you want to define your own implementation that converts the \
|
||||
arguments given to GenServer.start_link/3 to the server state
|
||||
"""
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
defoverridable init: 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a `GenServer` process linked to the current process.
|
||||
|
||||
@@ -698,7 +661,7 @@ defmodule GenServer do
|
||||
* `:name` - used for name registration as described in the "Name
|
||||
registration" section of the module documentation
|
||||
|
||||
* `:timeout` - if present, the server is allowed to spend the given number of
|
||||
* `:timeout` - if present, the server is allowed to spend the given amount of
|
||||
milliseconds initializing or it will be terminated and the start function
|
||||
will return `{:error, :timeout}`
|
||||
|
||||
@@ -738,16 +701,12 @@ defmodule GenServer do
|
||||
case Keyword.pop(options, :name) do
|
||||
{nil, opts} ->
|
||||
:gen.start(:gen_server, link, module, args, opts)
|
||||
|
||||
{atom, opts} when is_atom(atom) ->
|
||||
:gen.start(:gen_server, link, {:local, atom}, module, args, opts)
|
||||
|
||||
{{:global, _term} = tuple, opts} ->
|
||||
:gen.start(:gen_server, link, tuple, module, args, opts)
|
||||
|
||||
{{:via, via_module, _term} = tuple, opts} when is_atom(via_module) ->
|
||||
:gen.start(:gen_server, link, tuple, module, args, opts)
|
||||
|
||||
{other, _} ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
@@ -775,21 +734,7 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec stop(server, reason :: term, timeout) :: :ok
|
||||
def stop(server, reason \\ :normal, timeout \\ :infinity) do
|
||||
case whereis(server) do
|
||||
nil ->
|
||||
exit({:noproc, {__MODULE__, :stop, [server, reason, timeout]}})
|
||||
|
||||
pid when pid == self() ->
|
||||
exit({:calling_self, {__MODULE__, :stop, [server, reason, timeout]}})
|
||||
|
||||
pid ->
|
||||
try do
|
||||
:proc_lib.stop(pid, reason, timeout)
|
||||
catch
|
||||
:exit, err ->
|
||||
exit({err, {__MODULE__, :stop, [server, reason, timeout]}})
|
||||
end
|
||||
end
|
||||
:gen.stop(server, reason, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -819,10 +764,8 @@ defmodule GenServer do
|
||||
case whereis(server) do
|
||||
nil ->
|
||||
exit({:noproc, {__MODULE__, :call, [server, request, timeout]}})
|
||||
|
||||
pid when pid == self() ->
|
||||
exit({:calling_self, {__MODULE__, :call, [server, request, timeout]}})
|
||||
|
||||
pid ->
|
||||
try do
|
||||
:gen.call(pid, :"$gen_call", request, timeout)
|
||||
@@ -875,7 +818,8 @@ defmodule GenServer do
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest), do: do_send(dest, cast_msg(request))
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -888,7 +832,7 @@ defmodule GenServer do
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ [node() | Node.list()], name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
@@ -935,7 +879,7 @@ defmodule GenServer do
|
||||
|
||||
"""
|
||||
@spec multi_call([node], name :: atom, term, timeout) ::
|
||||
{replies :: [{node, term}], bad_nodes :: [node]}
|
||||
{replies :: [{node, term}], bad_nodes :: [node]}
|
||||
def multi_call(nodes \\ [node() | Node.list()], name, request, timeout \\ :infinity) do
|
||||
:gen_server.multi_call(nodes, name, request, timeout)
|
||||
end
|
||||
@@ -1007,14 +951,14 @@ defmodule GenServer do
|
||||
def whereis({:global, name}) do
|
||||
case :global.whereis_name(name) do
|
||||
pid when is_pid(pid) -> pid
|
||||
:undefined -> nil
|
||||
:undefined -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def whereis({:via, mod, name}) do
|
||||
case apply(mod, :whereis_name, [name]) do
|
||||
pid when is_pid(pid) -> pid
|
||||
:undefined -> nil
|
||||
:undefined -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+17
-49
@@ -26,7 +26,7 @@ defmodule HashDict do
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t()
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
%HashDict{}
|
||||
end
|
||||
@@ -37,9 +37,8 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} =
|
||||
do_update(root, key, fn -> raise KeyError, key: key, term: dict end, fun, key_hash(key))
|
||||
|
||||
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@@ -55,14 +54,14 @@ defmodule HashDict do
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, _value} -> dict
|
||||
:error -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, value} -> {value, dict}
|
||||
:error -> {default, dict}
|
||||
:error -> {default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,8 +73,8 @@ defmodule HashDict do
|
||||
def reduce(%HashDict{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -85,7 +84,7 @@ defmodule HashDict do
|
||||
def dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,32 +92,26 @@ defmodule HashDict do
|
||||
|
||||
defp do_fetch(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[^key | v] -> {:ok, v}
|
||||
[^key | v] -> {:ok, v}
|
||||
{^key, v, _} -> {:ok, v}
|
||||
{_, _, n} -> do_fetch(n, key, key_shift(hash))
|
||||
_ -> :error
|
||||
{_, _, n} -> do_fetch(n, key, key_shift(hash))
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp do_put(node, key, value, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{put_elem(node, index, [key | value]), 1}
|
||||
|
||||
[^key | _] ->
|
||||
{put_elem(node, index, [key | value]), 0}
|
||||
|
||||
[k | v] ->
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key | value])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
|
||||
{^key, _, n} ->
|
||||
{put_elem(node, index, {key, value, n}), 0}
|
||||
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_put(n, key, value, key_shift(hash))
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
@@ -127,21 +120,16 @@ defmodule HashDict do
|
||||
|
||||
defp do_update(node, key, initial, fun, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{put_elem(node, index, [key | initial.()]), 1}
|
||||
|
||||
[^key | value] ->
|
||||
{put_elem(node, index, [key | fun.(value)]), 0}
|
||||
|
||||
[k | v] ->
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key | initial.()])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
|
||||
{^key, value, n} ->
|
||||
{put_elem(node, index, {key, fun.(value), n}), 0}
|
||||
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
@@ -150,35 +138,28 @@ defmodule HashDict do
|
||||
|
||||
defp do_delete(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
|
||||
[^key | value] ->
|
||||
{put_elem(node, index, []), value}
|
||||
|
||||
[_ | _] ->
|
||||
:error
|
||||
|
||||
{^key, value, n} ->
|
||||
{put_elem(node, index, do_compact_node(n)), value}
|
||||
|
||||
{k, v, n} ->
|
||||
case do_delete(n, key, key_shift(hash)) do
|
||||
{@node_template, value} ->
|
||||
{put_elem(node, index, [k | v]), value}
|
||||
|
||||
{n, value} ->
|
||||
{put_elem(node, index, {k, v, n}), value}
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Enum.each(0..(@node_size - 1), fn index ->
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[k | v] ->
|
||||
@@ -186,12 +167,11 @@ defmodule HashDict do
|
||||
@node_template -> [k | v]
|
||||
n -> {k, v, n}
|
||||
end
|
||||
|
||||
{k, v, n} ->
|
||||
{k, v, put_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
## Dict reduce
|
||||
|
||||
@@ -216,12 +196,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(
|
||||
:erlang.element(count, node),
|
||||
acc,
|
||||
fun,
|
||||
&do_reduce(node, &1, fun, count - 1, next)
|
||||
)
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
@@ -267,24 +242,17 @@ defimpl Enumerable, for: HashDict do
|
||||
module = HashDict
|
||||
{:ok, module.size(dict)}
|
||||
end
|
||||
|
||||
def slice(_dict) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
def into(original) do
|
||||
# Avoid warnings about HashDict being deprecated.
|
||||
module = HashDict
|
||||
|
||||
collector_fun = fn
|
||||
{original, fn
|
||||
dict, {:cont, {key, value}} -> module.put(dict, key, value)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{original, collector_fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -294,6 +262,6 @@ defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
# Avoid warnings about HashDict being deprecated.
|
||||
module = HashDict
|
||||
concat(["#HashDict<", Inspect.List.inspect(module.to_list(dict), opts), ">"])
|
||||
concat ["#HashDict<", Inspect.List.inspect(module.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+26
-53
@@ -21,37 +21,37 @@ defmodule HashSet do
|
||||
@compile :inline_list_funcs
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@spec new :: Set.t()
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
%HashSet{}
|
||||
end
|
||||
|
||||
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
|
||||
set_fold(set1, set2, fn v, acc -> put(acc, v) end)
|
||||
set_fold set1, set2, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def union(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set2, set1, fn v, acc -> put(acc, v) end)
|
||||
set_fold set2, set1, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set1, %HashSet{}, fn v, acc ->
|
||||
set_fold set1, %HashSet{}, fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def difference(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold(set2, set1, fn v, acc -> delete(acc, v) end)
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], &[&1 | &2]) |> :lists.reverse()
|
||||
set_fold(set, [], &[&1 | &2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,20 +59,18 @@ defmodule HashSet do
|
||||
reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def member?(%HashSet{root: root}, term) do
|
||||
@@ -87,7 +85,7 @@ defmodule HashSet do
|
||||
def delete(%HashSet{root: root, size: size} = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> %HashSet{root: root, size: size - 1}
|
||||
:error -> set
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
@@ -95,8 +93,8 @@ defmodule HashSet do
|
||||
def reduce(%HashSet{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -114,29 +112,24 @@ defmodule HashSet do
|
||||
|
||||
defp do_member?(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] -> false
|
||||
[] -> false
|
||||
[^term | _] -> true
|
||||
[_] -> false
|
||||
[_ | n] -> do_member?(n, term, key_shift(hash))
|
||||
[_] -> false
|
||||
[_ | n] -> do_member?(n, term, key_shift(hash))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_put(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{put_elem(node, index, [term]), 1}
|
||||
|
||||
[^term | _] ->
|
||||
{node, 0}
|
||||
|
||||
[t] ->
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{put_elem(node, index, [t | n]), 1}
|
||||
|
||||
[t | n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{put_elem(node, index, [t | n]), counter}
|
||||
@@ -145,35 +138,28 @@ defmodule HashSet do
|
||||
|
||||
defp do_delete(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
|
||||
[^term] ->
|
||||
{:ok, put_elem(node, index, [])}
|
||||
|
||||
[_] ->
|
||||
:error
|
||||
|
||||
[^term | n] ->
|
||||
{:ok, put_elem(node, index, do_compact_node(n))}
|
||||
|
||||
[t | n] ->
|
||||
case do_delete(n, term, key_shift(hash)) do
|
||||
{:ok, @node_template} ->
|
||||
{:ok, put_elem(node, index, [t])}
|
||||
|
||||
{:ok, n} ->
|
||||
{:ok, put_elem(node, index, [t | n])}
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Enum.each(0..(@node_size - 1), fn index ->
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[t] ->
|
||||
@@ -181,17 +167,16 @@ defmodule HashSet do
|
||||
@node_template -> [t]
|
||||
n -> [t | n]
|
||||
end
|
||||
|
||||
[t | n] ->
|
||||
[t | put_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
## Set fold
|
||||
|
||||
defp do_fold_each([], acc, _fun), do: acc
|
||||
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
||||
defp do_fold_each([], acc, _fun), do: acc
|
||||
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
||||
defp do_fold_each([t | n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
|
||||
|
||||
defp do_fold(node, acc, fun, count) when count > 0 do
|
||||
@@ -226,12 +211,7 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(
|
||||
:erlang.element(count, node),
|
||||
acc,
|
||||
fun,
|
||||
&do_reduce(node, &1, fun, count - 1, next)
|
||||
)
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
@@ -273,24 +253,17 @@ defimpl Enumerable, for: HashSet do
|
||||
module = HashSet
|
||||
{:ok, module.size(set)}
|
||||
end
|
||||
|
||||
def slice(_set) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
def into(original) do
|
||||
# Avoid warnings about HashSet being deprecated.
|
||||
module = HashSet
|
||||
|
||||
collector_fun = fn
|
||||
{original, fn
|
||||
set, {:cont, term} -> module.put(set, term)
|
||||
set, :done -> set
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{original, collector_fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -300,6 +273,6 @@ defimpl Inspect, for: HashSet do
|
||||
def inspect(set, opts) do
|
||||
# Avoid warnings about HashSet being deprecated.
|
||||
module = HashSet
|
||||
concat(["#HashSet<", Inspect.List.inspect(module.to_list(set), opts), ">"])
|
||||
concat ["#HashSet<", Inspect.List.inspect(module.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+274
-89
@@ -1,8 +1,6 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
alias Code.Identifier
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
@@ -25,7 +23,7 @@ defprotocol Inspect do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat(["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"])
|
||||
concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +48,7 @@ defprotocol Inspect do
|
||||
implementation directly. For example, to test Inspect.MapSet above,
|
||||
you can invoke it as:
|
||||
|
||||
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
|
||||
Inspect.MapSet.inspect(MapSet.new, %Inspect.Opts{})
|
||||
|
||||
"""
|
||||
|
||||
@@ -64,12 +62,37 @@ defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
def inspect(atom, opts) do
|
||||
color(Identifier.inspect_as_atom(atom), color_key(atom), opts)
|
||||
color(inspect(atom), color_key(atom), opts)
|
||||
end
|
||||
|
||||
defp color_key(atom) when is_boolean(atom), do: :boolean
|
||||
defp color_key(nil), do: nil
|
||||
defp color_key(nil), do: :nil
|
||||
defp color_key(_), do: :atom
|
||||
|
||||
def inspect(atom) when is_nil(atom) or is_boolean(atom) do
|
||||
Atom.to_string(atom)
|
||||
end
|
||||
|
||||
def inspect(atom) when is_atom(atom) do
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
case Macro.classify_identifier(atom) do
|
||||
:alias ->
|
||||
case binary do
|
||||
binary when binary in ["Elixir", "Elixir.Elixir"] ->
|
||||
binary
|
||||
"Elixir.Elixir." <> _rest ->
|
||||
binary
|
||||
"Elixir." <> rest ->
|
||||
rest
|
||||
end
|
||||
type when type in [:callable, :not_callable] ->
|
||||
":" <> binary
|
||||
:other ->
|
||||
{escaped, _} = Inspect.BitString.escape(binary, ?")
|
||||
IO.iodata_to_binary [?:, ?", escaped, ?"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@@ -77,13 +100,12 @@ defimpl Inspect, for: BitString do
|
||||
%Inspect.Opts{binaries: bins, base: base, printable_limit: printable_limit} = opts
|
||||
|
||||
if base == :decimal and
|
||||
(bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
|
||||
(bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
|
||||
inspected =
|
||||
case Identifier.escape(term, ?", printable_limit) do
|
||||
case escape(term, ?", printable_limit) do
|
||||
{escaped, ""} -> [?", escaped, ?"]
|
||||
{escaped, _} -> [?", escaped, ?", " <> ..."]
|
||||
end
|
||||
|
||||
color(IO.iodata_to_binary(inspected), :string, opts)
|
||||
else
|
||||
inspect_bitstring(term, opts)
|
||||
@@ -94,6 +116,98 @@ defimpl Inspect, for: BitString do
|
||||
inspect_bitstring(term, opts)
|
||||
end
|
||||
|
||||
## Escaping
|
||||
|
||||
@doc false
|
||||
def escape(other, char) do
|
||||
escape(other, char, :infinity, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def escape(other, char, count) do
|
||||
escape(other, char, count, [])
|
||||
end
|
||||
|
||||
defp escape(<<_, _::binary>> = binary, _char, 0, acc) do
|
||||
{acc, binary}
|
||||
end
|
||||
defp escape(<<char, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | [?\\, char]])
|
||||
end
|
||||
defp escape(<<?#, ?{, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\\#{'])
|
||||
end
|
||||
defp escape(<<?\a, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\a'])
|
||||
end
|
||||
defp escape(<<?\b, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\b'])
|
||||
end
|
||||
defp escape(<<?\d, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\d'])
|
||||
end
|
||||
defp escape(<<?\e, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\e'])
|
||||
end
|
||||
defp escape(<<?\f, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\f'])
|
||||
end
|
||||
defp escape(<<?\n, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\n'])
|
||||
end
|
||||
defp escape(<<?\r, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\r'])
|
||||
end
|
||||
defp escape(<<?\\, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\\\'])
|
||||
end
|
||||
defp escape(<<?\t, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\t'])
|
||||
end
|
||||
defp escape(<<?\v, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | '\\v'])
|
||||
end
|
||||
defp escape(<<h::utf8, t::binary>>, char, count, acc)
|
||||
when h in 0x20..0x7E
|
||||
when h in 0xA0..0xD7FF
|
||||
when h in 0xE000..0xFFFD
|
||||
when h in 0x10000..0x10FFFF do
|
||||
escape(t, char, decrement(count), [acc | <<h::utf8>>])
|
||||
end
|
||||
defp escape(<<h, t::binary>>, char, count, acc) do
|
||||
escape(t, char, decrement(count), [acc | escape_char(h)])
|
||||
end
|
||||
defp escape(<<>>, _char, _count, acc) do
|
||||
{acc, <<>>}
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Also used by Regex
|
||||
def escape_char(0) do
|
||||
'\\0'
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::8>>
|
||||
['\\x', to_hex(a), to_hex(b)]
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::16>>
|
||||
['\\x{', to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}]
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
|
||||
['\\x{', to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}]
|
||||
end
|
||||
|
||||
defp to_hex(c) when c in 0..9, do: ?0 + c
|
||||
defp to_hex(c) when c in 10..15, do: ?A + c - 10
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring("", opts) do
|
||||
color("<<>>", :binary, opts)
|
||||
end
|
||||
@@ -101,8 +215,7 @@ defimpl Inspect, for: BitString do
|
||||
defp inspect_bitstring(bitstring, opts) do
|
||||
left = color("<<", :binary, opts)
|
||||
right = color(">>", :binary, opts)
|
||||
inner = each_bit(bitstring, opts.limit, opts)
|
||||
group(concat(concat(left, nest(inner, 2)), right))
|
||||
nest surround(left, each_bit(bitstring, opts.limit, opts), right), 1
|
||||
end
|
||||
|
||||
defp each_bit(_, 0, _) do
|
||||
@@ -118,10 +231,8 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t::bitstring>>, counter, opts) do
|
||||
flex_glue(
|
||||
concat(Inspect.Integer.inspect(h, opts), ","),
|
||||
each_bit(t, decrement(counter), opts)
|
||||
)
|
||||
glue(concat(Inspect.Integer.inspect(h, opts), ","),
|
||||
each_bit(t, decrement(counter), opts))
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter, opts) do
|
||||
@@ -132,7 +243,7 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@@ -142,26 +253,17 @@ defimpl Inspect, for: List do
|
||||
|
||||
# TODO: Remove :char_list and :as_char_lists handling in 2.0
|
||||
def inspect(term, opts) do
|
||||
%Inspect.Opts{
|
||||
charlists: lists,
|
||||
char_lists: lists_deprecated,
|
||||
printable_limit: printable_limit
|
||||
} = opts
|
||||
|
||||
%Inspect.Opts{charlists: lists, char_lists: lists_deprecated, printable_limit: printable_limit} = opts
|
||||
lists =
|
||||
if lists == :infer and lists_deprecated != :infer do
|
||||
case lists_deprecated do
|
||||
:as_char_lists ->
|
||||
IO.warn(
|
||||
"the :char_lists inspect option and its :as_char_lists " <>
|
||||
"value are deprecated, use the :charlists option and its " <>
|
||||
":as_charlists value instead"
|
||||
)
|
||||
|
||||
IO.warn "the :char_lists inspect option and its :as_char_lists " <>
|
||||
"value are deprecated, use the :charlists option and its " <>
|
||||
":as_charlists value instead"
|
||||
:as_charlists
|
||||
|
||||
_ ->
|
||||
IO.warn("the :char_lists inspect option is deprecated, use :charlists instead")
|
||||
IO.warn "the :char_lists inspect option is deprecated, use :charlists instead"
|
||||
lists_deprecated
|
||||
end
|
||||
else
|
||||
@@ -173,27 +275,24 @@ defimpl Inspect, for: List do
|
||||
close = color("]", :list, opts)
|
||||
|
||||
cond do
|
||||
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
lists == :as_charlists or (lists == :infer and printable?(term, printable_limit)) ->
|
||||
inspected =
|
||||
case Identifier.escape(IO.chardata_to_string(term), ?', printable_limit) do
|
||||
case Inspect.BitString.escape(IO.chardata_to_string(term), ?', printable_limit) do
|
||||
{escaped, ""} -> [?', escaped, ?']
|
||||
{escaped, _} -> [?', escaped, ?', " ++ ..."]
|
||||
end
|
||||
|
||||
IO.iodata_to_binary(inspected)
|
||||
|
||||
IO.iodata_to_binary inspected
|
||||
keyword?(term) ->
|
||||
container_doc(open, term, close, opts, &keyword/2, separator: sep, break: :strict)
|
||||
|
||||
surround_many(open, term, close, opts, &keyword/2, sep)
|
||||
true ->
|
||||
container_doc(open, term, close, opts, &to_doc/2, separator: sep)
|
||||
surround_many(open, term, close, opts, &to_doc/2, sep)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keyword({key, value}, opts) do
|
||||
key = color(Identifier.inspect_as_key(key), :atom, opts)
|
||||
concat(key, concat(" ", to_doc(value, opts)))
|
||||
key = color(key_to_binary(key) <> ": ", :atom, opts)
|
||||
concat(key, to_doc(value, opts))
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -204,8 +303,38 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
def keyword?([]), do: true
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
@doc false
|
||||
def printable?(list), do: printable?(list, :infinity)
|
||||
|
||||
@doc false
|
||||
def printable?(_, 0), do: true
|
||||
def printable?([char | rest], counter) when char in 32..126, do: printable?(rest, decrement(counter))
|
||||
def printable?([?\n | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\r | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\t | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\v | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\b | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\f | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\e | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([?\a | rest], counter), do: printable?(rest, decrement(counter))
|
||||
def printable?([], _counter), do: true
|
||||
def printable?(_, _counter), do: false
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
## Private
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@@ -213,14 +342,13 @@ defimpl Inspect, for: Tuple do
|
||||
open = color("{", :tuple, opts)
|
||||
sep = color(",", :tuple, opts)
|
||||
close = color("}", :tuple, opts)
|
||||
container_opts = [separator: sep, break: :flex]
|
||||
container_doc(open, Tuple.to_list(tuple), close, opts, &to_doc/2, container_opts)
|
||||
surround_many(open, Tuple.to_list(tuple), close, opts, &to_doc/2, sep)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
nest inspect(map, "", opts), 1
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
@@ -228,7 +356,7 @@ defimpl Inspect, for: Map do
|
||||
open = color("%" <> name <> "{", :map, opts)
|
||||
sep = color(",", :map, opts)
|
||||
close = color("}", :map, opts)
|
||||
container_doc(open, map, close, opts, traverse_fun(map, opts), separator: sep, break: :strict)
|
||||
surround_many(open, map, close, opts, traverse_fun(map, opts), sep)
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
@@ -241,7 +369,10 @@ defimpl Inspect, for: Map do
|
||||
end
|
||||
|
||||
defp to_map({key, value}, opts, sep) do
|
||||
concat(concat(to_doc(key, opts), sep), to_doc(value, opts))
|
||||
concat(
|
||||
concat(to_doc(key, opts), sep),
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -253,27 +384,20 @@ defimpl Inspect, for: Integer do
|
||||
|
||||
defp base_to_value(base) do
|
||||
case base do
|
||||
:binary -> 2
|
||||
:binary -> 2
|
||||
:decimal -> 10
|
||||
:octal -> 8
|
||||
:hex -> 16
|
||||
:octal -> 8
|
||||
:hex -> 16
|
||||
end
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, :decimal), do: value
|
||||
|
||||
defp prepend_prefix(<<?-, value::binary>>, base) do
|
||||
"-" <> prepend_prefix(value, base)
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, base) do
|
||||
prefix =
|
||||
case base do
|
||||
:binary -> "0b"
|
||||
:octal -> "0o"
|
||||
:hex -> "0x"
|
||||
end
|
||||
|
||||
prefix = case base do
|
||||
:binary -> "0b"
|
||||
:octal -> "0o"
|
||||
:hex -> "0x"
|
||||
end
|
||||
prefix <> value
|
||||
end
|
||||
end
|
||||
@@ -287,18 +411,48 @@ end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, opts) do
|
||||
{escaped, _} = Identifier.escape(regex.source, ?/, :infinity, &escape_map/1)
|
||||
source = IO.iodata_to_binary(['~r/', escaped, ?/, regex.opts])
|
||||
source = IO.iodata_to_binary(['~r/', escape(regex.source, ?/), ?/, regex.opts])
|
||||
color(source, :regex, opts)
|
||||
end
|
||||
|
||||
defp escape_map(?\a), do: '\\a'
|
||||
defp escape_map(?\f), do: '\\f'
|
||||
defp escape_map(?\n), do: '\\n'
|
||||
defp escape_map(?\r), do: '\\r'
|
||||
defp escape_map(?\t), do: '\\t'
|
||||
defp escape_map(?\v), do: '\\v'
|
||||
defp escape_map(_), do: false
|
||||
defp escape(bin, term),
|
||||
do: escape(bin, [], term)
|
||||
|
||||
defp escape(<<term, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | [?\\, term]], term)
|
||||
|
||||
# The list of characters is from 'String.printable?' implementation
|
||||
# minus characters treated specially by regex: \s, \d, \b, \e
|
||||
|
||||
defp escape(<<?\n, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\n'], term)
|
||||
|
||||
defp escape(<<?\r, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\r'], term)
|
||||
|
||||
defp escape(<<?\t, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\t'], term)
|
||||
|
||||
defp escape(<<?\v, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\v'], term)
|
||||
|
||||
defp escape(<<?\f, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\f'], term)
|
||||
|
||||
defp escape(<<?\a, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | '\\a'], term)
|
||||
|
||||
defp escape(<<char::utf8, rest::binary>>, buf, term)
|
||||
when char in 0x20..0x7E
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
when char in 0x10000..0x10FFFF,
|
||||
do: escape(rest, [buf | <<char::utf8>>], term)
|
||||
|
||||
defp escape(<<char, rest::binary>>, buf, term),
|
||||
do: escape(rest, [buf | Inspect.BitString.escape_char(char)], term)
|
||||
|
||||
defp escape(<<>>, buf, _), do: buf
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
@@ -308,9 +462,7 @@ defimpl Inspect, for: Function do
|
||||
name = fun_info[:name]
|
||||
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
inspected_as_atom = Identifier.inspect_as_atom(mod)
|
||||
inspected_as_function = Identifier.inspect_as_function(name)
|
||||
"&#{inspected_as_atom}.#{inspected_as_function}/#{fun_info[:arity]}"
|
||||
"&#{Inspect.Atom.inspect(mod)}.#{escape_name(name)}/#{fun_info[:arity]}"
|
||||
else
|
||||
case Atom.to_charlist(mod) do
|
||||
'elixir_compiler_' ++ _ ->
|
||||
@@ -319,17 +471,50 @@ defimpl Inspect, for: Function do
|
||||
else
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
|
||||
_ ->
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def escape_name(atom) when is_atom(atom) do
|
||||
string = Atom.to_string(atom)
|
||||
|
||||
case Macro.classify_identifier(atom) do
|
||||
:callable ->
|
||||
string
|
||||
type when type in [:not_callable, :alias] ->
|
||||
"\"" <> string <> "\""
|
||||
:other ->
|
||||
{escaped, _} = Inspect.BitString.escape(string, ?")
|
||||
IO.iodata_to_binary [?", escaped, ?"]
|
||||
end
|
||||
end
|
||||
|
||||
# Example of this format: -NAME/ARITY-fun-COUNT-
|
||||
def extract_anonymous_fun_parent(atom) when is_atom(atom) do
|
||||
extract_anonymous_fun_parent(Atom.to_string(atom))
|
||||
end
|
||||
|
||||
def extract_anonymous_fun_parent("-" <> rest) do
|
||||
[trailing | reversed] =
|
||||
rest
|
||||
|> String.split("/")
|
||||
|> Enum.reverse()
|
||||
|
||||
case String.split(trailing, "-") do
|
||||
[arity, _inner, _count, ""] ->
|
||||
{reversed |> Enum.reverse |> Enum.join("/") |> String.to_atom(), arity}
|
||||
_other ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def extract_anonymous_fun_parent(other) when is_binary(other), do: :error
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
inspected_as_atom = Identifier.inspect_as_atom(mod)
|
||||
extracted_name = extract_name(fun_info[:name])
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{inspected_as_atom}#{extracted_name}>"
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
|
||||
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name([]) do
|
||||
@@ -337,17 +522,17 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case Identifier.extract_anonymous_fun_parent(name) do
|
||||
case extract_anonymous_fun_parent(name) do
|
||||
{name, arity} ->
|
||||
"." <> Identifier.inspect_as_function(name) <> "/" <> arity
|
||||
|
||||
"." <> escape_name(name) <> "/" <> arity
|
||||
:error ->
|
||||
"." <> Identifier.inspect_as_function(name)
|
||||
"." <> escape_name(name)
|
||||
end
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
Integer.to_string(fun_info[:new_index]) <> "." <> Integer.to_string(fun_info[:uniq])
|
||||
Integer.to_string(fun_info[:new_index]) <> "." <>
|
||||
Integer.to_string(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -371,19 +556,19 @@ defimpl Inspect, for: Reference do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Any do
|
||||
def inspect(%module{} = struct, opts) do
|
||||
def inspect(%{__struct__: struct} = map, opts) do
|
||||
try do
|
||||
module.__struct__
|
||||
struct.__struct__
|
||||
rescue
|
||||
_ -> Inspect.Map.inspect(struct, opts)
|
||||
_ -> Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(struct) do
|
||||
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, struct))
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
|
||||
colorless_opts = %{opts | syntax_colors: []}
|
||||
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(module, colorless_opts), opts)
|
||||
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, colorless_opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(struct, opts)
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+238
-586
File diff suppressed because it is too large
Load Diff
+34
-26
@@ -28,7 +28,9 @@ defmodule Integer do
|
||||
false
|
||||
|
||||
"""
|
||||
defguard is_odd(integer) when is_integer(integer) and (integer &&& 1) == 1
|
||||
defmacro is_odd(integer) do
|
||||
quote do: (unquote(integer) &&& 1) == 1
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if an `integer` is even.
|
||||
@@ -53,7 +55,9 @@ defmodule Integer do
|
||||
true
|
||||
|
||||
"""
|
||||
defguard is_even(integer) when is_integer(integer) and (integer &&& 1) == 0
|
||||
defmacro is_even(integer) do
|
||||
quote do: (unquote(integer) &&& 1) == 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Computes the modulo remainder of an integer division.
|
||||
@@ -75,7 +79,6 @@ defmodule Integer do
|
||||
@spec mod(integer, neg_integer | pos_integer) :: integer
|
||||
def mod(dividend, divisor) do
|
||||
remainder = rem(dividend, divisor)
|
||||
|
||||
if remainder * divisor < 0 do
|
||||
remainder + divisor
|
||||
else
|
||||
@@ -107,7 +110,7 @@ defmodule Integer do
|
||||
"""
|
||||
@spec floor_div(integer, neg_integer | pos_integer) :: integer
|
||||
def floor_div(dividend, divisor) do
|
||||
if dividend * divisor < 0 and rem(dividend, divisor) != 0 do
|
||||
if (dividend * divisor < 0) and rem(dividend, divisor) != 0 do
|
||||
div(dividend, divisor) - 1
|
||||
else
|
||||
div(dividend, divisor)
|
||||
@@ -138,11 +141,14 @@ defmodule Integer do
|
||||
do_digits(integer, base, [])
|
||||
end
|
||||
|
||||
defp do_digits(digit, base, []) when abs(digit) < base, do: [digit]
|
||||
defp do_digits(digit, base, []) when digit == -base, do: [-1, 0]
|
||||
defp do_digits(base, base, []), do: [1, 0]
|
||||
defp do_digits(0, _base, acc), do: acc
|
||||
|
||||
defp do_digits(digit, base, []) when abs(digit) < base,
|
||||
do: [digit]
|
||||
defp do_digits(digit, base, []) when digit == -base,
|
||||
do: [-1, 0]
|
||||
defp do_digits(base, base, []),
|
||||
do: [1, 0]
|
||||
defp do_digits(0, _base, acc),
|
||||
do: acc
|
||||
defp do_digits(integer, base, acc),
|
||||
do: do_digits(div(integer, base), base, [rem(integer, base) | acc])
|
||||
|
||||
@@ -150,7 +156,7 @@ defmodule Integer do
|
||||
Returns the integer represented by the ordered `digits`.
|
||||
|
||||
An optional `base` value may be provided representing the radix for the `digits`.
|
||||
Base has to be an integer greater or equal than `2`.
|
||||
This one can be an integer >= 2.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -164,20 +170,24 @@ defmodule Integer do
|
||||
0
|
||||
|
||||
"""
|
||||
@spec undigits([integer], pos_integer) :: integer
|
||||
@spec undigits([integer], integer) :: integer
|
||||
def undigits(digits, base \\ 10) when is_list(digits) and is_integer(base) and base >= 2 do
|
||||
do_undigits(digits, base, 0)
|
||||
end
|
||||
|
||||
defp do_undigits([], _base, 0), do: 0
|
||||
defp do_undigits([digit], base, 0) when is_integer(digit) and digit < base, do: digit
|
||||
defp do_undigits([1, 0], base, 0), do: base
|
||||
defp do_undigits([0 | tail], base, 0), do: do_undigits(tail, base, 0)
|
||||
defp do_undigits([], _base, acc), do: acc
|
||||
defp do_undigits([], _base, 0),
|
||||
do: 0
|
||||
defp do_undigits([digit], base, 0) when is_integer(digit) and digit < base,
|
||||
do: digit
|
||||
defp do_undigits([1, 0], base, 0),
|
||||
do: base
|
||||
defp do_undigits([0 | tail], base, 0),
|
||||
do: do_undigits(tail, base, 0)
|
||||
|
||||
defp do_undigits([], _base, acc),
|
||||
do: acc
|
||||
defp do_undigits([digit | _], base, _) when is_integer(digit) and digit >= base,
|
||||
do: raise(ArgumentError, "invalid digit #{digit} in base #{base}")
|
||||
|
||||
do: raise ArgumentError, "invalid digit #{digit} in base #{base}"
|
||||
defp do_undigits([digit | tail], base, acc) when is_integer(digit),
|
||||
do: do_undigits(tail, base, acc * base + digit)
|
||||
|
||||
@@ -225,15 +235,14 @@ defmodule Integer do
|
||||
@spec parse(binary, 2..36) :: {integer, binary} | :error
|
||||
def parse(binary, base \\ 10)
|
||||
|
||||
def parse(_binary, base) when base not in 2..36 do
|
||||
raise ArgumentError, "invalid base #{inspect(base)}"
|
||||
def parse(_binary, base) when not base in 2..36 do
|
||||
raise ArgumentError, "invalid base #{inspect base}"
|
||||
end
|
||||
|
||||
def parse(binary, base) do
|
||||
case count_digits(binary, base) do
|
||||
0 ->
|
||||
:error
|
||||
|
||||
count ->
|
||||
{digits, rem} = :erlang.split_binary(binary, count)
|
||||
{:erlang.binary_to_integer(digits, base), rem}
|
||||
@@ -253,12 +262,11 @@ defmodule Integer do
|
||||
|
||||
digits = [{?0..?9, -?0}, {?A..?Z, 10 - ?A}, {?a..?z, 10 - ?a}]
|
||||
|
||||
for {chars, diff} <- digits,
|
||||
char <- chars do
|
||||
for {chars, diff} <- digits, char <- chars do
|
||||
digit = char + diff
|
||||
|
||||
defp count_digits_nosign(<<unquote(char), rest::binary>>, base, count)
|
||||
when base > unquote(digit) do
|
||||
when base > unquote(digit) do
|
||||
count_digits_nosign(rest, base, count + 1)
|
||||
end
|
||||
end
|
||||
@@ -286,7 +294,7 @@ defmodule Integer do
|
||||
"123"
|
||||
|
||||
"""
|
||||
@spec to_string(integer) :: String.t()
|
||||
@spec to_string(integer) :: String.t
|
||||
def to_string(integer) do
|
||||
:erlang.integer_to_binary(integer)
|
||||
end
|
||||
@@ -311,7 +319,7 @@ defmodule Integer do
|
||||
"ELIXIR"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, 2..36) :: String.t()
|
||||
@spec to_string(integer, 2..36) :: String.t
|
||||
def to_string(integer, base) do
|
||||
:erlang.integer_to_binary(integer, base)
|
||||
end
|
||||
|
||||
+29
-49
@@ -27,9 +27,9 @@ defmodule IO do
|
||||
* `:stderr` - a shortcut for the named process `:standard_error`
|
||||
provided in Erlang
|
||||
|
||||
IO devices maintain their position, which means subsequent calls to any
|
||||
reading or writing functions will start from the place where the device
|
||||
was last accessed. The position of files can be changed using the
|
||||
IO devices maintain their position, that means subsequent calls to any
|
||||
reading or writing functions will start from the place when the device
|
||||
was last accessed. Position of files can be changed using the
|
||||
`:file.position/2` function.
|
||||
|
||||
"""
|
||||
@@ -143,7 +143,8 @@ defmodule IO do
|
||||
@doc """
|
||||
Writes `item` to the given `device`.
|
||||
|
||||
By default, the `device` is the standard output.
|
||||
By default the `device` is the standard output.
|
||||
It returns `:ok` if it succeeds.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -154,9 +155,9 @@ defmodule IO do
|
||||
#=> error
|
||||
|
||||
"""
|
||||
@spec write(device, chardata | String.Chars.t()) :: :ok
|
||||
@spec write(device, chardata | String.Chars.t) :: :ok
|
||||
def write(device \\ :stdio, item) do
|
||||
:io.put_chars(map_dev(device), to_chardata(item))
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -171,28 +172,16 @@ defmodule IO do
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ :stdio, item) when is_iodata(item) do
|
||||
:file.write(map_dev(device), item)
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes `item` to the given `device`, similar to `write/2`,
|
||||
but adds a newline at the end.
|
||||
|
||||
By default, the `device` is the standard output. It returns `:ok`
|
||||
if it succeeds.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.puts "Hello World!"
|
||||
#=> Hello World!
|
||||
|
||||
IO.puts :stderr, "error"
|
||||
#=> error
|
||||
|
||||
"""
|
||||
@spec puts(device, chardata | String.Chars.t()) :: :ok
|
||||
@spec puts(device, chardata | String.Chars.t) :: :ok
|
||||
def puts(device \\ :stdio, item) do
|
||||
:io.put_chars(map_dev(device), [to_chardata(item), ?\n])
|
||||
:io.put_chars map_dev(device), [to_chardata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -212,17 +201,13 @@ defmodule IO do
|
||||
#=> my_app.ex:4: MyApp.main/1
|
||||
|
||||
"""
|
||||
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
|
||||
@spec warn(chardata | String.Chars.t, Exception.stacktrace) :: :ok
|
||||
def warn(message, []) do
|
||||
:elixir_errors.bare_warn(nil, nil, [to_chardata(message), ?\n])
|
||||
:elixir_errors.warn([to_chardata(message), ?\n])
|
||||
end
|
||||
|
||||
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
|
||||
formatted_trace = Enum.map_join(stacktrace, "\n ", &Exception.format_stacktrace_entry(&1))
|
||||
message = [to_chardata(message), ?\n, " ", formatted_trace, ?\n]
|
||||
line = opts[:line]
|
||||
file = opts[:file]
|
||||
:elixir_errors.bare_warn(line, file && List.to_string(file), message)
|
||||
def warn(message, stacktrace) when is_list(stacktrace) do
|
||||
formatted = Enum.map_join(stacktrace, "\n ", &Exception.format_stacktrace_entry(&1))
|
||||
:elixir_errors.warn([to_chardata(message), ?\n, " ", formatted, ?\n])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -237,7 +222,7 @@ defmodule IO do
|
||||
#=> (iex) evaluator.ex:108: IEx.Evaluator.eval/4
|
||||
|
||||
"""
|
||||
@spec warn(chardata | String.Chars.t()) :: :ok
|
||||
@spec warn(chardata | String.Chars.t) :: :ok
|
||||
def warn(message) do
|
||||
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
|
||||
warn(message, Enum.drop(stacktrace, 2))
|
||||
@@ -293,7 +278,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec inspect(item, keyword) :: item when item: var
|
||||
def inspect(item, opts \\ []) do
|
||||
inspect(:stdio, item, opts)
|
||||
inspect :stdio, item, opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -303,11 +288,10 @@ defmodule IO do
|
||||
"""
|
||||
@spec inspect(device, item, keyword) :: item when item: var
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
label = if label = opts[:label], do: [to_chardata(label), ": "], else: []
|
||||
opts = struct(Inspect.Opts, opts)
|
||||
doc = Inspect.Algebra.group(Inspect.Algebra.to_doc(item, opts))
|
||||
chardata = Inspect.Algebra.format(doc, opts.width)
|
||||
puts(device, [label, chardata])
|
||||
label = if (label = opts[:label]), do: [to_chardata(label), ": "], else: []
|
||||
opts = struct(Inspect.Opts, opts)
|
||||
chardata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
|
||||
puts device, [label, chardata]
|
||||
item
|
||||
end
|
||||
|
||||
@@ -321,8 +305,8 @@ defmodule IO do
|
||||
See `IO.getn/3` for a description of return values.
|
||||
|
||||
"""
|
||||
@spec getn(chardata | String.Chars.t(), pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t()) :: chardata | nodata
|
||||
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) and count > 0 do
|
||||
@@ -351,7 +335,7 @@ defmodule IO do
|
||||
NFS volume
|
||||
|
||||
"""
|
||||
@spec getn(device, chardata | String.Chars.t(), pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
def getn(device, prompt, count) when is_integer(count) and count > 0 do
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
end
|
||||
@@ -377,7 +361,7 @@ defmodule IO do
|
||||
IO.gets "What is your name?\n"
|
||||
|
||||
"""
|
||||
@spec gets(device, chardata | String.Chars.t()) :: chardata | nodata
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ :stdio, prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
@@ -406,7 +390,7 @@ defmodule IO do
|
||||
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
|
||||
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t()
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints)
|
||||
when line_or_codepoints == :line
|
||||
when is_integer(line_or_codepoints) and line_or_codepoints > 0 do
|
||||
@@ -431,7 +415,7 @@ defmodule IO do
|
||||
mode as it will return the wrong result.
|
||||
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t()
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes)
|
||||
when line_or_bytes == :line
|
||||
when is_integer(line_or_bytes) and line_or_bytes > 0 do
|
||||
@@ -457,7 +441,7 @@ defmodule IO do
|
||||
"string"
|
||||
|
||||
"""
|
||||
@spec chardata_to_string(chardata) :: String.t() | no_return
|
||||
@spec chardata_to_string(chardata) :: String.t | no_return
|
||||
def chardata_to_string(string) when is_binary(string) do
|
||||
string
|
||||
end
|
||||
@@ -519,10 +503,8 @@ defmodule IO do
|
||||
case read(device, line_or_codepoints) do
|
||||
:eof ->
|
||||
{:halt, device}
|
||||
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
|
||||
data ->
|
||||
{[data], device}
|
||||
end
|
||||
@@ -533,10 +515,8 @@ defmodule IO do
|
||||
case binread(device, line_or_chars) do
|
||||
:eof ->
|
||||
{:halt, device}
|
||||
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
|
||||
data ->
|
||||
{[data], device}
|
||||
end
|
||||
@@ -545,7 +525,7 @@ defmodule IO do
|
||||
@compile {:inline, map_dev: 1, to_chardata: 1}
|
||||
|
||||
# Map the Elixir names for standard IO and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
|
||||
+42
-43
@@ -26,8 +26,7 @@ defmodule IO.ANSI do
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@type ansicode :: atom
|
||||
@type ansilist ::
|
||||
maybe_improper_list(char | ansicode | binary | ansilist, binary | ansicode | [])
|
||||
@type ansilist :: maybe_improper_list(char | ansicode | binary | ansilist, binary | ansicode | [])
|
||||
@type ansidata :: ansilist | ansicode | binary
|
||||
|
||||
@doc """
|
||||
@@ -44,7 +43,7 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc "Sets foreground color."
|
||||
@spec color(0..255) :: String.t()
|
||||
@spec color(0..255) :: String.t
|
||||
def color(code) when code in 0..255, do: "\e[38;5;#{code}m"
|
||||
|
||||
@doc ~S"""
|
||||
@@ -52,13 +51,13 @@ defmodule IO.ANSI do
|
||||
|
||||
Valid values for each color are in the range 0 to 5.
|
||||
"""
|
||||
@spec color(0..5, 0..5, 0..5) :: String.t()
|
||||
@spec color(0..5, 0..5, 0..5) :: String.t
|
||||
def color(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
|
||||
color(16 + 36 * r + 6 * g + b)
|
||||
color(16 + (36 * r) + (6 * g) + b)
|
||||
end
|
||||
|
||||
@doc "Sets background color."
|
||||
@spec color_background(0..255) :: String.t()
|
||||
@spec color_background(0..255) :: String.t
|
||||
def color_background(code) when code in 0..255, do: "\e[48;5;#{code}m"
|
||||
|
||||
@doc ~S"""
|
||||
@@ -66,118 +65,118 @@ defmodule IO.ANSI do
|
||||
|
||||
Valid values for each color are in the range 0 to 5.
|
||||
"""
|
||||
@spec color_background(0..5, 0..5, 0..5) :: String.t()
|
||||
@spec color_background(0..5, 0..5, 0..5) :: String.t
|
||||
def color_background(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
|
||||
color_background(16 + 36 * r + 6 * g + b)
|
||||
color_background(16 + (36 * r) + (6 * g) + b)
|
||||
end
|
||||
|
||||
@doc "Resets all attributes."
|
||||
defsequence(:reset, 0)
|
||||
defsequence :reset, 0
|
||||
|
||||
@doc "Bright (increased intensity) or bold."
|
||||
defsequence(:bright, 1)
|
||||
defsequence :bright, 1
|
||||
|
||||
@doc "Faint (decreased intensity). Not widely supported."
|
||||
defsequence(:faint, 2)
|
||||
defsequence :faint, 2
|
||||
|
||||
@doc "Italic: on. Not widely supported. Sometimes treated as inverse."
|
||||
defsequence(:italic, 3)
|
||||
defsequence :italic, 3
|
||||
|
||||
@doc "Underline: single."
|
||||
defsequence(:underline, 4)
|
||||
defsequence :underline, 4
|
||||
|
||||
@doc "Blink: slow. Less than 150 per minute."
|
||||
defsequence(:blink_slow, 5)
|
||||
defsequence :blink_slow, 5
|
||||
|
||||
@doc "Blink: rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported."
|
||||
defsequence(:blink_rapid, 6)
|
||||
defsequence :blink_rapid, 6
|
||||
|
||||
@doc "Image: negative. Swap foreground and background."
|
||||
defsequence(:inverse, 7)
|
||||
defsequence :inverse, 7
|
||||
|
||||
@doc "Image: negative. Swap foreground and background."
|
||||
defsequence(:reverse, 7)
|
||||
defsequence :reverse, 7
|
||||
|
||||
@doc "Conceal. Not widely supported."
|
||||
defsequence(:conceal, 8)
|
||||
defsequence :conceal, 8
|
||||
|
||||
@doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported."
|
||||
defsequence(:crossed_out, 9)
|
||||
defsequence :crossed_out, 9
|
||||
|
||||
@doc "Sets primary (default) font."
|
||||
defsequence(:primary_font, 10)
|
||||
defsequence :primary_font, 10
|
||||
|
||||
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}."
|
||||
defsequence(:"font_#{font_n}", font_n + 10)
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
|
||||
@doc "Normal color or intensity."
|
||||
defsequence(:normal, 22)
|
||||
defsequence :normal, 22
|
||||
|
||||
@doc "Not italic."
|
||||
defsequence(:not_italic, 23)
|
||||
defsequence :not_italic, 23
|
||||
|
||||
@doc "Underline: none."
|
||||
defsequence(:no_underline, 24)
|
||||
defsequence :no_underline, 24
|
||||
|
||||
@doc "Blink: off."
|
||||
defsequence(:blink_off, 25)
|
||||
defsequence :blink_off, 25
|
||||
|
||||
@doc "Image: positive. Normal foreground and background."
|
||||
defsequence(:inverse_off, 27)
|
||||
defsequence :inverse_off, 27
|
||||
|
||||
@doc "Image: positive. Normal foreground and background."
|
||||
defsequence(:reverse_off, 27)
|
||||
defsequence :reverse_off, 27
|
||||
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
|
||||
for {color, code} <- Enum.with_index(colors) do
|
||||
@doc "Sets foreground color to #{color}."
|
||||
defsequence(color, code + 30)
|
||||
defsequence color, code + 30
|
||||
|
||||
@doc "Sets foreground color to light #{color}."
|
||||
defsequence(:"light_#{color}", code + 90)
|
||||
defsequence :"light_#{color}", code + 90
|
||||
|
||||
@doc "Sets background color to #{color}."
|
||||
defsequence(:"#{color}_background", code + 40)
|
||||
defsequence :"#{color}_background", code + 40
|
||||
|
||||
@doc "Sets background color to light #{color}."
|
||||
defsequence(:"light_#{color}_background", code + 100)
|
||||
defsequence :"light_#{color}_background", code + 100
|
||||
end
|
||||
|
||||
@doc "Default text color."
|
||||
defsequence(:default_color, 39)
|
||||
defsequence :default_color, 39
|
||||
|
||||
@doc "Default background color."
|
||||
defsequence(:default_background, 49)
|
||||
defsequence :default_background, 49
|
||||
|
||||
@doc "Framed."
|
||||
defsequence(:framed, 51)
|
||||
defsequence :framed, 51
|
||||
|
||||
@doc "Encircled."
|
||||
defsequence(:encircled, 52)
|
||||
defsequence :encircled, 52
|
||||
|
||||
@doc "Overlined."
|
||||
defsequence(:overlined, 53)
|
||||
defsequence :overlined, 53
|
||||
|
||||
@doc "Not framed or encircled."
|
||||
defsequence(:not_framed_encircled, 54)
|
||||
defsequence :not_framed_encircled, 54
|
||||
|
||||
@doc "Not overlined."
|
||||
defsequence(:not_overlined, 55)
|
||||
defsequence :not_overlined, 55
|
||||
|
||||
@doc "Sends cursor home."
|
||||
defsequence(:home, "", "H")
|
||||
defsequence :home, "", "H"
|
||||
|
||||
@doc "Clears screen."
|
||||
defsequence(:clear, "2", "J")
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
@doc "Clears line."
|
||||
defsequence(:clear_line, "2", "K")
|
||||
defsequence :clear_line, "2", "K"
|
||||
|
||||
defp format_sequence(other) do
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{inspect(other)}"
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{inspect other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -244,7 +243,7 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp do_format([], [], acc, true, true) do
|
||||
[acc | IO.ANSI.reset()]
|
||||
[acc | IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp do_format([], [], acc, _emit?, _append_reset) do
|
||||
|
||||
@@ -23,17 +23,15 @@ defmodule IO.ANSI.Docs do
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def default_options do
|
||||
[
|
||||
enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan],
|
||||
doc_headings: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
doc_underline: [:underline],
|
||||
width: 80
|
||||
]
|
||||
[enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan],
|
||||
doc_headings: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
doc_underline: [:underline],
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -42,9 +40,9 @@ defmodule IO.ANSI.Docs do
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, options \\ []) do
|
||||
IO.puts(IO.ANSI.reset())
|
||||
IO.puts IO.ANSI.reset
|
||||
options = Keyword.merge(default_options(), options)
|
||||
width = options[:width]
|
||||
width = options[:width]
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.pad_leading(padding) |> String.pad_trailing(width)
|
||||
write(:doc_title, heading, options)
|
||||
@@ -59,7 +57,6 @@ defmodule IO.ANSI.Docs do
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
options = Keyword.merge(default_options(), options)
|
||||
|
||||
doc
|
||||
|> String.split(["\r\n", "\n"], trim: false)
|
||||
|> Enum.map(&String.trim_trailing/1)
|
||||
@@ -73,23 +70,18 @@ defmodule IO.ANSI.Docs do
|
||||
defp process(["# " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["## " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["### " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["#### " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["##### " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["###### " <> _ = heading | rest], text, indent, options) do
|
||||
write_heading(heading, rest, text, indent, options)
|
||||
end
|
||||
@@ -114,16 +106,13 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp process(all = [line | rest], text, indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0, :infinity)
|
||||
|
||||
cond do
|
||||
link_label?(stripped, count) ->
|
||||
write_text([line], indent, options, true)
|
||||
process(rest, text, indent, options)
|
||||
|
||||
table_line?(stripped) and rest != [] and table_line?(hd(rest)) ->
|
||||
write_text(text, indent, options)
|
||||
process_table(all, indent, options)
|
||||
|
||||
true ->
|
||||
process_rest(stripped, rest, count, text, indent, options)
|
||||
end
|
||||
@@ -145,15 +134,12 @@ defmodule IO.ANSI.Docs do
|
||||
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list("• ", item, rest, count, indent, options)
|
||||
|
||||
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
|
||||
|
||||
<<d1, d2, ?., ?\s, item::binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
|
||||
|
||||
_ ->
|
||||
process(rest, [stripped | text], indent, options)
|
||||
end
|
||||
@@ -173,7 +159,6 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp process_list_next([line | rest], count, max, acc) do
|
||||
{stripped, next_count} = strip_spaces(line, 0, max)
|
||||
|
||||
case process_list_next_kind(stripped, rest, count, next_count) do
|
||||
:next -> process_list_next(rest, count, max, [stripped | acc])
|
||||
:done -> {Enum.reverse(acc), [line | rest], true}
|
||||
@@ -189,20 +174,14 @@ defmodule IO.ANSI.Docs do
|
||||
case {stripped, rest} do
|
||||
{<<bullet, ?\s, _::binary>>, _} when bullet in @bullets and next_count <= count ->
|
||||
:list
|
||||
|
||||
{<<d1, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
|
||||
{<<d1, d2, ?., ?\s, _::binary>>, _}
|
||||
when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
{<<d1, d2, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
|
||||
{"", [" " <> _ | _]} ->
|
||||
:next
|
||||
|
||||
{"", _} ->
|
||||
:done
|
||||
|
||||
_ ->
|
||||
:next
|
||||
end
|
||||
@@ -285,7 +264,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp table_lines(lines, options) do
|
||||
lines = Enum.map(lines, &split_into_columns(&1, options))
|
||||
count = Enum.map(lines, &length/1) |> Enum.max()
|
||||
count = Enum.map(lines, &length/1) |> Enum.max
|
||||
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
|
||||
|
||||
widths =
|
||||
@@ -297,7 +276,10 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
end
|
||||
|
||||
col_widths = Enum.reduce(widths, List.duplicate(0, count), &max_column_widths/2)
|
||||
col_widths = Enum.reduce(widths,
|
||||
List.duplicate(0, count),
|
||||
&max_column_widths/2)
|
||||
|
||||
render_table(lines, col_widths, options)
|
||||
end
|
||||
|
||||
@@ -316,7 +298,6 @@ defmodule IO.ANSI.Docs do
|
||||
|> String.trim()
|
||||
|> handle_links
|
||||
|> handle_inline(options)
|
||||
|
||||
{col, length_without_escape(col, 0)}
|
||||
end
|
||||
|
||||
@@ -329,7 +310,6 @@ defmodule IO.ANSI.Docs do
|
||||
# If second line is heading separator, use the heading style on the first
|
||||
defp render_table([first, second | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
|
||||
if table_header?(second) do
|
||||
alignments = Enum.map(second, &column_alignment/1)
|
||||
options = Keyword.put_new(options, :alignments, alignments)
|
||||
@@ -347,7 +327,8 @@ defmodule IO.ANSI.Docs do
|
||||
render_table(rest, widths, options)
|
||||
end
|
||||
|
||||
defp render_table([], _, _), do: nil
|
||||
defp render_table([], _, _),
|
||||
do: nil
|
||||
|
||||
defp column_alignment({line, _}) do
|
||||
cond do
|
||||
@@ -357,14 +338,14 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
end
|
||||
|
||||
defp table_header?(line) do
|
||||
Enum.all?(line, fn {col, _} -> table_header_column?(col) end)
|
||||
defp table_header?(row) do
|
||||
Enum.all?(row, fn {col, _} -> table_header_column?(col) end)
|
||||
end
|
||||
|
||||
defp table_header_column?(":" <> rest), do: table_header_contents?(rest)
|
||||
defp table_header_column?(col), do: table_header_contents?(col)
|
||||
defp table_header_column?(":" <> row), do: table_header_contents?(row)
|
||||
defp table_header_column?(row), do: table_header_contents?(row)
|
||||
|
||||
defp table_header_contents?("-" <> rest), do: table_header_contents?(rest)
|
||||
defp table_header_contents?("-" <> row), do: table_header_contents?(row)
|
||||
defp table_header_contents?(":"), do: true
|
||||
defp table_header_contents?(""), do: true
|
||||
defp table_header_contents?(_), do: false
|
||||
@@ -381,22 +362,22 @@ defmodule IO.ANSI.Docs do
|
||||
if heading do
|
||||
write(:doc_table_heading, columns, options)
|
||||
else
|
||||
IO.puts(columns)
|
||||
IO.puts columns
|
||||
end
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, length}, width}, :center}) do
|
||||
col
|
||||
|> String.pad_leading(div(width, 2) - div(length, 2) + length)
|
||||
|> String.pad_trailing(width + 1 - rem(width, 2))
|
||||
pad = if rem(length, 2) == 0, do: 1, else: rem(width, 2)
|
||||
spaces = div(width, 2) - div(length, 2)
|
||||
String.duplicate(" ", spaces) <> col <> String.duplicate(" ", spaces + pad)
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, _length}, width}, :right}) do
|
||||
String.pad_leading(col, width)
|
||||
defp generate_table_cell({{{col, length}, width}, :right}) do
|
||||
String.duplicate(" ", width - length) <> col
|
||||
end
|
||||
|
||||
defp generate_table_cell({{{col, _length}, width}, :left}) do
|
||||
String.pad_trailing(col, width)
|
||||
defp generate_table_cell({{{col, length}, width}, _}) do
|
||||
col <> String.duplicate(" ", width - length)
|
||||
end
|
||||
|
||||
defp table_line?(line) do
|
||||
@@ -413,11 +394,13 @@ defmodule IO.ANSI.Docs do
|
||||
defp link_label?(""), do: false
|
||||
defp link_label?(<<_>> <> rest), do: link_label?(rest)
|
||||
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max, do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max), do: {rest, acc}
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max,
|
||||
do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max),
|
||||
do: {rest, acc}
|
||||
|
||||
defp write(style, string, options) do
|
||||
IO.puts([color(style, options), string, IO.ANSI.reset()])
|
||||
IO.puts [color(style, options), string, IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
@@ -426,7 +409,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp write_with_wrap(words, available, indent, first) do
|
||||
{words, rest} = take_words(words, available, [])
|
||||
IO.puts(if(first, do: "", else: indent) <> Enum.join(words, " "))
|
||||
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
|
||||
write_with_wrap(rest, available, indent, false)
|
||||
end
|
||||
|
||||
@@ -475,13 +458,13 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
~r{https?\S*}
|
||||
|> Regex.recompile!()
|
||||
|> Regex.recompile!
|
||||
|> Regex.replace(text, &String.replace(&1, "_", "\\_"))
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
~r{\[(.*?)\]\((.*?)\)}
|
||||
|> Regex.recompile!()
|
||||
|> Regex.recompile!
|
||||
|> Regex.replace(text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
@@ -500,8 +483,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# Characters that can mark the beginning or the end of a word.
|
||||
# Only support the most common ones at this moment.
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&] ++
|
||||
[?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
|
||||
|
||||
# Inline start
|
||||
|
||||
@@ -520,29 +502,29 @@ defmodule IO.ANSI.Docs do
|
||||
# Inline delimiters
|
||||
|
||||
defp handle_inline(<<delimiter, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters do
|
||||
when rest != "" and delimiter in @delimiters do
|
||||
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<delimiter, mark, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters and mark in @single do
|
||||
when rest != "" and delimiter in @delimiters and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?`, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer) | acc], options)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, mark, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and mark in @single do
|
||||
when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer) | acc], options)
|
||||
end
|
||||
|
||||
@@ -552,31 +534,29 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# An escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest::binary>>, limit, buffer, acc, options)
|
||||
when not (mark == limit and mark == ?`) do
|
||||
when not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark | buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
|
||||
defp handle_inline(<<?*, ?*, delimiter, rest::binary>>, ?d, buffer, acc, options)
|
||||
when delimiter in @delimiters do
|
||||
inline_buffer = inline_buffer(buffer, options)
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer | acc], options)
|
||||
when delimiter in @delimiters do
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, delimiter, rest::binary>>, mark, buffer, acc, options)
|
||||
when delimiter in @delimiters and mark in @single do
|
||||
inline_buffer = inline_buffer(buffer, options)
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer | acc], options)
|
||||
when delimiter in @delimiters and mark in @single do
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest::binary>>, ?d, buffer, acc, options)
|
||||
when rest == "" do
|
||||
when rest == "" do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options) | acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest::binary>>, mark, buffer, acc, options)
|
||||
when rest == "" and mark in @single do
|
||||
when rest == "" and mark in @single do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options) | acc], options)
|
||||
end
|
||||
|
||||
@@ -591,19 +571,19 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
|
||||
IO.iodata_to_binary(Enum.reverse([Enum.reverse(buffer) | acc]))
|
||||
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer) | acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, options) do
|
||||
[h | t] = Enum.reverse([IO.ANSI.reset() | buffer])
|
||||
[h | t] = Enum.reverse([IO.ANSI.reset | buffer])
|
||||
[color_for(h, options) | t]
|
||||
end
|
||||
|
||||
defp color_for(mark, colors) do
|
||||
case mark do
|
||||
"`" -> color(:doc_inline_code, colors)
|
||||
"_" -> color(:doc_underline, colors)
|
||||
"*" -> color(:doc_bold, colors)
|
||||
"`" -> color(:doc_inline_code, colors)
|
||||
"_" -> color(:doc_underline, colors)
|
||||
"*" -> color(:doc_bold, colors)
|
||||
"**" -> color(:doc_bold, colors)
|
||||
end
|
||||
end
|
||||
@@ -613,5 +593,5 @@ defmodule IO.ANSI.Docs do
|
||||
IO.ANSI.format_fragment(color, colors[:enabled])
|
||||
end
|
||||
|
||||
defp newline_after_block, do: IO.puts(IO.ANSI.reset())
|
||||
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule IO.StreamError do
|
||||
defexception [:reason, :message]
|
||||
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
reason = opts[:reason]
|
||||
formatted = IO.iodata_to_binary(:file.format_error(reason))
|
||||
%IO.StreamError{message: "error during streaming: #{formatted}", reason: reason}
|
||||
end
|
||||
@@ -16,7 +16,7 @@ defmodule IO.Stream do
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given number of bytes
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
It is worth noting that an IO stream has side effects and every time you go
|
||||
over the stream you may get different results.
|
||||
@@ -41,12 +41,10 @@ defmodule IO.Stream do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
|
||||
:ok, _ ->
|
||||
stream
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -55,11 +53,10 @@ defmodule IO.Stream do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(fn -> device end, next_fun, & &1).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
@@ -69,9 +66,5 @@ defmodule IO.Stream do
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def slice(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+363
-870
File diff suppressed because it is too large
Load Diff
+79
-118
@@ -1,17 +1,9 @@
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
@blank_config %{
|
||||
commands: [],
|
||||
output: ".",
|
||||
compile: [],
|
||||
halt: true,
|
||||
compiler_options: [],
|
||||
errors: [],
|
||||
pa: [],
|
||||
pz: [],
|
||||
verbose_compile: false
|
||||
}
|
||||
@blank_config %{commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
pa: [], pz: [], verbose_compile: false}
|
||||
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
@@ -22,16 +14,14 @@ defmodule Kernel.CLI do
|
||||
{config, argv} = parse_argv(argv)
|
||||
System.argv(argv)
|
||||
|
||||
fun = fn _ ->
|
||||
run fn _ ->
|
||||
errors = process_commands(config)
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, &IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
run(fun, config.halt)
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -44,7 +34,6 @@ defmodule Kernel.CLI do
|
||||
"""
|
||||
def run(fun, halt \\ true) do
|
||||
{ok_or_shutdown, status} = exec_fun(fun, {:ok, 0})
|
||||
|
||||
if ok_or_shutdown == :shutdown or halt do
|
||||
{_, status} = at_exit({ok_or_shutdown, status})
|
||||
|
||||
@@ -66,25 +55,21 @@ defmodule Kernel.CLI do
|
||||
@doc false
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
Enum.reverse(config.errors, errors)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_error(kind, reason, stacktrace) do
|
||||
{blamed, stacktrace} = Exception.blame(kind, reason, stacktrace)
|
||||
|
||||
iodata =
|
||||
case blamed do
|
||||
%FunctionClauseError{} ->
|
||||
formatted = Exception.format_banner(kind, reason, stacktrace)
|
||||
padded_blame = pad(FunctionClauseError.blame(blamed, &inspect/1, &blame_match/2))
|
||||
[formatted, padded_blame]
|
||||
|
||||
[Exception.format_banner(kind, reason, stacktrace),
|
||||
pad(FunctionClauseError.blame(blamed, &inspect/1, &blame_match/2))]
|
||||
_ ->
|
||||
Exception.format_banner(kind, blamed, stacktrace)
|
||||
end
|
||||
|
||||
[iodata, ?\n, Exception.format_stacktrace(prune_stacktrace(stacktrace))]
|
||||
end
|
||||
|
||||
@@ -105,24 +90,22 @@ defmodule Kernel.CLI do
|
||||
fun.(elem(res, 1))
|
||||
catch
|
||||
:exit, {:shutdown, int} when is_integer(int) ->
|
||||
send(parent, {self(), {:shutdown, int}})
|
||||
send parent, {self(), {:shutdown, int}}
|
||||
exit({:shutdown, int})
|
||||
|
||||
:exit, reason
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
|
||||
send(parent, {self(), {:shutdown, 0}})
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
|
||||
send parent, {self(), {:shutdown, 0}}
|
||||
exit(reason)
|
||||
|
||||
kind, reason ->
|
||||
stack = System.stacktrace()
|
||||
stack = System.stacktrace
|
||||
print_error(kind, reason, stack)
|
||||
send(parent, {self(), {:shutdown, 1}})
|
||||
send parent, {self(), {:shutdown, 1}}
|
||||
exit(to_exit(kind, reason, stack))
|
||||
else
|
||||
_ ->
|
||||
send(parent, {self(), res})
|
||||
send parent, {self(), res}
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -130,7 +113,6 @@ defmodule Kernel.CLI do
|
||||
{^pid, res} ->
|
||||
:erlang.demonitor(ref, [:flush])
|
||||
res
|
||||
|
||||
{:DOWN, ^ref, _, _, other} ->
|
||||
print_error({:EXIT, pid}, other, [])
|
||||
{:shutdown, 1}
|
||||
@@ -146,7 +128,6 @@ defmodule Kernel.CLI do
|
||||
{[h | hs], _} when h == hd(list) ->
|
||||
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
|
||||
callback.(hs, new_config)
|
||||
|
||||
{new_list, new_config} ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
@@ -155,15 +136,18 @@ defmodule Kernel.CLI do
|
||||
## Error handling
|
||||
|
||||
defp print_error(kind, reason, stacktrace) do
|
||||
IO.write(:stderr, format_error(kind, reason, stacktrace))
|
||||
IO.write :stderr, format_error(kind, reason, stacktrace)
|
||||
end
|
||||
|
||||
defp blame_match(%{match?: true, node: node}, _), do: blame_ansi(:normal, "+", node)
|
||||
defp blame_match(%{match?: false, node: node}, _), do: blame_ansi(:red, "-", node)
|
||||
defp blame_match(_, string), do: string
|
||||
defp blame_match(%{match?: true, node: node}, _),
|
||||
do: blame_ansi(:normal, "+", node)
|
||||
defp blame_match(%{match?: false, node: node}, _),
|
||||
do: blame_ansi(:red, "-", node)
|
||||
defp blame_match(_, string),
|
||||
do: string
|
||||
|
||||
defp blame_ansi(color, no_ansi, node) do
|
||||
if IO.ANSI.enabled?() do
|
||||
if IO.ANSI.enabled? do
|
||||
[color | Macro.to_string(node)]
|
||||
|> IO.ANSI.format(true)
|
||||
|> IO.iodata_to_binary()
|
||||
@@ -176,9 +160,9 @@ defmodule Kernel.CLI do
|
||||
" " <> String.replace(string, "\n", "\n ")
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir, :elixir_expand, :elixir_compiler, :elixir_module] ++
|
||||
[:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map] ++
|
||||
[:elixir_erl, :elixir_erl_clauses, :elixir_erl_pass, Kernel.ErrorHandler]
|
||||
@elixir_internals [:elixir, :elixir_expand, :elixir_compiler, :elixir_module,
|
||||
:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map,
|
||||
:elixir_erl, :elixir_erl_clauses, :elixir_erl_pass, Kernel.ErrorHandler]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _} | t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
@@ -199,59 +183,54 @@ defmodule Kernel.CLI do
|
||||
# Parse shared options
|
||||
|
||||
defp parse_shared([opt | _t], _config) when opt in ["-v", "--version"] do
|
||||
if function_exported?(IEx, :started?, 0) and IEx.started?() do
|
||||
IO.puts("IEx " <> System.build_info()[:build])
|
||||
if function_exported?(IEx, :started?, 0) and IEx.started? do
|
||||
IO.puts "IEx " <> System.build_info[:build]
|
||||
else
|
||||
IO.puts(:erlang.system_info(:system_version))
|
||||
IO.puts("Elixir " <> System.build_info()[:build])
|
||||
IO.puts :erlang.system_info(:system_version)
|
||||
IO.puts "Elixir " <> System.build_info[:build]
|
||||
end
|
||||
|
||||
System.halt(0)
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h | t], config) do
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_patha/1)
|
||||
parse_shared(t, %{config | pa: config.pa ++ paths})
|
||||
parse_shared t, %{config | pa: config.pa ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["-pz", h | t], config) do
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_pathz/1)
|
||||
parse_shared(t, %{config | pz: config.pz ++ paths})
|
||||
parse_shared t, %{config | pz: config.pz ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["--app", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:app, h} | config.commands]})
|
||||
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp parse_shared(["--no-halt" | t], config) do
|
||||
parse_shared(t, %{config | halt: false})
|
||||
parse_shared t, %{config | halt: false}
|
||||
end
|
||||
|
||||
defp parse_shared(["-e", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
|
||||
parse_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp parse_shared(["-r", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:require, h} | config.commands]})
|
||||
parse_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp parse_shared(["-pr", h | t], config) do
|
||||
parse_shared(t, %{config | commands: [{:parallel_require, h} | config.commands]})
|
||||
parse_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
end
|
||||
|
||||
@erl_arg_options ["--erl", "--sname", "--name", "--cookie"] ++
|
||||
["--logger-otp-reports", "--logger-sasl-reports"]
|
||||
|
||||
@erl_boolean_options ["--detached", "--hidden", "--werl"]
|
||||
|
||||
defp parse_shared([erl, _ | t], config) when erl in @erl_arg_options do
|
||||
parse_shared(t, config)
|
||||
defp parse_shared([erl, _ | t], config) when erl in ["--erl", "--sname", "--name", "--cookie", "--logger-otp-reports", "--logger-sasl-reports"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl | t], config) when erl in @erl_boolean_options do
|
||||
parse_shared(t, config)
|
||||
defp parse_shared([erl | t], config) when erl in ["--detached", "--hidden", "--werl"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared(list, config) do
|
||||
@@ -260,9 +239,8 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp expand_code_path(path) do
|
||||
path = Path.expand(path)
|
||||
|
||||
case Path.wildcard(path) do
|
||||
[] -> [to_charlist(path)]
|
||||
[] -> [to_charlist(path)]
|
||||
list -> Enum.map(list, &to_charlist/1)
|
||||
end
|
||||
end
|
||||
@@ -274,11 +252,11 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_argv(["+elixirc" | t], config) do
|
||||
parse_compiler(t, config)
|
||||
parse_compiler t, config
|
||||
end
|
||||
|
||||
defp parse_argv(["+iex" | t], config) do
|
||||
parse_iex(t, config)
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp parse_argv(["-S", h | t], config) do
|
||||
@@ -288,8 +266,7 @@ defmodule Kernel.CLI do
|
||||
defp parse_argv([h | t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option?(list, config, &parse_argv(&1, &2))
|
||||
|
||||
shared_option? list, config, &parse_argv(&1, &2)
|
||||
_ ->
|
||||
if Keyword.has_key?(config.commands, :eval) do
|
||||
{config, list}
|
||||
@@ -310,40 +287,36 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_compiler(["-o", h | t], config) do
|
||||
parse_compiler(t, %{config | output: h})
|
||||
parse_compiler t, %{config | output: h}
|
||||
end
|
||||
|
||||
defp parse_compiler(["--no-docs" | t], config) do
|
||||
parse_compiler(t, %{config | compiler_options: [{:docs, false} | config.compiler_options]})
|
||||
parse_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp parse_compiler(["--no-debug-info" | t], config) do
|
||||
compiler_options = [{:debug_info, false} | config.compiler_options]
|
||||
parse_compiler(t, %{config | compiler_options: compiler_options})
|
||||
parse_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp parse_compiler(["--ignore-module-conflict" | t], config) do
|
||||
compiler_options = [{:ignore_module_conflict, true} | config.compiler_options]
|
||||
parse_compiler(t, %{config | compiler_options: compiler_options})
|
||||
parse_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp parse_compiler(["--warnings-as-errors" | t], config) do
|
||||
compiler_options = [{:warnings_as_errors, true} | config.compiler_options]
|
||||
parse_compiler(t, %{config | compiler_options: compiler_options})
|
||||
parse_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp parse_compiler(["--verbose" | t], config) do
|
||||
parse_compiler(t, %{config | verbose_compile: true})
|
||||
parse_compiler t, %{config | verbose_compile: true}
|
||||
end
|
||||
|
||||
defp parse_compiler([h | t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option?(list, config, &parse_compiler(&1, &2))
|
||||
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
parse_compiler(t, %{config | compile: [pattern | config.compile]})
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -360,11 +333,11 @@ defmodule Kernel.CLI do
|
||||
# This clause is here so that Kernel.CLI does not
|
||||
# error out with "unknown option"
|
||||
defp parse_iex(["--dot-iex", _ | t], config) do
|
||||
parse_iex(t, config)
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp parse_iex([opt, _ | t], config) when opt in ["--remsh"] do
|
||||
parse_iex(t, config)
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp parse_iex(["-S", h | t], config) do
|
||||
@@ -373,8 +346,10 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp parse_iex([h | t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ -> shared_option?(list, config, &parse_iex(&1, &2))
|
||||
_ -> {%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &parse_iex(&1, &2)
|
||||
_ ->
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -385,23 +360,22 @@ defmodule Kernel.CLI do
|
||||
# Process commands
|
||||
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive?() do
|
||||
wrapper(fn -> Node.set_cookie(String.to_atom(h)) end)
|
||||
if Node.alive? do
|
||||
wrapper fn -> Node.set_cookie(String.to_atom(h)) end
|
||||
else
|
||||
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_binary(expr) do
|
||||
wrapper(fn -> Code.eval_string(expr, []) end)
|
||||
wrapper fn -> Code.eval_string(expr, []) end
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.ensure_all_started(String.to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
msg = "--app : Could not start application #{app}: " <> Application.format_error(reason)
|
||||
{:error, msg}
|
||||
|
||||
{:error, "--app : Could not start application #{app}: " <>
|
||||
Application.format_error(reason)}
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
end
|
||||
@@ -409,7 +383,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
wrapper(fn -> Code.require_file(exec) end)
|
||||
wrapper fn -> Code.require_file(exec) end
|
||||
else
|
||||
{:error, "-S : Could not find executable #{file}"}
|
||||
end
|
||||
@@ -417,7 +391,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if File.regular?(file) do
|
||||
wrapper(fn -> Code.require_file(file) end)
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{:error, "No file named #{file}"}
|
||||
end
|
||||
@@ -427,7 +401,7 @@ defmodule Kernel.CLI do
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper(fn -> Enum.map(files, &Code.require_file(&1)) end)
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
else
|
||||
{:error, "-r : No files matched pattern #{pattern}"}
|
||||
end
|
||||
@@ -437,12 +411,7 @@ defmodule Kernel.CLI do
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper(fn ->
|
||||
case Kernel.ParallelCompiler.require(files) do
|
||||
{:ok, _, _} -> :ok
|
||||
{:error, _, _} -> exit({:shutdown, 1})
|
||||
end
|
||||
end)
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
else
|
||||
{:error, "-pr : No files matched pattern #{pattern}"}
|
||||
end
|
||||
@@ -455,24 +424,17 @@ defmodule Kernel.CLI do
|
||||
case filter_multiple_patterns(patterns) do
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
|
||||
{:ok, files} ->
|
||||
wrapper(fn ->
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
|
||||
opts =
|
||||
if config.verbose_compile do
|
||||
[each_long_compilation: &IO.puts("Compiling #{&1} (it's taking more than 15s)")]
|
||||
[each_long_compilation: &IO.puts("Compiling #{&1} (it's taking more than 5s)")]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
case Kernel.ParallelCompiler.compile_to_path(files, config.output, opts) do
|
||||
{:ok, _, _} -> :ok
|
||||
{:error, _, _} -> exit({:shutdown, 1})
|
||||
end
|
||||
end)
|
||||
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output, opts)
|
||||
end
|
||||
{:missing, missing} ->
|
||||
{:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
|
||||
end
|
||||
@@ -480,23 +442,23 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp filter_patterns(pattern) do
|
||||
pattern
|
||||
|> Path.wildcard()
|
||||
|> :lists.usort()
|
||||
|> Path.wildcard
|
||||
|> :lists.usort
|
||||
|> Enum.filter(&File.regular?/1)
|
||||
end
|
||||
|
||||
defp filter_multiple_patterns(patterns) do
|
||||
{files, missing} =
|
||||
Enum.reduce(patterns, {[], []}, fn pattern, {files, missing} ->
|
||||
Enum.reduce patterns, {[], []}, fn pattern, {files, missing} ->
|
||||
case filter_patterns(pattern) do
|
||||
[] -> {files, [pattern | missing]}
|
||||
match -> {match ++ files, missing}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
case missing do
|
||||
[] -> {:ok, :lists.usort(files)}
|
||||
_ -> {:missing, :lists.usort(missing)}
|
||||
_ -> {:missing, :lists.usort(missing)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,7 +476,6 @@ defmodule Kernel.CLI do
|
||||
{:win32, _} ->
|
||||
base = Path.rootname(exec)
|
||||
if File.regular?(base), do: base, else: exec
|
||||
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
|
||||
@@ -31,12 +31,11 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
def ensure_compiled(module, kind) do
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref()
|
||||
send(parent, {:waiting, kind, self(), ref, module, :elixir_module.compiler_modules()})
|
||||
ref = :erlang.make_ref
|
||||
send parent, {:waiting, kind, self(), ref, module, :elixir_module.compiler_modules()}
|
||||
:erlang.garbage_collect(self())
|
||||
|
||||
receive do
|
||||
{^ref, :found} -> true
|
||||
{^ref, :found} -> true
|
||||
{^ref, :not_found} -> false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@moduledoc false
|
||||
@timeout 30000
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc """
|
||||
@@ -31,8 +31,7 @@ defmodule Kernel.LexicalTracker do
|
||||
:gen_server.call(to_pid(arg), :dest, @timeout)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
|
||||
@@ -72,11 +71,6 @@ defmodule Kernel.LexicalTracker do
|
||||
:gen_server.cast(pid, {:remote_dispatch, module, fa, line, mode})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def remote_struct(pid, module, line) when is_atom(module) do
|
||||
:gen_server.cast(pid, {:remote_struct, module, line})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def import_dispatch(pid, module, fa, line, mode) when is_atom(module) do
|
||||
:gen_server.cast(pid, {:import_dispatch, module, fa, line, mode})
|
||||
@@ -87,16 +81,6 @@ defmodule Kernel.LexicalTracker do
|
||||
:gen_server.cast(pid, {:alias_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def set_file(pid, file) do
|
||||
:gen_server.cast(pid, {:set_file, file})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reset_file(pid) do
|
||||
:gen_server.cast(pid, :reset_file)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def write_cache(pid, value) do
|
||||
key = :erlang.unique_integer()
|
||||
@@ -126,33 +110,22 @@ defmodule Kernel.LexicalTracker do
|
||||
# Callbacks
|
||||
|
||||
def init(dest) do
|
||||
state = %{
|
||||
directives: %{},
|
||||
references: %{},
|
||||
compile: %{},
|
||||
runtime: %{},
|
||||
structs: %{},
|
||||
dest: dest,
|
||||
cache: %{},
|
||||
file: nil
|
||||
}
|
||||
|
||||
{:ok, state}
|
||||
{:ok, %{directives: %{}, references: %{}, compile: %{},
|
||||
runtime: %{}, dest: dest, cache: %{}}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call({:unused, tag}, _from, state) do
|
||||
directives =
|
||||
for {{^tag, module_or_mfa}, marker} <- state.directives, is_integer(marker) do
|
||||
{module_or_mfa, marker}
|
||||
end
|
||||
for {{^tag, module_or_mfa}, marker} <- state.directives,
|
||||
is_integer(marker),
|
||||
do: {module_or_mfa, marker}
|
||||
|
||||
{:reply, Enum.sort(directives), state}
|
||||
end
|
||||
|
||||
def handle_call(:remote_references, _from, state) do
|
||||
{compile, runtime} = partition(:maps.to_list(state.references), [], [])
|
||||
{:reply, {compile, :maps.keys(state.structs), runtime}, state}
|
||||
{:reply, partition(Enum.to_list(state.references), [], []), state}
|
||||
end
|
||||
|
||||
def handle_call(:remote_dispatches, _from, state) do
|
||||
@@ -164,23 +137,17 @@ defmodule Kernel.LexicalTracker do
|
||||
end
|
||||
|
||||
def handle_call({:read_cache, key}, _from, %{cache: cache} = state) do
|
||||
{:reply, :maps.get(key, cache), state}
|
||||
{:reply, Map.fetch!(cache, key), state}
|
||||
end
|
||||
|
||||
def handle_cast({:write_cache, key, value}, %{cache: cache} = state) do
|
||||
{:noreply, %{state | cache: :maps.put(key, value, cache)}}
|
||||
{:noreply, Map.put(state, :cache, Map.put(cache, key, value))}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_reference, module, mode}, state) do
|
||||
{:noreply, %{state | references: add_reference(state.references, module, mode)}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_struct, module, line}, state) do
|
||||
state = add_remote_dispatch(state, module, {:__struct__, 1}, line, :compile)
|
||||
structs = :maps.put(module, true, state.structs)
|
||||
{:noreply, %{state | structs: structs}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module, fa, line, mode}, state) do
|
||||
references = add_reference(state.references, module, mode)
|
||||
state = add_remote_dispatch(state, module, fa, line, mode)
|
||||
@@ -200,19 +167,11 @@ defmodule Kernel.LexicalTracker do
|
||||
{:noreply, %{state | directives: add_dispatch(state.directives, module, :alias)}}
|
||||
end
|
||||
|
||||
def handle_cast({:set_file, file}, state) do
|
||||
{:noreply, %{state | file: file}}
|
||||
end
|
||||
|
||||
def handle_cast(:reset_file, state) do
|
||||
{:noreply, %{state | file: nil}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, module, fas, line, warn}, state) do
|
||||
directives =
|
||||
state.directives
|
||||
|> Enum.reject(&match?({{:import, {^module, _, _}}, _}, &1))
|
||||
|> :maps.from_list()
|
||||
|> :maps.from_list
|
||||
|> add_directive(module, line, warn, :import)
|
||||
|
||||
directives =
|
||||
@@ -248,42 +207,30 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
defp partition([{remote, :compile} | t], compile, runtime),
|
||||
do: partition(t, [remote | compile], runtime)
|
||||
|
||||
defp partition([{remote, :runtime} | t], compile, runtime),
|
||||
do: partition(t, compile, [remote | runtime])
|
||||
|
||||
defp partition([], compile, runtime), do: {compile, runtime}
|
||||
defp partition([], compile, runtime),
|
||||
do: {compile, runtime}
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
defp add_reference(references, module, :runtime) when is_atom(module),
|
||||
do: map_put_new(module, :runtime, references)
|
||||
defp add_reference(references, module, :compile) when is_atom(module),
|
||||
do: :maps.put(module, :compile, references)
|
||||
|
||||
defp add_reference(references, module, :runtime) when is_atom(module) do
|
||||
case :maps.find(module, references) do
|
||||
{:ok, _} -> references
|
||||
:error -> :maps.put(module, :runtime, references)
|
||||
defp add_remote_dispatch(state, module, fa, line, mode) when is_atom(module) do
|
||||
map_update mode, %{module => %{fa => [line]}}, state, fn mode_dispatches ->
|
||||
map_update module, %{fa => [line]}, mode_dispatches, fn module_dispatches ->
|
||||
map_update fa, [line], module_dispatches, &[line | List.delete(&1, line)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp add_remote_dispatch(state, module, fa, line, mode) when is_atom(module) do
|
||||
location = location(state.file, line)
|
||||
|
||||
map_update(mode, %{module => %{fa => [location]}}, state, fn mode_dispatches ->
|
||||
map_update(module, %{fa => [location]}, mode_dispatches, fn module_dispatches ->
|
||||
map_update(fa, [location], module_dispatches, &[location | List.delete(&1, location)])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp location(nil, line), do: line
|
||||
defp location(file, line), do: {file, line}
|
||||
|
||||
defp add_import_dispatch(state, module, function, arity) do
|
||||
directives =
|
||||
add_dispatch(state.directives, module, :import)
|
||||
|> add_dispatch({module, function, arity}, :import)
|
||||
|
||||
# Always compile time because we depend
|
||||
# on the module at compile time
|
||||
references = add_reference(state.references, module, :compile)
|
||||
@@ -309,4 +256,11 @@ defmodule Kernel.LexicalTracker do
|
||||
:error -> :maps.put(key, initial, map)
|
||||
end
|
||||
end
|
||||
|
||||
defp map_put_new(key, value, map) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, _} -> map
|
||||
:error -> :maps.put(key, value, map)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,37 +1,8 @@
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
@moduledoc """
|
||||
A module responsible for compiling and requiring files in parallel.
|
||||
A module responsible for compiling files in parallel.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Starts a task for parallel compilation.
|
||||
|
||||
If you have a file that needs to compile other modules in parallel,
|
||||
the spawned processes need to be aware of the compiler environment.
|
||||
This function allows a developer to create a task that is aware of
|
||||
those environments.
|
||||
|
||||
See `Task.async/1` for more information. The task spawned must be
|
||||
always awaited on by calling `Task.await/1`
|
||||
"""
|
||||
def async(fun) when is_function(fun) do
|
||||
if parent = :erlang.get(:elixir_compiler_pid) do
|
||||
file = :erlang.get(:elixir_compiler_file)
|
||||
{:error_handler, error_handler} = :erlang.process_info(self(), :error_handler)
|
||||
|
||||
Task.async(fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
:erlang.process_flag(:error_handler, error_handler)
|
||||
fun.()
|
||||
end)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"cannot spawn parallel compiler task because " <>
|
||||
"the current file is not being compiled/required"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files.
|
||||
|
||||
@@ -40,12 +11,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`.
|
||||
If there is an error during compilation or if `warnings_as_errors`
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
Both errors and warnings are a list of three element tuples containing
|
||||
the file, line and the formatted error/warning.
|
||||
|
||||
## Options
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
@@ -54,189 +24,130 @@ defmodule Kernel.ParallelCompiler do
|
||||
timeout (see the `:long_compilation_threshold` option) to compile, invoke
|
||||
this callback passing the file as its argument
|
||||
|
||||
* `:long_compilation_threshold` - the timeout (in seconds) after the
|
||||
`:each_long_compilation` callback is invoked; defaults to `10`
|
||||
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
* `:each_cycle` - after the given files are compiled, invokes this function
|
||||
that return a list with potentially more files to compile
|
||||
|
||||
* `:long_compilation_threshold` - the timeout (in seconds) after the
|
||||
`:each_long_compilation` callback is invoked; defaults to `15`
|
||||
* `:each_warning` - for each warning, invokes the callback passing
|
||||
the file, line number, and warning message
|
||||
|
||||
* `:dest` - the destination directory for the BEAM files. When using `files/2`,
|
||||
this information is only used to properly annotate the BEAM files before
|
||||
they are loaded into memory. If you want a file to actually be written to
|
||||
`dest`, use `compile_to_path/3` instead.
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def compile(files, options \\ []) when is_list(options) do
|
||||
spawn_workers(files, :compile, options)
|
||||
end
|
||||
def files(files, options \\ [])
|
||||
|
||||
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
|
||||
spawn_workers(files, {:compile, path}, options)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given files in parallel.
|
||||
|
||||
Opposite to compile, dependencies are not attempted to be
|
||||
automatically solved between files.
|
||||
|
||||
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`.
|
||||
|
||||
Both errors and warnings are a list of three element tuples containing
|
||||
the file, line and the formatted error/warning.
|
||||
|
||||
## Options
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def require(files, options \\ []) when is_list(options) do
|
||||
spawn_workers(files, :require, options)
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
# TODO: Deprecate on Elixir v1.8
|
||||
@doc false
|
||||
def files(files, options \\ []) when is_list(options) do
|
||||
case spawn_workers(files, :compile, options) do
|
||||
{:ok, modules, _} -> modules
|
||||
{:error, _, _} -> exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Deprecate on Elixir v1.8
|
||||
@doc false
|
||||
def files_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
|
||||
case spawn_workers(files, {:compile, path}, options) do
|
||||
{:ok, modules, _} -> modules
|
||||
{:error, _, _} -> exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
defp spawn_workers(files, output, options) do
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result =
|
||||
spawn_workers(files, [], [], [], [], %{
|
||||
dest: Keyword.get(options, :dest),
|
||||
each_cycle: Keyword.get(options, :each_cycle, fn -> [] end),
|
||||
each_file: Keyword.get(options, :each_file, fn _file -> :ok end),
|
||||
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
|
||||
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
|
||||
output: output,
|
||||
long_compilation_threshold: Keyword.get(options, :long_compilation_threshold, 15),
|
||||
schedulers: schedulers
|
||||
})
|
||||
result = spawn_compilers(%{
|
||||
entries: files,
|
||||
original: files,
|
||||
output: path,
|
||||
options: options,
|
||||
waiting: [],
|
||||
queued: [],
|
||||
schedulers: schedulers,
|
||||
result: [],
|
||||
})
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error.
|
||||
compilation_status = :elixir_code_server.call({:compilation_status, compiler_pid})
|
||||
|
||||
case {result, compilation_status} do
|
||||
{{:ok, _, warnings}, :error} ->
|
||||
message = "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
IO.puts(:stderr, message)
|
||||
{:error, warnings, []}
|
||||
|
||||
{{:error, errors, warnings}, :error} ->
|
||||
{:error, errors ++ warnings, []}
|
||||
|
||||
_ ->
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok ->
|
||||
result
|
||||
:error ->
|
||||
IO.puts :stderr, "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
# We already have n=schedulers currently running, don't spawn new ones
|
||||
defp spawn_workers(files, waiting, queued, result, warnings, %{schedulers: schedulers} = state)
|
||||
when length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, waiting, queued, result, warnings, state)
|
||||
defp spawn_compilers(%{queued: queued, waiting: waiting, schedulers: schedulers} = state)
|
||||
when length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(state)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_workers([{ref, found} | t], waiting, queued, result, warnings, state) do
|
||||
defp spawn_compilers(%{entries: [{ref, found} | t], waiting: waiting} = state) do
|
||||
waiting =
|
||||
case List.keytake(waiting, ref, 2) do
|
||||
{{_kind, pid, ^ref, _on, _defining}, waiting} ->
|
||||
send(pid, {ref, found})
|
||||
send pid, {ref, found}
|
||||
waiting
|
||||
|
||||
nil ->
|
||||
waiting
|
||||
end
|
||||
|
||||
spawn_workers(t, waiting, queued, result, warnings, state)
|
||||
spawn_compilers(%{state | entries: t, waiting: waiting})
|
||||
end
|
||||
|
||||
defp spawn_workers([file | files], waiting, queued, result, warnings, state) do
|
||||
%{output: output, long_compilation_threshold: threshold, dest: dest} = state
|
||||
defp spawn_compilers(%{entries: [file | files], queued: queued, output: output, options: options} = state) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
:erlang.spawn_monitor(fn ->
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
result =
|
||||
try do
|
||||
_ =
|
||||
case output do
|
||||
{:compile, path} ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:elixir_compiler.file_to_path(file, path)
|
||||
|
||||
:compile ->
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
:elixir_compiler.file(file, dest)
|
||||
|
||||
:require ->
|
||||
Code.require_file(file)
|
||||
end
|
||||
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(file, output)
|
||||
else
|
||||
:elixir_compiler.file(file, Keyword.get(options, :dest))
|
||||
end
|
||||
:ok
|
||||
catch
|
||||
kind, reason ->
|
||||
{kind, reason, System.stacktrace()}
|
||||
{kind, reason, System.stacktrace}
|
||||
end
|
||||
|
||||
send(parent, {:file_done, self(), file, result})
|
||||
send(parent, {:file_compiled, self(), file, result})
|
||||
exit(:shutdown)
|
||||
end)
|
||||
end
|
||||
|
||||
timer_ref = Process.send_after(self(), {:timed_out, pid}, threshold * 1000)
|
||||
queued = [{pid, ref, file, timer_ref} | queued]
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
timeout = Keyword.get(options, :long_compilation_threshold, 10) * 1_000
|
||||
timer_ref = Process.send_after(self(), {:timed_out, pid}, timeout)
|
||||
|
||||
new_queued = [{pid, ref, file, timer_ref} | queued]
|
||||
spawn_compilers(%{state | entries: files, queued: new_queued})
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, this cycle is done
|
||||
defp spawn_workers([], [], [], result, warnings, state) do
|
||||
case state.each_cycle.() do
|
||||
[] ->
|
||||
modules = for {:module, mod} <- result, do: mod
|
||||
warnings = Enum.reverse(warnings)
|
||||
{:ok, modules, warnings}
|
||||
|
||||
more ->
|
||||
spawn_workers(more, [], [], result, warnings, state)
|
||||
end
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers(%{entries: [], waiting: [], queued: [], result: result}) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_workers([], waiting, queued, result, warnings, state)
|
||||
when length(waiting) == length(queued) do
|
||||
pending =
|
||||
for {pid, _, _, _} <- queued,
|
||||
entry = waiting_on_without_definition(waiting, pid),
|
||||
{_, _, ref, on, _} = entry,
|
||||
do: {on, {ref, :not_found}}
|
||||
defp spawn_compilers(%{entries: [], waiting: waiting, queued: queued} = state) when length(waiting) == length(queued) do
|
||||
entries = for {pid, _, _, _} <- queued,
|
||||
entry = waiting_on_without_definition(waiting, pid),
|
||||
{_, _, ref, on, _} = entry,
|
||||
do: {on, {ref, :not_found}}
|
||||
|
||||
# Instead of releasing all files at once, we release them in groups
|
||||
# based on the module they are waiting on. We pick the module being
|
||||
@@ -247,27 +158,22 @@ defmodule Kernel.ParallelCompiler do
|
||||
# are waiting on the same module required by the faulty code. However,
|
||||
# since we need to pick something to be first, the one with fewer edges
|
||||
# sounds like a sane choice.
|
||||
pending
|
||||
entries
|
||||
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|
||||
|> Enum.sort_by(&length(elem(&1, 1)))
|
||||
|> case do
|
||||
[{_on, refs} | _] ->
|
||||
spawn_workers(refs, waiting, queued, result, warnings, state)
|
||||
|
||||
[] ->
|
||||
errors = handle_deadlock(waiting, queued)
|
||||
{:error, errors, warnings}
|
||||
[{_on, refs} | _] -> spawn_compilers(%{state | entries: refs})
|
||||
[] -> handle_deadlock(waiting, queued)
|
||||
end
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_workers([], waiting, queued, result, warnings, state) do
|
||||
wait_for_messages([], waiting, queued, result, warnings, state)
|
||||
defp spawn_compilers(%{entries: []} = state) do
|
||||
wait_for_messages(state)
|
||||
end
|
||||
|
||||
defp waiting_on_without_definition(waiting, pid) do
|
||||
{_, ^pid, _, on, _} = entry = List.keyfind(waiting, pid, 1)
|
||||
|
||||
if Enum.any?(waiting, fn {_, _, _, _, defining} -> on in defining end) do
|
||||
nil
|
||||
else
|
||||
@@ -276,39 +182,32 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, waiting, queued, result, warnings, state) do
|
||||
%{output: output} = state
|
||||
defp wait_for_messages(state) do
|
||||
%{entries: entries, options: options, waiting: waiting, queued: queued, result: result} = state
|
||||
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available =
|
||||
for {:struct, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
do: {ref, :found}
|
||||
available = for {:struct, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
do: {ref, :found}
|
||||
|
||||
result = [{:struct, module} | result]
|
||||
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
|
||||
spawn_compilers(%{state | entries: available ++ entries, result: [{:struct, module} | result]})
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
state.each_module.(file, module, binary)
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
send(child, {ref, :ack})
|
||||
send child, {ref, :ack}
|
||||
|
||||
available =
|
||||
for {:module, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
do: {ref, :found}
|
||||
available = for {:module, _, ref, waiting_module, _defining} <- waiting,
|
||||
module == waiting_module,
|
||||
do: {ref, :found}
|
||||
|
||||
cancel_waiting_timer(queued, child)
|
||||
|
||||
result = [{:module, module} | result]
|
||||
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
|
||||
|
||||
# If we are simply requiring files, we do not add to waiting.
|
||||
{:waiting, _kind, child, ref, _on, _defining} when output == :require ->
|
||||
send(child, {ref, :not_found})
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_compilers(%{state | entries: available ++ entries, result: [{:module, module} | result]})
|
||||
|
||||
{:waiting, kind, child, ref, on, defining} ->
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
@@ -316,54 +215,54 @@ defmodule Kernel.ParallelCompiler do
|
||||
# send :found so that we can crash with a better error.
|
||||
waiting =
|
||||
if :lists.any(&match?({^kind, ^on}, &1), result) or on in defining do
|
||||
send(child, {ref, :found})
|
||||
send child, {ref, :found}
|
||||
waiting
|
||||
else
|
||||
[{kind, child, ref, on, defining} | waiting]
|
||||
end
|
||||
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_compilers(%{state | waiting: waiting})
|
||||
|
||||
{:timed_out, child} ->
|
||||
callback = Keyword.get(options, :each_long_compilation)
|
||||
case List.keyfind(queued, child, 0) do
|
||||
{^child, _, file, _} ->
|
||||
state.each_long_compilation.(file)
|
||||
|
||||
{^child, _, file, _} when not is_nil(callback) ->
|
||||
callback.(file)
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
spawn_workers(files, waiting, queued, result, warnings, state)
|
||||
spawn_compilers(state)
|
||||
|
||||
{:warning, file, line, message} ->
|
||||
file = file && Path.absname(file)
|
||||
message = :unicode.characters_to_binary(message)
|
||||
warning = {file, line, message}
|
||||
wait_for_messages(files, waiting, queued, result, [warning | warnings], state)
|
||||
if callback = Keyword.get(options, :each_warning) do
|
||||
callback.(file, line, message)
|
||||
end
|
||||
wait_for_messages(state)
|
||||
|
||||
{:file_done, child_pid, file, :ok} ->
|
||||
{:file_compiled, child_pid, file, :ok} ->
|
||||
discard_down(child_pid)
|
||||
state.each_file.(file)
|
||||
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
cancel_waiting_timer(queued, child_pid)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_files = List.delete(files, child_pid)
|
||||
new_queued = List.keydelete(queued, child_pid, 0)
|
||||
new_entries = List.delete(entries, child_pid)
|
||||
new_queued = List.keydelete(queued, child_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, child_pid, 1)
|
||||
spawn_workers(new_files, new_waiting, new_queued, result, warnings, state)
|
||||
spawn_compilers(%{state | entries: new_entries, waiting: new_waiting, queued: new_queued})
|
||||
|
||||
{:file_done, child_pid, file, {kind, reason, stack}} ->
|
||||
{:file_compiled, child_pid, file, {kind, reason, stack}} ->
|
||||
discard_down(child_pid)
|
||||
print_error(file, kind, reason, stack)
|
||||
terminate(queued)
|
||||
{:error, [to_error(file, kind, reason, stack)], warnings}
|
||||
|
||||
{:DOWN, ref, :process, _pid, reason} ->
|
||||
case handle_down(queued, ref, reason) do
|
||||
:ok -> wait_for_messages(files, waiting, queued, result, warnings, state)
|
||||
{:error, errors} -> {:error, errors, warnings}
|
||||
end
|
||||
handle_down(queued, ref, reason)
|
||||
wait_for_messages(state)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -376,14 +275,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp handle_down(_queued, _ref, :normal) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_down(queued, ref, reason) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{_child, ^ref, file, _timer_ref} ->
|
||||
print_error(file, :exit, reason, [])
|
||||
terminate(queued)
|
||||
{:error, [to_error(file, :exit, reason, [])]}
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
@@ -396,44 +292,42 @@ defmodule Kernel.ParallelCompiler do
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
{_kind, ^pid, _, on, _} = List.keyfind(waiting, pid, 1)
|
||||
description = "deadlocked waiting on module #{inspect(on)}"
|
||||
error = CompileError.exception(description: description, file: nil, line: nil)
|
||||
error = CompileError.exception(description: "deadlocked waiting on module #{inspect on}",
|
||||
file: nil, line: nil)
|
||||
print_error(file, :error, error, stacktrace)
|
||||
|
||||
{file, on, description}
|
||||
{file, on}
|
||||
end
|
||||
|
||||
IO.puts("""
|
||||
IO.puts """
|
||||
|
||||
Compilation failed because of a deadlock between files.
|
||||
The following files depended on the following modules:
|
||||
""")
|
||||
"""
|
||||
|
||||
max =
|
||||
deadlock
|
||||
|> Enum.map(&(&1 |> elem(0) |> String.length()))
|
||||
|> Enum.max()
|
||||
|> Enum.map(& &1 |> elem(0) |> String.length)
|
||||
|> Enum.max
|
||||
|
||||
for {file, mod, _} <- deadlock do
|
||||
IO.puts([" ", String.pad_leading(file, max), " => " | inspect(mod)])
|
||||
for {file, mod} <- deadlock do
|
||||
IO.puts [" ", String.pad_leading(file, max), " => " | inspect(mod)]
|
||||
end
|
||||
|
||||
IO.puts("")
|
||||
|
||||
for {file, _, description} <- deadlock, do: {Path.absname(file), nil, description}
|
||||
IO.puts ""
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
|
||||
defp terminate(queued) do
|
||||
for {pid, _, _, _} <- queued do
|
||||
Process.exit(pid, :kill)
|
||||
end
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
|
||||
defp print_error(file, kind, reason, stack) do
|
||||
IO.write([
|
||||
"\n== Compilation error in file #{Path.relative_to_cwd(file)} ==\n",
|
||||
Kernel.CLI.format_error(kind, reason, stack)
|
||||
])
|
||||
IO.write ["\n== Compilation error in file #{Path.relative_to_cwd(file)} ==\n",
|
||||
Kernel.CLI.format_error(kind, reason, stack)]
|
||||
end
|
||||
|
||||
defp cancel_waiting_timer(queued, child_pid) do
|
||||
@@ -447,36 +341,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
|
||||
nil ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp to_error(file, kind, reason, stack) do
|
||||
line = get_line(file, reason, stack)
|
||||
file = Path.absname(file)
|
||||
message = :unicode.characters_to_binary(Kernel.CLI.format_error(kind, reason, stack))
|
||||
{file, line, message}
|
||||
end
|
||||
|
||||
defp get_line(_file, %{line: line}, _stack) when is_integer(line) and line > 0 do
|
||||
line
|
||||
end
|
||||
|
||||
defp get_line(file, :undef, [{_, _, _, []}, {_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(file, _reason, [{_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(_, _, _) do
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +1,120 @@
|
||||
defmodule Kernel.ParallelRequire do
|
||||
# TODO: Deprecate on Elixir v1.8
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
A module responsible for requiring files in parallel.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Requires the given files.
|
||||
|
||||
A callback that will be invoked with each file, or a keyword list of `callbacks` can be provided:
|
||||
|
||||
* `:each_file` - invoked with each file
|
||||
|
||||
* `:each_module` - invoked with file, module name, and binary code
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
|
||||
def files(files, callback) when is_function(callback, 1) do
|
||||
files(files, each_file: callback)
|
||||
files(files, [each_file: callback])
|
||||
end
|
||||
|
||||
def files(files, options) when is_list(options) do
|
||||
case Kernel.ParallelCompiler.require(files, options) do
|
||||
{:ok, modules, _} -> modules
|
||||
{:error, _, _} -> exit({:shutdown, 1})
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
result = spawn_requires(files, [], callbacks, schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error.
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok ->
|
||||
result
|
||||
:error ->
|
||||
IO.puts :stderr, "\nExecution failed due to warnings while using the --warnings-as-errors option"
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
defp spawn_requires([], [], _callbacks, _schedulers, result), do: result
|
||||
|
||||
defp spawn_requires([], waiting, callbacks, schedulers, result) do
|
||||
wait_for_messages([], waiting, callbacks, schedulers, result)
|
||||
end
|
||||
|
||||
defp spawn_requires(files, waiting, callbacks, schedulers, result) when length(waiting) >= schedulers do
|
||||
wait_for_messages(files, waiting, callbacks, schedulers, result)
|
||||
end
|
||||
|
||||
defp spawn_requires([file | files], waiting, callbacks, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} = :erlang.spawn_monitor fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_compiler_file, file)
|
||||
|
||||
result =
|
||||
try do
|
||||
new = Code.require_file(file) || []
|
||||
{:required, Enum.map(new, &elem(&1, 0))}
|
||||
catch
|
||||
kind, reason ->
|
||||
{kind, reason, System.stacktrace}
|
||||
end
|
||||
|
||||
send(parent, {:file_required, self(), file, result})
|
||||
exit(:shutdown)
|
||||
end
|
||||
|
||||
spawn_requires(files, [{pid, ref} | waiting], callbacks, schedulers, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callbacks, schedulers, result) do
|
||||
receive do
|
||||
{:file_required, pid, file, {:required, mods}} ->
|
||||
discard_down(pid)
|
||||
if each_file_callback = callbacks[:each_file] do
|
||||
each_file_callback.(file)
|
||||
end
|
||||
waiting = List.keydelete(waiting, pid, 0)
|
||||
spawn_requires(files, waiting, callbacks, schedulers, mods ++ result)
|
||||
|
||||
{:file_required, pid, _file, {kind, reason, stacktrace}} ->
|
||||
discard_down(pid)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
|
||||
{:DOWN, ref, :process, pid, reason} ->
|
||||
handle_down(waiting, pid, ref, reason)
|
||||
spawn_requires(files, waiting, callbacks, schedulers, result)
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if each_module_callback = callbacks[:each_module] do
|
||||
each_module_callback.(file, module, binary)
|
||||
end
|
||||
|
||||
send(child, {ref, :ack})
|
||||
spawn_requires(files, waiting, callbacks, schedulers, result)
|
||||
|
||||
{:struct_available, _} ->
|
||||
spawn_requires(files, waiting, callbacks, schedulers, result)
|
||||
|
||||
{:waiting, _, child, ref, _, _} ->
|
||||
send(child, {ref, :not_found})
|
||||
spawn_requires(files, waiting, callbacks, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp discard_down(pid) do
|
||||
receive do
|
||||
{:DOWN, _, :process, ^pid, _} -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_down(waiting, pid, ref, reason) do
|
||||
if reason != :normal and {pid, ref} in waiting do
|
||||
:erlang.raise(:exit, reason, [])
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Kernel.SpecialForms do
|
||||
cannot be overridden by the developer.
|
||||
|
||||
We define them in this module. Some of these forms are lexical (like
|
||||
`alias/2`, `case/2`, etc). The macros `{}/1` and `<<>>/1` are also special
|
||||
`alias/2`, `case/2`, etc). The macros `{}` and `<<>>` are also special
|
||||
forms used to define tuple and binary data structures respectively.
|
||||
|
||||
This module also documents macros that return information about Elixir's
|
||||
@@ -19,10 +19,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacrop error!(args) do
|
||||
quote do
|
||||
_ = unquote(args)
|
||||
|
||||
message =
|
||||
"Elixir's special forms are expanded by the compiler and must not be invoked directly"
|
||||
|
||||
message = "Elixir's special forms are expanded by the compiler and must not be invoked directly"
|
||||
:erlang.error(RuntimeError.exception(message))
|
||||
end
|
||||
end
|
||||
@@ -575,9 +572,8 @@ defmodule Kernel.SpecialForms do
|
||||
import List, only: [flatten: 1]
|
||||
import String, except: [split: 2]
|
||||
|
||||
Notice that calling `except` is always exclusive on a previously
|
||||
declared `import/2`. If there is no previous import, then it applies
|
||||
to all functions and macros in the module. For example:
|
||||
Notice that calling `except` for a previously declared `import/2`
|
||||
simply filters the previously imported elements. For example:
|
||||
|
||||
import List, only: [flatten: 1, keyfind: 4]
|
||||
import List, except: [flatten: 1]
|
||||
@@ -700,7 +696,7 @@ defmodule Kernel.SpecialForms do
|
||||
1
|
||||
|
||||
"""
|
||||
defmacro ^var, do: error!([var])
|
||||
defmacro ^(var), do: error!([var])
|
||||
|
||||
@doc """
|
||||
Matches the value on the right against the pattern on the left.
|
||||
@@ -767,8 +763,6 @@ defmodule Kernel.SpecialForms do
|
||||
quote. Read the Stacktrace information section below for more
|
||||
information.
|
||||
|
||||
* `:line` - sets the quoted expressions to have the given line.
|
||||
|
||||
* `:generated` - marks the given chunk as generated so it does not emit warnings.
|
||||
Currently it only works on special forms (for example, you can annotate a `case`
|
||||
but not an `if`).
|
||||
@@ -872,7 +866,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
`:bind_quoted` will translate to the same code as the example above.
|
||||
`:bind_quoted` can be used in many cases and is seen as good practice,
|
||||
not only because it helps prevent us from running into common mistakes, but also
|
||||
not only because it helps us from running into common mistakes but also
|
||||
because it allows us to leverage other tools exposed by macros, such as
|
||||
unquote fragments discussed in some sections below.
|
||||
|
||||
@@ -1319,18 +1313,6 @@ defmodule Kernel.SpecialForms do
|
||||
String.upcase(line)
|
||||
end
|
||||
|
||||
## Uniq
|
||||
|
||||
`uniq: true` can also be given to comprehensions to guarantee that
|
||||
that results are only added to the collection if they were not returned
|
||||
before. For example:
|
||||
|
||||
iex> for(x <- [1, 1, 2, 3], uniq: true, do: x * 2)
|
||||
[2, 4, 6]
|
||||
|
||||
iex> for(<<x <- "abcabc">>, uniq: true, into: "", do: <<x-32>>)
|
||||
"ABC"
|
||||
|
||||
"""
|
||||
defmacro for(args), do: error!([args])
|
||||
|
||||
@@ -1374,12 +1356,6 @@ defmodule Kernel.SpecialForms do
|
||||
iex> width
|
||||
nil
|
||||
|
||||
Note that if a "bare expression" fails to match, it will raise a `MatchError`
|
||||
instead of returning the non-matched value:
|
||||
|
||||
with :foo = :bar, do: :ok
|
||||
#=> ** (MatchError) no match of right hand side value: :bar
|
||||
|
||||
An `else` option can be given to modify what is being returned from
|
||||
`with` in the case of a failed match:
|
||||
|
||||
@@ -1394,6 +1370,7 @@ defmodule Kernel.SpecialForms do
|
||||
{:error, :wrong_data}
|
||||
|
||||
If there is no matching `else` condition, then a `WithClauseError` exception is raised.
|
||||
|
||||
"""
|
||||
defmacro with(args), do: error!([args])
|
||||
|
||||
@@ -1406,16 +1383,6 @@ defmodule Kernel.SpecialForms do
|
||||
iex> add.(1, 2)
|
||||
3
|
||||
|
||||
Anonymous functions can also have multiple clauses. All clauses
|
||||
should expect the same number of arguments:
|
||||
|
||||
iex> negate = fn
|
||||
...> true -> false
|
||||
...> false -> true
|
||||
...> end
|
||||
iex> negate.(false)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro unquote(:fn)(clauses), do: error!([clauses])
|
||||
|
||||
@@ -1434,7 +1401,7 @@ defmodule Kernel.SpecialForms do
|
||||
{:__block__, [], [1, 2, 3]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:__block__)(args), do: error!([args])
|
||||
defmacro __block__(args), do: error!([args])
|
||||
|
||||
@doc """
|
||||
Captures or creates an anonymous function.
|
||||
@@ -1470,18 +1437,11 @@ defmodule Kernel.SpecialForms do
|
||||
4
|
||||
|
||||
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
|
||||
Another example using a local function:
|
||||
|
||||
We can partially apply a remote function with placeholder:
|
||||
|
||||
iex> take_five = &Enum.take(&1, 5)
|
||||
iex> take_five.(1..10)
|
||||
[1, 2, 3, 4, 5]
|
||||
|
||||
Another example while using an imported or local function:
|
||||
|
||||
iex> first_elem = &elem(&1, 0)
|
||||
iex> first_elem.({0, 1})
|
||||
0
|
||||
iex> fun = &is_atom(&1)
|
||||
iex> fun.(:atom)
|
||||
true
|
||||
|
||||
The `&` operator can be used with more complex expressions:
|
||||
|
||||
@@ -1496,8 +1456,8 @@ defmodule Kernel.SpecialForms do
|
||||
{1, 2}
|
||||
|
||||
iex> fun = &[&1 | &2]
|
||||
iex> fun.(1, [2, 3])
|
||||
[1, 2, 3]
|
||||
iex> fun.(1, 2)
|
||||
[1 | 2]
|
||||
|
||||
The only restrictions when creating anonymous functions is that at
|
||||
least one placeholder must be present, i.e. it must contain at least
|
||||
@@ -1544,7 +1504,7 @@ defmodule Kernel.SpecialForms do
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happens.
|
||||
|
||||
"""
|
||||
defmacro unquote(:__aliases__)(args), do: error!([args])
|
||||
defmacro __aliases__(args), do: error!([args])
|
||||
|
||||
@doc """
|
||||
Calls the overridden function when overriding it with `Kernel.defoverridable/1`.
|
||||
@@ -1676,7 +1636,7 @@ defmodule Kernel.SpecialForms do
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
|
||||
The `rescue` clause is used to handle exceptions while the `catch`
|
||||
The `rescue` clause is used to handle exceptions, while the `catch`
|
||||
clause can be used to catch thrown values and exits.
|
||||
The `else` clause can be used to control flow based on the result of
|
||||
the expression. `catch`, `rescue`, and `else` clauses work based on
|
||||
@@ -1692,37 +1652,33 @@ defmodule Kernel.SpecialForms do
|
||||
exception by its name. All the following formats are valid patterns
|
||||
in `rescue` clauses:
|
||||
|
||||
# Rescue a single exception without binding the exception
|
||||
# to a variable
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
UndefinedFunctionError -> nil
|
||||
end
|
||||
|
||||
# Rescue any of the given exception without binding
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
[UndefinedFunctionError, ArgumentError] -> nil
|
||||
[UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# Rescue and bind the exception to the variable "x"
|
||||
# rescue and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x in [UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# Rescue all kinds of exceptions and bind the rescued exception
|
||||
# to the variable "x"
|
||||
# rescue all and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x -> nil
|
||||
end
|
||||
|
||||
### Erlang errors
|
||||
## Erlang errors
|
||||
|
||||
Erlang errors are transformed into Elixir ones when rescuing:
|
||||
|
||||
@@ -1731,18 +1687,16 @@ defmodule Kernel.SpecialForms do
|
||||
rescue
|
||||
ArgumentError -> :ok
|
||||
end
|
||||
#=> :ok
|
||||
|
||||
The most common Erlang errors will be transformed into their
|
||||
Elixir counterpart. Those which are not will be transformed
|
||||
into the more generic `ErlangError`:
|
||||
Elixir counter-part. Those which are not will be transformed
|
||||
into `ErlangError`:
|
||||
|
||||
try do
|
||||
:erlang.error(:unknown)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
#=> :ok
|
||||
|
||||
In fact, `ErlangError` can be used to rescue any error that is
|
||||
not a proper Elixir error. For example, it can be used to rescue
|
||||
@@ -1753,63 +1707,55 @@ defmodule Kernel.SpecialForms do
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
#=> :ok
|
||||
|
||||
## `catch` clauses
|
||||
## Catching throws and exits
|
||||
|
||||
The `catch` clause can be used to catch thrown values, exits, and errors.
|
||||
|
||||
### Catching thrown values
|
||||
|
||||
`catch` can be used to catch values thrown by `Kernel.throw/1`:
|
||||
The `catch` clause can be used to catch thrown values and exits.
|
||||
|
||||
try do
|
||||
throw(:some_value)
|
||||
exit(:shutdown)
|
||||
catch
|
||||
thrown_value ->
|
||||
IO.puts "A value was thrown: #{inspect(thrown_value)}"
|
||||
:exit, :shutdown ->
|
||||
IO.puts "Exited with shutdown reason"
|
||||
end
|
||||
|
||||
### Catching values of any kind
|
||||
try do
|
||||
throw(:sample)
|
||||
catch
|
||||
:throw, :sample ->
|
||||
IO.puts ":sample was thrown"
|
||||
end
|
||||
|
||||
The `catch` clause also supports catching exits and errors. To do that, it
|
||||
allows matching on both the *kind* of the caught value as well as the value
|
||||
itself:
|
||||
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, value
|
||||
IO.puts "Exited with value #{inspect(value)}"
|
||||
end
|
||||
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
kind, value when kind in [:exit, :throw] ->
|
||||
IO.puts "Caught exit or throw with value #{inspect(value)}"
|
||||
end
|
||||
|
||||
The `catch` clause also supports `:error` alongside `:exit` and `:throw` as
|
||||
in Erlang, although this is commonly avoided in favor of `raise`/`rescue` control
|
||||
The `catch` clause also supports `:error` alongside `:exit` and `:throw`, as
|
||||
in Erlang, although it is commonly avoided in favor of `raise`/`rescue` control
|
||||
mechanisms. One reason for this is that when catching `:error`, the error is
|
||||
not automatically transformed into an Elixir error:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
catch
|
||||
:error, :badarg -> :ok
|
||||
:error, :badarg ->
|
||||
:ok
|
||||
end
|
||||
|
||||
Note that it is possible to match both on the caught value as well as the *kind*
|
||||
of such value:
|
||||
|
||||
try do
|
||||
exit(:shutdown)
|
||||
catch
|
||||
kind, value when kind in [:exit, :throw] ->
|
||||
IO.puts "Exited with or thrown value #{inspect(value)}"
|
||||
end
|
||||
#=> :ok
|
||||
|
||||
## `after` clauses
|
||||
|
||||
An `after` clause allows you to define cleanup logic that will be invoked both
|
||||
when the block of code passed to `try/1` succeeds and also when an error is raised. Note
|
||||
that the process will exit as usual when receiving an exit signal that causes
|
||||
when the tried block of code succeeds and also when an error is raised. Note
|
||||
that the process will exit as usually when receiving an exit signal that causes
|
||||
it to exit abruptly and so the `after` clause is not guaranteed to be executed.
|
||||
Luckily, most resources in Elixir (such as open files, ETS tables, ports, sockets,
|
||||
and so on) are linked to or monitor the owning process and will automatically clean
|
||||
etc.) are linked to or monitor the owning process and will automatically clean
|
||||
themselves up if that process exits.
|
||||
|
||||
File.write!("tmp/story.txt", "Hello, World")
|
||||
@@ -1821,7 +1767,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## `else` clauses
|
||||
|
||||
`else` clauses allow the result of the body passed to `try/1` to be pattern
|
||||
`else` clauses allow the result of the tried expression to be pattern
|
||||
matched on:
|
||||
|
||||
x = 2
|
||||
@@ -1858,7 +1804,7 @@ defmodule Kernel.SpecialForms do
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
# The TryClauseError cannot be rescued here:
|
||||
# The TryClauseError can not be rescued here:
|
||||
TryClauseError ->
|
||||
:error_a
|
||||
else
|
||||
|
||||
+151
-293
@@ -13,16 +13,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro deftype(type) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.deftype(
|
||||
:type,
|
||||
unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.deftype(:type, unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,16 +31,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro defopaque(type) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.deftype(
|
||||
:opaque,
|
||||
unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,16 +49,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro deftypep(type) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.deftype(
|
||||
:typep,
|
||||
unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.deftype(:typep, unquote(Macro.escape(type, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,16 +67,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro defspec(spec) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.defspec(
|
||||
:spec,
|
||||
unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.defspec(:spec, unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -113,16 +85,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro defcallback(spec) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.defspec(
|
||||
:callback,
|
||||
unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.defspec(:callback, unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,16 +103,9 @@ defmodule Kernel.Typespec do
|
||||
defmacro defmacrocallback(spec) do
|
||||
pos = :elixir_locals.cache_env(__CALLER__)
|
||||
%{line: line, file: file, module: module} = __CALLER__
|
||||
|
||||
quote do
|
||||
Kernel.Typespec.defspec(
|
||||
:macrocallback,
|
||||
unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line),
|
||||
unquote(file),
|
||||
unquote(module),
|
||||
unquote(pos)
|
||||
)
|
||||
Kernel.Typespec.defspec(:macrocallback, unquote(Macro.escape(spec, unquote: true)),
|
||||
unquote(line), unquote(file), unquote(module), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,9 +120,8 @@ defmodule Kernel.Typespec do
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
type_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -177,7 +134,6 @@ defmodule Kernel.Typespec do
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@@ -191,26 +147,23 @@ defmodule Kernel.Typespec do
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
@spec spec_to_ast(atom, tuple) :: {atom, keyword, [Macro.t()]}
|
||||
@spec spec_to_ast(atom, tuple) :: {atom, keyword, [Macro.t]}
|
||||
def spec_to_ast(name, spec)
|
||||
|
||||
def spec_to_ast(name, {:type, line, :fun, [{:type, _, :product, args}, result]})
|
||||
when is_atom(name) do
|
||||
meta = [line: line]
|
||||
body = {name, meta, Enum.map(args, &typespec_to_ast/1)}
|
||||
|
||||
vars =
|
||||
for type_expr <- args ++ [result],
|
||||
var <- collect_vars(type_expr),
|
||||
uniq: true,
|
||||
do: {var, {:var, meta, nil}}
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
spec = {:::, meta, [body, typespec_to_ast(result)]}
|
||||
|
||||
@@ -225,60 +178,50 @@ defmodule Kernel.Typespec do
|
||||
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
|
||||
end
|
||||
|
||||
def spec_to_ast(name, {:type, line, :bounded_fun, [type, constrs]}) when is_atom(name) do
|
||||
{:type, _, :fun, [{:type, _, :product, args}, result]} = type
|
||||
|
||||
def spec_to_ast(name, {:type, line, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, args}, result]}, constraints]})
|
||||
when is_atom(name) do
|
||||
guards =
|
||||
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constrs do
|
||||
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constraints do
|
||||
{var, typespec_to_ast(type)}
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
ignore_vars = Keyword.keys(guards)
|
||||
|
||||
vars =
|
||||
for type_expr <- args ++ [result],
|
||||
var <- collect_vars(type_expr),
|
||||
var not in ignore_vars,
|
||||
uniq: true,
|
||||
do: {var, {:var, meta, nil}}
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
|
||||
when_args = [
|
||||
{:when, meta, [
|
||||
{:::, meta, [{name, [line: line], args}, typespec_to_ast(result)]},
|
||||
guards ++ vars
|
||||
]
|
||||
|
||||
{:when, meta, when_args}
|
||||
]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast(type)
|
||||
|
||||
def type_to_ast({{:record, record}, fields, args}) when is_atom(record) do
|
||||
fields = for field <- fields, do: typespec_to_ast(field)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
type = {:{}, [], [record | fields]}
|
||||
quote(do: unquote(record)(unquote_splicing(args)) :: unquote(type))
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({name, type, args}) when is_atom(name) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
quote(do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type)))
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: Remove on v2.0
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
warning =
|
||||
"Kernel.Typespec.beam_typedocs/1 is deprecated, please use Code.get_docs/2 instead\n" <>
|
||||
Exception.format_stacktrace()
|
||||
|
||||
IO.write(:stderr, warning)
|
||||
|
||||
IO.write :stderr, "Kernel.Typespec.beam_typedocs/1 is deprecated, please use Code.get_docs/2 instead\n" <>
|
||||
Exception.format_stacktrace
|
||||
if docs = Code.get_docs(module, :type_docs) do
|
||||
for {tuple, _, _, doc} <- docs, do: {tuple, doc}
|
||||
end
|
||||
@@ -300,15 +243,13 @@ defmodule Kernel.Typespec do
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = :lists.flatten(exported_types)
|
||||
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code,
|
||||
kind in [:opaque, :type] do
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> {:opaque, type}
|
||||
{name, length(args)} in exported_types -> {:type, type}
|
||||
true -> {:typep, type}
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -346,7 +287,6 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
for {:attribute, _, abs_kind, value} <- abstract_code, kind == abs_kind, do: value
|
||||
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
@@ -356,7 +296,6 @@ defmodule Kernel.Typespec do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, {_, [{:abstract_code, {_raw_abstract_v1, abstract_code}}]}} ->
|
||||
{:ok, abstract_code}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
@@ -376,18 +315,18 @@ defmodule Kernel.Typespec do
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def spec_to_signature({:when, _, [spec, _]}), do: type_to_signature(spec)
|
||||
def spec_to_signature(other), do: type_to_signature(other)
|
||||
def spec_to_signature({:when, _, [spec, _]}),
|
||||
do: type_to_signature(spec)
|
||||
def spec_to_signature(other),
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, context}, _]})
|
||||
when is_atom(name) and is_atom(context),
|
||||
do: {name, 0}
|
||||
|
||||
def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
|
||||
do: {name, length(args)}
|
||||
|
||||
def type_to_signature(_), do: :error
|
||||
def type_to_signature(_),
|
||||
do: :error
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@@ -396,11 +335,9 @@ defmodule Kernel.Typespec do
|
||||
case spec_to_signature(expr) do
|
||||
{name, arity} ->
|
||||
store_callbackdoc(line, file, module, kind, name, arity)
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
|
||||
Module.store_typespec(module, kind, {kind, expr, pos})
|
||||
end
|
||||
|
||||
@@ -412,7 +349,6 @@ defmodule Kernel.Typespec do
|
||||
defp store_callbackdoc(line, _file, module, kind, name, arity) do
|
||||
table = :elixir_module.data_table(module)
|
||||
{line, doc} = get_doc_info(table, :doc, line)
|
||||
_ = get_since_info(table)
|
||||
:ets.insert(table, {{:callbackdoc, {name, arity}}, line, kind, doc})
|
||||
end
|
||||
|
||||
@@ -423,30 +359,22 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_since_info(table) do
|
||||
:ets.take(table, :since)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftype(kind, expr, line, file, module, pos) do
|
||||
case type_to_signature(expr) do
|
||||
{name, arity} -> store_typedoc(line, file, module, kind, name, arity)
|
||||
:error -> :error
|
||||
end
|
||||
|
||||
Module.store_typespec(module, kind, {kind, expr, pos})
|
||||
end
|
||||
|
||||
defp store_typedoc(line, file, module, kind, name, arity) do
|
||||
table = :elixir_module.data_table(module)
|
||||
{line, doc} = get_doc_info(table, :typedoc, line)
|
||||
_ = get_since_info(table)
|
||||
|
||||
if kind == :typep && doc do
|
||||
warning =
|
||||
"type #{name}/#{arity} is private, @typedoc's are always discarded for private types"
|
||||
|
||||
:elixir_errors.warn(line, file, warning)
|
||||
:elixir_errors.warn(line, file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types")
|
||||
end
|
||||
|
||||
:ets.insert(table, {{:typedoc, {name, arity}}, line, kind, doc})
|
||||
@@ -455,8 +383,7 @@ defmodule Kernel.Typespec do
|
||||
## Translation from Elixir AST to typespec AST
|
||||
|
||||
@doc false
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, pos)
|
||||
when is_atom(name) and name != ::: do
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, pos) when is_atom(name) and name != ::: do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
|
||||
args =
|
||||
@@ -466,30 +393,30 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
arity = length(vars)
|
||||
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
if builtin_type?(name, arity) do
|
||||
compile_error(caller, "type #{name}/#{arity} is a builtin type and it cannot be redefined")
|
||||
compile_error caller, "type #{name}/#{arity} is a builtin type and it cannot be redefined"
|
||||
end
|
||||
|
||||
{kind, {name, arity}, caller.line, type, export}
|
||||
{{kind, {name, arity}, type}, caller.line, export}
|
||||
end
|
||||
|
||||
def translate_type(_kind, other, pos) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error(caller, "invalid type specification: #{type_spec}")
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
defp builtin_type?(:as_boolean, 1), do: true
|
||||
@@ -514,30 +441,28 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller)
|
||||
when is_atom(name) and name != ::: do
|
||||
when is_atom(name) and name != ::: do
|
||||
translate_spec(kind, meta, name, args, return, guard, caller)
|
||||
end
|
||||
|
||||
defp translate_spec(_kind, {name, _meta, _args} = spec, _guard, caller)
|
||||
when is_atom(name) and name != ::: do
|
||||
defp translate_spec(_kind, {name, _meta, _args} = spec, _guard, caller) when is_atom(name) and name != ::: do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error(caller, "type specification missing return type: #{spec}")
|
||||
compile_error caller, "type specification missing return type: #{spec}"
|
||||
end
|
||||
|
||||
defp translate_spec(_kind, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error(caller, "invalid type specification: #{spec}")
|
||||
compile_error caller, "invalid type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
||||
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
||||
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
||||
ensure_no_defaults!(args)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
error = "expected keywords as guard in type specification, got: #{Macro.to_string(guard)}"
|
||||
compile_error(caller, error)
|
||||
compile_error caller, "expected keywords as guard in type specification, " <>
|
||||
"got: #{Macro.to_string(guard)}"
|
||||
end
|
||||
|
||||
vars = Keyword.keys(guard)
|
||||
@@ -550,22 +475,19 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
{kind, {name, arity}, caller.line, spec}
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
foreach_fun = fn
|
||||
:lists.foreach fn
|
||||
{:::, _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
|
||||
other ->
|
||||
ensure_not_default(other)
|
||||
other
|
||||
end
|
||||
|
||||
:lists.foreach(foreach_fun, args)
|
||||
end, args
|
||||
end
|
||||
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
@@ -577,21 +499,14 @@ defmodule Kernel.Typespec do
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
foldl_fun = fn
|
||||
:lists.foldl(fn
|
||||
{_name, {:var, _, context}}, acc when is_atom(context) ->
|
||||
acc
|
||||
|
||||
{name, type}, acc ->
|
||||
constraint = [
|
||||
{:atom, line, :is_subtype},
|
||||
[{:var, line, name}, typespec(type, vars, caller)]
|
||||
]
|
||||
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type | acc]
|
||||
end
|
||||
|
||||
:lists.reverse(:lists.foldl(foldl_fun, [], guard))
|
||||
end, [], guard) |> :lists.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
@@ -665,20 +580,16 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields =
|
||||
Enum.map(fields, fn
|
||||
{:type, _, :map_field_assoc, :any} ->
|
||||
{{:optional, [], [{:any, [], []}]}, {:any, [], []}}
|
||||
|
||||
{:type, _, :map_field_exact, [{:atom, _, k}, v]} ->
|
||||
{k, typespec_to_ast(v)}
|
||||
|
||||
{:type, _, :map_field_exact, [k, v]} ->
|
||||
{{:required, [], [typespec_to_ast(k)]}, typespec_to_ast(v)}
|
||||
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{{:optional, [], [typespec_to_ast(k)]}, typespec_to_ast(v)}
|
||||
end)
|
||||
fields = Enum.map fields, fn
|
||||
{:type, _, :map_field_assoc, :any} ->
|
||||
{{:optional, [], [{:any, [], []}]}, {:any, [], []}}
|
||||
{:type, _, :map_field_exact, [{:atom, _, k}, v]} ->
|
||||
{k, typespec_to_ast(v)}
|
||||
{:type, _, :map_field_exact, [k, v]} ->
|
||||
{{:required, [], [typespec_to_ast(k)]}, typespec_to_ast(v)}
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{{:optional, [], [typespec_to_ast(k)]}, typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
map = {:%{}, [line: line], fields}
|
||||
@@ -692,22 +603,19 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({:type, line, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
|
||||
|
||||
case {typespec_to_ast(arg1), typespec_to_ast(arg2)} do
|
||||
{arg1, 0} ->
|
||||
quote(line: line, do: <<_::unquote(arg1)>>)
|
||||
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
{0, arg2} ->
|
||||
quote(line: line, do: <<_::_*unquote(arg2)>>)
|
||||
|
||||
quote line: line, do: <<_ :: _ * unquote(arg2)>>
|
||||
{arg1, arg2} ->
|
||||
quote(line: line, do: <<_::unquote(arg1), _::_*unquote(arg2)>>)
|
||||
quote line: line, do: <<_ :: unquote(arg1), _ :: _ * unquote(arg2)>>
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :union, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
Enum.reduce(Enum.reverse(args), fn arg, expr -> {:|, [line: line], [arg, expr]} end)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> {:|, [line: line], [arg, expr]} end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :fun, [{:type, _, :product, args}, result]}) do
|
||||
@@ -747,15 +655,11 @@ defmodule Kernel.Typespec do
|
||||
# Special shortcut(s)
|
||||
# TODO: Remove char_list type by 2.0
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, type}, []]})
|
||||
when type in [:charlist, :char_list] do
|
||||
when type in [:charlist, :char_list] do
|
||||
typespec_to_ast({:type, line, :charlist, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({
|
||||
:remote_type,
|
||||
line,
|
||||
[{:atom, _, :elixir}, {:atom, _, :nonempty_charlist}, []]
|
||||
}) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :nonempty_charlist}, []]}) do
|
||||
typespec_to_ast({:type, line, :nonempty_charlist, []})
|
||||
end
|
||||
|
||||
@@ -763,9 +667,7 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :struct, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast(
|
||||
{:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}
|
||||
) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
@@ -775,7 +677,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [mod, name, args]}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
dot = {:., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)]}
|
||||
dot = {:., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)]}
|
||||
{dot, [line: line], args}
|
||||
end
|
||||
|
||||
@@ -783,12 +685,14 @@ defmodule Kernel.Typespec do
|
||||
{:::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:typed_record_field, {:record_field, line, {:atom, line1, name}}, type}) do
|
||||
defp typespec_to_ast({:typed_record_field,
|
||||
{:record_field, line, {:atom, line1, name}},
|
||||
type}) do
|
||||
typespec_to_ast({:ann_type, line, [{:var, line1, name}, type]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
quote(do: ...)
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
@@ -805,7 +709,6 @@ defmodule Kernel.Typespec do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c::binary-1, rest::binary>> ->
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
|
||||
<<c::binary-1, rest::binary>> ->
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
@@ -832,36 +735,19 @@ defmodule Kernel.Typespec do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
|
||||
end
|
||||
|
||||
defp typespec(
|
||||
{:<<>>, meta, [{:::, unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]},
|
||||
_,
|
||||
_
|
||||
)
|
||||
when is_atom(ctx1) and is_atom(ctx2) and is_integer(unit) do
|
||||
defp typespec({:<<>>, meta, [{:::, unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]}, _, _)
|
||||
when is_atom(ctx1) and is_atom(ctx2) and is_integer(unit) do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(unit_meta), unit}]}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx}, size]}]}, _, _)
|
||||
when is_atom(ctx) and is_integer(size) do
|
||||
when is_atom(ctx) and is_integer(size) do
|
||||
{:type, line(meta), :binary, [{:integer, line(size_meta), size}, {:integer, line(meta), 0}]}
|
||||
end
|
||||
|
||||
defp typespec(
|
||||
{
|
||||
:<<>>,
|
||||
meta,
|
||||
[
|
||||
{:::, size_meta, [{:_, _, ctx1}, size]},
|
||||
{:::, unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}
|
||||
]
|
||||
},
|
||||
_,
|
||||
_
|
||||
)
|
||||
when is_atom(ctx1) and is_atom(ctx2) and is_atom(ctx3) and is_integer(size) and
|
||||
is_integer(unit) do
|
||||
args = [{:integer, line(size_meta), size}, {:integer, line(unit_meta), unit}]
|
||||
{:type, line(meta), :binary, args}
|
||||
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx1}, size]}, {:::, unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}]}, _, _)
|
||||
when is_atom(ctx1) and is_atom(ctx2) and is_atom(ctx3) and is_integer(size) and is_integer(unit) do
|
||||
{:type, line(meta), :binary, [{:integer, line(size_meta), size}, {:integer, line(unit_meta), unit}]}
|
||||
end
|
||||
|
||||
## Handle maps and structs
|
||||
@@ -870,41 +756,29 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:%{}, meta, fields} = map, vars, caller) do
|
||||
map_fun = fn
|
||||
{k, v} when is_atom(k) ->
|
||||
args = [typespec(k, vars, caller), typespec(v, vars, caller)]
|
||||
{:type, line(meta), :map_field_exact, args}
|
||||
fields =
|
||||
:lists.map(fn
|
||||
{k, v} when is_atom(k) ->
|
||||
{:type, line(meta), :map_field_exact, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
{{:required, meta2, [k]}, v} ->
|
||||
{:type, line(meta2), :map_field_exact, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
{{:optional, meta2, [k]}, v} ->
|
||||
{:type, line(meta2), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
{k, v} ->
|
||||
# TODO: Emit warnings on v1.6 (when we drop OTP 18 support)
|
||||
# :elixir_errors.warn(caller.line, caller.file,
|
||||
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
|
||||
# "%{required(foo) => bar} and %{optional(foo) => bar}. required/1 is an " <>
|
||||
# "OTP 19 only feature, if you are targeting OTP 18 use optional/1.")
|
||||
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
{:|, _, [_, _]} ->
|
||||
compile_error(caller,
|
||||
"invalid map specification. When using the | operator in the map key, " <>
|
||||
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}")
|
||||
_ ->
|
||||
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
|
||||
end, fields)
|
||||
|
||||
{{:required, meta2, [k]}, v} ->
|
||||
args = [typespec(k, vars, caller), typespec(v, vars, caller)]
|
||||
{:type, line(meta2), :map_field_exact, args}
|
||||
|
||||
{{:optional, meta2, [k]}, v} ->
|
||||
args = [typespec(k, vars, caller), typespec(v, vars, caller)]
|
||||
{:type, line(meta2), :map_field_assoc, args}
|
||||
|
||||
{k, v} ->
|
||||
# TODO: Warn on Elixir v1.8 (since v1.6 is the first version to drop support for 18 and
|
||||
# older)
|
||||
# warning =
|
||||
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
|
||||
# "%{required(foo) => bar} and %{optional(foo) => bar}."
|
||||
# :elixir_errors.warn(caller.line, caller.file, warning)
|
||||
args = [typespec(k, vars, caller), typespec(v, vars, caller)]
|
||||
{:type, line(meta), :map_field_assoc, args}
|
||||
|
||||
{:|, _, [_, _]} ->
|
||||
error =
|
||||
"invalid map specification. When using the | operator in the map key, " <>
|
||||
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}"
|
||||
|
||||
compile_error(caller, error)
|
||||
|
||||
_ ->
|
||||
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
|
||||
end
|
||||
|
||||
fields = :lists.map(map_fun, fields)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
@@ -921,22 +795,22 @@ defmodule Kernel.Typespec do
|
||||
module.__struct__
|
||||
end
|
||||
|
||||
struct = struct |> Map.from_struct() |> Map.to_list()
|
||||
struct = struct |> Map.from_struct |> Map.to_list
|
||||
|
||||
unless Keyword.keyword?(fields) do
|
||||
compile_error(caller, "expected key-value pairs in struct #{Macro.to_string(name)}")
|
||||
end
|
||||
|
||||
map_fun = fn {field, _} -> {field, Keyword.get(fields, field, quote(do: term()))} end
|
||||
types = :lists.map(map_fun, struct)
|
||||
types =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, Keyword.get(fields, field, quote(do: term()))}
|
||||
end, struct)
|
||||
|
||||
foreach_fun = fn {field, _} ->
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(struct, field) do
|
||||
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
|
||||
end
|
||||
end
|
||||
|
||||
:lists.foreach(foreach_fun, fields)
|
||||
end, fields)
|
||||
|
||||
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller)
|
||||
end
|
||||
@@ -951,21 +825,20 @@ defmodule Kernel.Typespec do
|
||||
# as a compile time dependency because for records it actually is one.
|
||||
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||
keyword when is_list(keyword) ->
|
||||
map_fun = fn {field, _} -> Keyword.get(fields, field, quote(do: term())) end
|
||||
types = :lists.map(map_fun, keyword)
|
||||
types =
|
||||
:lists.map(fn {field, _} ->
|
||||
Keyword.get(fields, field, quote(do: term()))
|
||||
end, keyword)
|
||||
|
||||
foreach_fun = fn {field, _} ->
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(keyword, field) do
|
||||
compile_error(caller, "undefined field #{field} on record #{inspect(atom)}")
|
||||
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
|
||||
end
|
||||
end
|
||||
|
||||
:lists.foreach(foreach_fun, fields)
|
||||
end, fields)
|
||||
|
||||
typespec({:{}, meta, [atom | types]}, vars, caller)
|
||||
|
||||
_ ->
|
||||
compile_error(caller, "unknown record #{inspect(atom)}")
|
||||
compile_error(caller, "unknown record #{inspect atom}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -995,7 +868,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:::, meta, [var, expr]}, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0) | vars], caller)
|
||||
left = typespec(var, [elem(var, 0) | vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{:ann_type, line(meta), [left, right]}
|
||||
end
|
||||
@@ -1011,16 +884,11 @@ defmodule Kernel.Typespec do
|
||||
# things like __MODULE__).
|
||||
defp typespec({{:., meta, [{:@, _, [{attr, _, _}]}, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Module.get_attribute(caller.module, attr)
|
||||
|
||||
unless is_atom(remote) and remote != nil do
|
||||
message =
|
||||
"invalid remote in typespec: #{Macro.to_string(orig)} (@#{attr} is #{inspect(remote)})"
|
||||
|
||||
message = "invalid remote in typespec: #{Macro.to_string(orig)} (@#{attr} is #{inspect remote})"
|
||||
compile_error(caller, message)
|
||||
end
|
||||
|
||||
type = {typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}
|
||||
remote_type(type, vars, caller)
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
@@ -1028,13 +896,10 @@ defmodule Kernel.Typespec do
|
||||
# We set a function name to avoid tracking
|
||||
# aliases in typespecs as compile time dependencies.
|
||||
remote = Macro.expand(remote, %{caller | function: {:typespec, 0}})
|
||||
|
||||
unless is_atom(remote) do
|
||||
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
|
||||
end
|
||||
|
||||
type = {typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}
|
||||
remote_type(type, vars, caller)
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle tuples
|
||||
@@ -1067,24 +932,20 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
warning =
|
||||
:elixir_errors.warn caller.line, caller.file,
|
||||
"string() type use is discouraged. " <>
|
||||
"For character lists, use charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
"For character lists, use charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :string, arguments}
|
||||
end
|
||||
|
||||
defp typespec({:nonempty_string, meta, arguments}, vars, caller) do
|
||||
warning =
|
||||
:elixir_errors.warn caller.line, caller.file,
|
||||
"nonempty_string() type use is discouraged. " <>
|
||||
"For non-empty character lists, use nonempty_charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
"For non-empty character lists, use nonempty_charlist() type, for strings, String.t()\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
|
||||
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :nonempty_string, arguments}
|
||||
@@ -1093,27 +954,25 @@ defmodule Kernel.Typespec do
|
||||
# TODO: Remove char_list type by 2.0
|
||||
defp typespec({type, _meta, []}, vars, caller) when type in [:charlist, :char_list] do
|
||||
if type == :char_list do
|
||||
warning = "the char_list() type is deprecated, use charlist()"
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
:elixir_errors.warn caller.line, caller.file, "the char_list() type is deprecated, use charlist()"
|
||||
end
|
||||
|
||||
typespec(quote(do: :elixir.charlist()), vars, caller)
|
||||
typespec((quote do: :elixir.charlist()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:nonempty_charlist, _meta, []}, vars, caller) do
|
||||
typespec(quote(do: :elixir.nonempty_charlist()), vars, caller)
|
||||
typespec((quote do: :elixir.nonempty_charlist()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:struct, _meta, []}, vars, caller) do
|
||||
typespec(quote(do: :elixir.struct()), vars, caller)
|
||||
typespec((quote do: :elixir.struct()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
|
||||
typespec(quote(do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:keyword, _meta, args}, vars, caller) when length(args) <= 1 do
|
||||
typespec(quote(do: :elixir.keyword(unquote_splicing(args))), vars, caller)
|
||||
typespec((quote do: :elixir.keyword(unquote_splicing(args))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:fun, meta, args}, vars, caller) do
|
||||
@@ -1155,14 +1014,14 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h | t] = :lists.reverse(list)
|
||||
|
||||
foldl_fun = fn x, acc -> {:|, [], [validate_kw(x, list, caller), acc]} end
|
||||
union = :lists.foldl(foldl_fun, validate_kw(h, list, caller), t)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end, validate_kw(h, list, caller), t)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string(other)}")
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1180,9 +1039,8 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({key, _} = t, _, _caller) when is_atom(key), do: t
|
||||
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string(original)}")
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
|
||||
+19
-146
@@ -6,13 +6,10 @@ defmodule Kernel.Utils do
|
||||
@doc """
|
||||
Callback for destructure.
|
||||
"""
|
||||
def destructure(list, count)
|
||||
when is_list(list) and is_integer(count) and count >= 0,
|
||||
do: destructure_list(list, count)
|
||||
|
||||
def destructure(nil, count)
|
||||
when is_integer(count) and count >= 0,
|
||||
do: destructure_nil(count)
|
||||
def destructure(list, count) when is_list(list) and is_integer(count) and count >= 0,
|
||||
do: destructure_list(list, count)
|
||||
def destructure(nil, count) when is_integer(count) and count >= 0,
|
||||
do: destructure_nil(count)
|
||||
|
||||
defp destructure_list(_, 0), do: []
|
||||
defp destructure_list([], count), do: destructure_nil(count)
|
||||
@@ -57,8 +54,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
defp validate_arg(ast) do
|
||||
raise ArgumentError,
|
||||
"defdelegate/2 only accepts function parameters, got: #{Macro.to_string(ast)}"
|
||||
raise ArgumentError, "defdelegate/2 only accepts function parameters, got: #{Macro.to_string(ast)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,12 +64,11 @@ defmodule Kernel.Utils do
|
||||
case fields do
|
||||
fs when is_list(fs) ->
|
||||
:ok
|
||||
|
||||
other ->
|
||||
raise ArgumentError, "struct fields definition must be list, got: #{inspect(other)}"
|
||||
raise ArgumentError, "struct fields definition must be list, got: #{inspect other}"
|
||||
end
|
||||
|
||||
mapper = fn
|
||||
fields = :lists.map(fn
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
@@ -83,29 +78,22 @@ defmodule Kernel.Utils do
|
||||
else
|
||||
_ -> {key, val}
|
||||
end
|
||||
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
|
||||
other ->
|
||||
raise ArgumentError, "struct field names must be atoms, got: #{inspect(other)}"
|
||||
end
|
||||
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
|
||||
fields = :lists.map(mapper, fields)
|
||||
enforce_keys = List.wrap(Module.get_attribute(module, :enforce_keys))
|
||||
|
||||
foreach = fn
|
||||
key when is_atom(key) ->
|
||||
:ok
|
||||
:lists.foreach(fn
|
||||
key when is_atom(key) -> :ok
|
||||
key -> raise ArgumentError, "keys given to @enforce_keys must be atoms, got: #{inspect key}"
|
||||
end, enforce_keys)
|
||||
|
||||
key ->
|
||||
raise ArgumentError, "keys given to @enforce_keys must be atoms, got: #{inspect(key)}"
|
||||
end
|
||||
|
||||
:lists.foreach(foreach, enforce_keys)
|
||||
|
||||
struct = :maps.put(:__struct__, module, :maps.from_list(fields))
|
||||
{struct, enforce_keys, Module.get_attribute(module, :derive)}
|
||||
{:maps.put(:__struct__, module, :maps.from_list(fields)),
|
||||
enforce_keys,
|
||||
Module.get_attribute(module, :derive)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -124,129 +112,14 @@ defmodule Kernel.Utils do
|
||||
def raise(msg) when is_binary(msg) do
|
||||
RuntimeError.exception(msg)
|
||||
end
|
||||
|
||||
def raise(atom) when is_atom(atom) do
|
||||
atom.exception([])
|
||||
end
|
||||
|
||||
def raise(%_{__exception__: true} = exception) do
|
||||
def raise(%{__struct__: struct, __exception__: true} = exception) when is_atom(struct) do
|
||||
exception
|
||||
end
|
||||
|
||||
def raise(other) do
|
||||
ArgumentError.exception(
|
||||
"raise/1 and reraise/2 expect a module name, string or exception " <>
|
||||
"as the first argument, got: #{inspect(other)}"
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback for defguard.
|
||||
|
||||
Rewrites an expression so it can be used both inside and outside a guard.
|
||||
|
||||
Take, for example, the expression:
|
||||
|
||||
is_integer(value) and rem(value, 2) == 0
|
||||
|
||||
If we wanted to create a macro, `is_even`, from this expression, that could be
|
||||
used in guards, we'd have to take several things into account.
|
||||
|
||||
First, if this expression is being used inside a guard, `value` needs to be
|
||||
unquoted each place it occurs, since it has not yet been at that point in our
|
||||
macro.
|
||||
|
||||
Secondly, if the expression is being used outside of a guard, we want to unquote
|
||||
`value`––but only once, and then re-use the unquoted form throughout the expression.
|
||||
|
||||
This helper does exactly that: takes the AST for an expression and a list of
|
||||
variable references it should be aware of, and rewrites it into a new expression
|
||||
that checks for its presence in a guard, then unquotes the variable references as
|
||||
appropriate.
|
||||
|
||||
The resulting transformation looks something like this:
|
||||
|
||||
> expression = quote do: is_integer(value) and rem(value, 2) == 0
|
||||
> variable_references = [value: Elixir]
|
||||
> Kernel.Utils.defguard(expression, variable_references) |> Macro.to_string |> IO.puts
|
||||
|
||||
case Macro.Env.in_guard? __CALLER__ do
|
||||
true -> quote do
|
||||
is_integer(unquote(value)) and rem(unquote(value), 2) == 0
|
||||
end
|
||||
false -> quote do
|
||||
value = unquote(value)
|
||||
is_integer(value) and rem(value, 2) == 0
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro defguard(args, expr) do
|
||||
defguard(args, expr, __CALLER__)
|
||||
end
|
||||
|
||||
@spec defguard([Macro.t()], Macro.t(), Macro.Env.t()) :: Macro.t()
|
||||
def defguard(args, expr, env) do
|
||||
{^args, vars} = extract_refs_from_args(args)
|
||||
{expr, _scope} = :elixir_expand.expand(expr, %{env | context: :guard, vars: vars})
|
||||
|
||||
quote do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true -> unquote(literal_quote(unquote_every_ref(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_refs_from_args(args) do
|
||||
Macro.postwalk(args, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
{var, [{ref, context} | acc]}
|
||||
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
# Finds every reference to `refs` in `expr` and wraps them in an unquote.
|
||||
defp unquote_every_ref(expr, refs) do
|
||||
Macro.postwalk(expr, fn
|
||||
{ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs do
|
||||
true -> literal_unquote(var)
|
||||
false -> var
|
||||
end
|
||||
|
||||
node ->
|
||||
node
|
||||
end)
|
||||
end
|
||||
|
||||
# Prefaces `expr` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(expr, refs) do
|
||||
{^expr, used_refs} =
|
||||
Macro.postwalk(expr, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs and {ref, context} not in acc do
|
||||
true -> {var, [{ref, context} | acc]}
|
||||
false -> {var, acc}
|
||||
end
|
||||
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
|
||||
for {ref, context} <- :lists.reverse(used_refs) do
|
||||
var = {ref, [], context}
|
||||
quote do: unquote(var) = unquote(literal_unquote(var))
|
||||
end ++ List.wrap(expr)
|
||||
end
|
||||
|
||||
defp literal_quote(ast) do
|
||||
{:quote, [], [[do: {:__block__, [], List.wrap(ast)}]]}
|
||||
end
|
||||
|
||||
defp literal_unquote(ast) do
|
||||
{:unquote, [], List.wrap(ast)}
|
||||
ArgumentError.exception("raise/1 expects a module name, string or exception as " <>
|
||||
"the first argument, got: #{inspect other}")
|
||||
end
|
||||
end
|
||||
|
||||
+36
-69
@@ -88,7 +88,7 @@ defmodule Keyword do
|
||||
def keyword?(term)
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key), do: keyword?(rest)
|
||||
def keyword?([]), do: true
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
@doc """
|
||||
@@ -119,7 +119,7 @@ defmodule Keyword do
|
||||
[a: 3]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t()) :: t
|
||||
@spec new(Enum.t) :: t
|
||||
def new(pairs) do
|
||||
new(pairs, fn pair -> pair end)
|
||||
end
|
||||
@@ -137,13 +137,12 @@ defmodule Keyword do
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t(), (term -> {key, value})) :: t
|
||||
@spec new(Enum.t, (term -> {key, value})) :: t
|
||||
def new(pairs, transform) do
|
||||
fun = fn el, acc ->
|
||||
{k, v} = transform.(el)
|
||||
put_new(acc, k, v)
|
||||
end
|
||||
|
||||
:lists.foldl(fun, [], Enum.reverse(pairs))
|
||||
end
|
||||
|
||||
@@ -250,32 +249,29 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get_and_update(t, key, (value -> {get, value} | :pop)) :: {get, t} when get: term
|
||||
def get_and_update(keywords, key, fun)
|
||||
when is_list(keywords) and is_atom(key),
|
||||
do: get_and_update(keywords, [], key, fun)
|
||||
when is_list(keywords) and is_atom(key),
|
||||
do: get_and_update(keywords, [], key, fun)
|
||||
|
||||
defp get_and_update([{key, current} | t], acc, key, fun) do
|
||||
case fun.(current) do
|
||||
{get, value} ->
|
||||
{get, :lists.reverse(acc, [{key, value} | t])}
|
||||
|
||||
:pop ->
|
||||
{current, :lists.reverse(acc, t)}
|
||||
|
||||
other ->
|
||||
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp get_and_update([{_, _} = h | t], acc, key, fun), do: get_and_update(t, [h | acc], key, fun)
|
||||
defp get_and_update([{_, _} = h | t], acc, key, fun),
|
||||
do: get_and_update(t, [h | acc], key, fun)
|
||||
|
||||
defp get_and_update([], acc, key, fun) do
|
||||
case fun.(nil) do
|
||||
{get, update} ->
|
||||
{get, [{key, update} | :lists.reverse(acc)]}
|
||||
|
||||
:pop ->
|
||||
{nil, :lists.reverse(acc)}
|
||||
|
||||
other ->
|
||||
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
|
||||
end
|
||||
@@ -319,10 +315,8 @@ defmodule Keyword do
|
||||
case fun.(value) do
|
||||
{get, value} ->
|
||||
{get, :lists.reverse(acc, [{key, value} | delete(keywords, key)])}
|
||||
|
||||
:pop ->
|
||||
{value, :lists.reverse(acc, keywords)}
|
||||
|
||||
other ->
|
||||
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
|
||||
end
|
||||
@@ -397,7 +391,6 @@ defmodule Keyword do
|
||||
{^key, val} -> {true, val}
|
||||
{_, _} -> false
|
||||
end
|
||||
|
||||
:lists.filtermap(fun, keywords)
|
||||
end
|
||||
|
||||
@@ -456,20 +449,9 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
delete_key_value(keywords, key, value, _deleted? = false)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
|
||||
end
|
||||
|
||||
defp delete_key_value([{key, value} | rest], key, value, _deleted?),
|
||||
do: delete_key_value(rest, key, value, true)
|
||||
|
||||
defp delete_key_value([{_, _} = pair | rest], key, value, deleted?),
|
||||
do: [pair | delete_key_value(rest, key, value, deleted?)]
|
||||
|
||||
defp delete_key_value([], _key, _value, _deleted? = true), do: []
|
||||
defp delete_key_value([], _key, _value, _deleted? = false), do: throw(:not_deleted)
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
|
||||
@@ -488,21 +470,10 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
@compile {:inline, delete: 2}
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
delete_key(keywords, key, _deleted? = false)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
:lists.filter(fn {k, _} -> k != key end, keywords)
|
||||
end
|
||||
|
||||
defp delete_key([{key, _} | rest], key, _deleted?), do: delete_key(rest, key, true)
|
||||
|
||||
defp delete_key([{_, _} = pair | rest], key, deleted?),
|
||||
do: [pair | delete_key(rest, key, deleted?)]
|
||||
|
||||
defp delete_key([], _key, _deleted? = true), do: []
|
||||
defp delete_key([], _key, _deleted? = false), do: throw(:not_deleted)
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
|
||||
@@ -518,15 +489,9 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
delete_first_key(keywords, key)
|
||||
catch
|
||||
:not_deleted -> keywords
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
defp delete_first_key([{key, _} | rest], key), do: rest
|
||||
defp delete_first_key([{_, _} = pair | rest], key), do: [pair | delete_first_key(rest, key)]
|
||||
defp delete_first_key([], _key), do: throw(:not_deleted)
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
@@ -597,7 +562,22 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Alters the value stored under `key` to `value`, but only
|
||||
if the entry `key` already exists in the keyword list.
|
||||
|
||||
In the case a value is stored multiple times in the keyword list,
|
||||
later occurrences are removed.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.replace([a: 1], :b, 2)
|
||||
[a: 1]
|
||||
iex> Keyword.replace([a: 1, b: 2, a: 4], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec replace(t, key, value) :: t
|
||||
def replace(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, _} -> [{key, value} | delete(keywords, key)]
|
||||
@@ -606,16 +586,13 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Alters the value stored under `key` to `value`, but only
|
||||
if the entry `key` already exists in `keywords`.
|
||||
|
||||
If `key` is not present in `keywords`, a `KeyError` exception is raised.
|
||||
Similar to `replace/3`, but will raise a `KeyError`
|
||||
if the entry `key` does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.replace!([a: 1, b: 2, a: 4], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> Keyword.replace!([a: 1], :b, 2)
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
@@ -675,16 +652,12 @@ defmodule Keyword do
|
||||
fun = fn
|
||||
{key, _value} when is_atom(key) ->
|
||||
not has_key?(keywords2, key)
|
||||
|
||||
_ ->
|
||||
raise ArgumentError,
|
||||
message: "expected a keyword list as the first argument, got: #{inspect(keywords1)}"
|
||||
raise ArgumentError, message: "expected a keyword list as the first argument, got: #{inspect keywords1}"
|
||||
end
|
||||
|
||||
:lists.filter(fun, keywords1) ++ keywords2
|
||||
else
|
||||
raise ArgumentError,
|
||||
message: "expected a keyword list as the second argument, got: #{inspect(keywords2)}"
|
||||
raise ArgumentError, message: "expected a keyword list as the second argument, got: #{inspect keywords2}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -723,22 +696,19 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(keywords1, keywords2, fun)
|
||||
when is_list(keywords1) and is_list(keywords2) and is_function(fun, 3) do
|
||||
def merge(keywords1, keywords2, fun) when is_list(keywords1) and is_list(keywords2) and is_function(fun, 3) do
|
||||
if keyword?(keywords1) do
|
||||
do_merge(keywords2, [], keywords1, keywords1, fun, keywords2)
|
||||
else
|
||||
raise ArgumentError,
|
||||
message: "expected a keyword list as the first argument, got: #{inspect(keywords1)}"
|
||||
raise ArgumentError, message: "expected a keyword list as the first argument, got: #{inspect keywords1}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_merge([{key, value2} | tail], acc, rest, original, fun, keywords2) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, original) do
|
||||
{^key, value1} ->
|
||||
acc = [{key, fun.(key, value1, value2)} | acc]
|
||||
original = :lists.keydelete(key, 1, original)
|
||||
do_merge(tail, acc, delete(rest, key), original, fun, keywords2)
|
||||
do_merge(tail, [{key, fun.(key, value1, value2)} | acc],
|
||||
delete(rest, key), :lists.keydelete(key, 1, original), fun, keywords2)
|
||||
|
||||
false ->
|
||||
do_merge(tail, [{key, value2} | acc], rest, original, fun, keywords2)
|
||||
@@ -750,8 +720,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
defp do_merge(_other, _acc, _rest, _original, _fun, keywords2) do
|
||||
raise ArgumentError,
|
||||
message: "expected a keyword list as the second argument, got: #{inspect(keywords2)}"
|
||||
raise ArgumentError, message: "expected a keyword list as the second argument, got: #{inspect keywords2}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -861,7 +830,7 @@ defmodule Keyword do
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
fun = fn {k, v}, {take, drop} ->
|
||||
case k in keys do
|
||||
true -> {[{k, v} | take], drop}
|
||||
true -> {[{k, v} | take], drop}
|
||||
false -> {take, [{k, v} | drop]}
|
||||
end
|
||||
end
|
||||
@@ -931,7 +900,6 @@ defmodule Keyword do
|
||||
case fetch(keywords, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(keywords, key)}
|
||||
|
||||
:error ->
|
||||
{default, keywords}
|
||||
end
|
||||
@@ -965,7 +933,6 @@ defmodule Keyword do
|
||||
case fetch(keywords, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(keywords, key)}
|
||||
|
||||
:error ->
|
||||
{fun.(), keywords}
|
||||
end
|
||||
|
||||
+23
-111
@@ -78,13 +78,10 @@ defmodule List do
|
||||
|
||||
Application.loaded_applications
|
||||
#=> [{:stdlib, 'ERTS CXC 138 10', '2.6'},
|
||||
#=> {:compiler, 'ERTS CXC 138 10', '6.0.1'},
|
||||
#=> {:elixir, 'elixir', '1.0.0'},
|
||||
#=> {:kernel, 'ERTS CXC 138 10', '4.1'},
|
||||
#=> {:logger, 'logger', '1.0.0'}]
|
||||
|
||||
A list can be checked if it is made of printable ascii
|
||||
codepoints with `ascii_printable?/2`.
|
||||
{:compiler, 'ERTS CXC 138 10', '6.0.1'},
|
||||
{:elixir, 'elixir', '1.0.0'},
|
||||
{:kernel, 'ERTS CXC 138 10', '4.1'},
|
||||
{:logger, 'logger', '1.0.0'}]
|
||||
|
||||
## List and Enum modules
|
||||
|
||||
@@ -185,8 +182,8 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldl(list, acc, fun) when is_list(list) and is_function(fun) do
|
||||
:lists.foldl(fun, acc, list)
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldl(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -200,8 +197,8 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldr(list, acc, fun) when is_list(list) and is_function(fun) do
|
||||
:lists.foldr(fun, acc, list)
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -220,7 +217,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec first([elem]) :: nil | elem when elem: var
|
||||
def first([]), do: nil
|
||||
def first([]), do: nil
|
||||
def first([head | _]), do: head
|
||||
|
||||
@doc """
|
||||
@@ -385,7 +382,7 @@ defmodule List do
|
||||
def keytake(list, key, position) do
|
||||
case :lists.keytake(key, position + 1, list) do
|
||||
{:value, item, list} -> {item, list}
|
||||
false -> nil
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -436,86 +433,10 @@ defmodule List do
|
||||
"""
|
||||
@spec zip([list]) :: [tuple]
|
||||
def zip([]), do: []
|
||||
|
||||
def zip(list_of_lists) when is_list(list_of_lists) do
|
||||
do_zip(list_of_lists, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a list is a charlist made only of printable ASCII characters.
|
||||
|
||||
A printable charlist in Elixir contains only ASCII characters.
|
||||
|
||||
Takes an optional `limit` as a second argument. `ascii_printable?/2` only
|
||||
checks the printability of the list up to the `limit`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.ascii_printable?('abc')
|
||||
true
|
||||
|
||||
iex> List.ascii_printable?('abc' ++ [0])
|
||||
false
|
||||
|
||||
iex> List.ascii_printable?('abc' ++ [0], 2)
|
||||
true
|
||||
|
||||
Improper lists are not printable, even if made only of ascii characters:
|
||||
|
||||
iex> List.ascii_printable?('abc' ++ ?d)
|
||||
false
|
||||
|
||||
"""
|
||||
def ascii_printable?(list, counter \\ :infinity)
|
||||
|
||||
def ascii_printable?(_, 0) do
|
||||
true
|
||||
end
|
||||
|
||||
def ascii_printable?([char | rest], counter)
|
||||
when is_integer(char) and char >= 32 and char <= 126 do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\n | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\r | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\t | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\v | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\b | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\f | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\e | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([?\a | rest], counter) do
|
||||
ascii_printable?(rest, decrement(counter))
|
||||
end
|
||||
|
||||
def ascii_printable?([], _counter), do: true
|
||||
def ascii_printable?(_, _counter), do: false
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
|
||||
@@ -681,9 +602,12 @@ defmodule List do
|
||||
@spec starts_with?([], nonempty_list) :: false
|
||||
def starts_with?(list, prefix)
|
||||
|
||||
def starts_with?([head | tail], [head | prefix_tail]), do: starts_with?(tail, prefix_tail)
|
||||
def starts_with?(list, []) when is_list(list), do: true
|
||||
def starts_with?(list, [_ | _]) when is_list(list), do: false
|
||||
def starts_with?([head | tail], [head | prefix_tail]),
|
||||
do: starts_with?(tail, prefix_tail);
|
||||
def starts_with?(list, []) when is_list(list),
|
||||
do: true
|
||||
def starts_with?(list, [_ | _]) when is_list(list),
|
||||
do: false
|
||||
|
||||
@doc """
|
||||
Converts a charlist to an atom.
|
||||
@@ -808,11 +732,8 @@ defmodule List do
|
||||
iex> List.to_string([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
iex> List.to_string([0x0064, "ee", ['p']])
|
||||
"deep"
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.charlist()) :: String.t()
|
||||
@spec to_string(:unicode.charlist) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
try do
|
||||
:unicode.characters_to_binary(list)
|
||||
@@ -829,7 +750,7 @@ defmodule List do
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
#{inspect(list)}
|
||||
#{inspect list}
|
||||
"""
|
||||
else
|
||||
result when is_binary(result) ->
|
||||
@@ -885,24 +806,18 @@ defmodule List do
|
||||
compact_reverse(rest, [{kind, [elem | result]} | acc])
|
||||
end
|
||||
|
||||
defp compact_reverse(rest, [{:eq, elem}, {:ins, elem}, {:eq, other} | acc]) do
|
||||
compact_reverse(rest, [{:ins, elem}, {:eq, elem ++ other} | acc])
|
||||
end
|
||||
|
||||
defp compact_reverse([{kind, elem} | rest], acc) do
|
||||
compact_reverse(rest, [{kind, [elem]} | acc])
|
||||
end
|
||||
|
||||
defp each_diagonal(diag, limit, _paths, next_paths) when diag > limit do
|
||||
{:next, :lists.reverse(next_paths)}
|
||||
{:next, Enum.reverse(next_paths)}
|
||||
end
|
||||
|
||||
defp each_diagonal(diag, limit, paths, next_paths) do
|
||||
{path, rest} = proceed_path(diag, limit, paths)
|
||||
|
||||
case follow_snake(path) do
|
||||
{:cont, path} -> each_diagonal(diag + 2, limit, rest, [path | next_paths])
|
||||
{:done, edits} -> {:done, edits}
|
||||
with {:cont, path} <- follow_snake(path) do
|
||||
each_diagonal(diag + 2, limit, rest, [path | next_paths])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1026,11 +941,8 @@ defmodule List do
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
converter = fn x, acc -> do_zip_each(to_list(x), acc) end
|
||||
|
||||
case :lists.mapfoldl(converter, [], list) do
|
||||
{_, nil} ->
|
||||
:lists.reverse(acc)
|
||||
|
||||
{_, nil} -> :lists.reverse(acc)
|
||||
{mlist, heads} ->
|
||||
do_zip(mlist, [to_tuple(:lists.reverse(heads)) | acc])
|
||||
end
|
||||
@@ -1049,5 +961,5 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
|
||||
defp to_list(list) when is_list(list), do: list
|
||||
defp to_list(list) when is_list(list), do: list
|
||||
end
|
||||
|
||||
@@ -38,9 +38,9 @@ defimpl List.Chars, for: BitString do
|
||||
|
||||
def to_charlist(term) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: term,
|
||||
description: "cannot convert a bitstring to a charlist"
|
||||
protocol: @protocol,
|
||||
value: term,
|
||||
description: "cannot convert a bitstring to a charlist"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+342
-304
File diff suppressed because it is too large
Load Diff
+21
-40
@@ -70,29 +70,26 @@ defmodule Macro.Env do
|
||||
@opaque match_vars :: vars | :warn | :apply
|
||||
@opaque prematch_vars :: vars | nil
|
||||
|
||||
@type t :: %{
|
||||
__struct__: __MODULE__,
|
||||
module: atom,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
context: context,
|
||||
requires: requires,
|
||||
aliases: aliases,
|
||||
functions: functions,
|
||||
macros: macros,
|
||||
macro_aliases: aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
export_vars: export_vars,
|
||||
match_vars: match_vars,
|
||||
prematch_vars: prematch_vars,
|
||||
lexical_tracker: lexical_tracker
|
||||
}
|
||||
@type t :: %{__struct__: __MODULE__,
|
||||
module: atom,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
context: context,
|
||||
requires: requires,
|
||||
aliases: aliases,
|
||||
functions: functions,
|
||||
macros: macros,
|
||||
macro_aliases: aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
export_vars: export_vars,
|
||||
match_vars: match_vars,
|
||||
prematch_vars: prematch_vars,
|
||||
lexical_tracker: lexical_tracker}
|
||||
|
||||
def __struct__ do
|
||||
%{
|
||||
__struct__: __MODULE__,
|
||||
%{__struct__: __MODULE__,
|
||||
module: nil,
|
||||
file: "nofile",
|
||||
line: 0,
|
||||
@@ -108,12 +105,11 @@ defmodule Macro.Env do
|
||||
lexical_tracker: nil,
|
||||
export_vars: nil,
|
||||
match_vars: :warn,
|
||||
prematch_vars: nil
|
||||
}
|
||||
prematch_vars: nil}
|
||||
end
|
||||
|
||||
def __struct__(kv) do
|
||||
Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
|
||||
Enum.reduce kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -122,23 +118,10 @@ defmodule Macro.Env do
|
||||
"""
|
||||
@spec location(t) :: keyword
|
||||
def location(env)
|
||||
|
||||
def location(%{__struct__: Macro.Env, file: file, line: line}) do
|
||||
[file: file, line: line]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `Macro.Env` in the match context.
|
||||
"""
|
||||
@spec to_match(t) :: t
|
||||
def to_match(%{__struct__: Macro.Env, context: :match} = env) do
|
||||
env
|
||||
end
|
||||
|
||||
def to_match(%{__struct__: Macro.Env, prematch_vars: nil, vars: vars} = env) do
|
||||
%{env | context: :match, match_vars: [], prematch_vars: vars}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the compilation environment is currently
|
||||
inside a guard.
|
||||
@@ -163,10 +146,8 @@ defmodule Macro.Env do
|
||||
cond do
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
[{env.module, name, arity, relative_location(env)}]
|
||||
@@ -174,6 +155,6 @@ defmodule Macro.Env do
|
||||
end
|
||||
|
||||
defp relative_location(env) do
|
||||
[file: String.to_charlist(Path.relative_to_cwd(env.file)), line: env.line]
|
||||
[file: Path.relative_to_cwd(env.file), line: env.line]
|
||||
end
|
||||
end
|
||||
|
||||
+54
-66
@@ -162,17 +162,17 @@ defmodule Map do
|
||||
%{a: 3}
|
||||
|
||||
"""
|
||||
@spec new(Enumerable.t()) :: map
|
||||
@spec new(Enumerable.t) :: map
|
||||
def new(enumerable)
|
||||
def new(list) when is_list(list), do: :maps.from_list(list)
|
||||
def new(%_{} = struct), do: new_from_enum(struct)
|
||||
def new(%{__struct__: _} = struct), do: new_from_enum(struct)
|
||||
def new(%{} = map), do: map
|
||||
def new(enum), do: new_from_enum(enum)
|
||||
|
||||
defp new_from_enum(enumerable) do
|
||||
enumerable
|
||||
|> Enum.to_list()
|
||||
|> :maps.from_list()
|
||||
|> Enum.to_list
|
||||
|> :maps.from_list
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -186,17 +186,17 @@ defmodule Map do
|
||||
%{a: :a, b: :b}
|
||||
|
||||
"""
|
||||
@spec new(Enumerable.t(), (term -> {key, value})) :: map
|
||||
@spec new(Enumerable.t, (term -> {key, value})) :: map
|
||||
def new(enumerable, transform) when is_function(transform, 1) do
|
||||
enumerable
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> new_transform(transform, [])
|
||||
end
|
||||
|
||||
defp new_transform([], _fun, acc) do
|
||||
acc
|
||||
|> :lists.reverse()
|
||||
|> :maps.from_list()
|
||||
|> :lists.reverse
|
||||
|> :maps.from_list
|
||||
end
|
||||
|
||||
defp new_transform([item | rest], fun, acc) do
|
||||
@@ -273,24 +273,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => _value} ->
|
||||
map
|
||||
|
||||
%{} ->
|
||||
put(map, key, value)
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other})
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def replace(map, key, value) do
|
||||
case map do
|
||||
%{^key => _value} ->
|
||||
put(map, key, value)
|
||||
|
||||
%{} ->
|
||||
map
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other})
|
||||
end
|
||||
@@ -300,13 +284,34 @@ defmodule Map do
|
||||
Alters the value stored under `key` to `value`, but only
|
||||
if the entry `key` already exists in `map`.
|
||||
|
||||
If `key` is not present in `map`, a `KeyError` exception is raised.
|
||||
## Examples
|
||||
|
||||
iex> Map.replace(%{a: 1}, :b, 2)
|
||||
%{a: 1}
|
||||
iex> Map.replace(%{a: 1, b: 2}, :a, 3)
|
||||
%{a: 3, b: 2}
|
||||
|
||||
"""
|
||||
@spec replace(map, key, value) :: map
|
||||
def replace(map, key, value) do
|
||||
case map do
|
||||
%{^key => _value} ->
|
||||
put(map, key, value)
|
||||
%{} ->
|
||||
map
|
||||
other ->
|
||||
:erlang.error({:badmap, other})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `replace/3`, but will raise a `KeyError`
|
||||
if the key does not exist in the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.replace!(%{a: 1, b: 2}, :a, 3)
|
||||
%{a: 3, b: 2}
|
||||
|
||||
iex> Map.replace!(%{a: 1}, :b, 2)
|
||||
** (KeyError) key :b not found in: %{a: 1}
|
||||
|
||||
@@ -343,10 +348,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => _value} ->
|
||||
map
|
||||
|
||||
%{} ->
|
||||
put(map, key, fun.())
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other})
|
||||
end
|
||||
@@ -364,12 +367,12 @@ defmodule Map do
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec take(map, Enumerable.t()) :: map
|
||||
@spec take(map, Enumerable.t) :: map
|
||||
def take(map, keys)
|
||||
|
||||
def take(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> take(map, [])
|
||||
end
|
||||
|
||||
@@ -415,10 +418,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => value} ->
|
||||
value
|
||||
|
||||
%{} ->
|
||||
default
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other}, [map, key, default])
|
||||
end
|
||||
@@ -451,10 +452,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => value} ->
|
||||
value
|
||||
|
||||
%{} ->
|
||||
fun.()
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other}, [map, key, fun])
|
||||
end
|
||||
@@ -515,12 +514,12 @@ defmodule Map do
|
||||
defdelegate merge(map1, map2), to: :maps
|
||||
|
||||
@doc """
|
||||
Merges two maps into one, resolving conflicts through the given `fun`.
|
||||
Merges two maps into one, resolving conflicts through the given `callback`.
|
||||
|
||||
All keys in `map2` will be added to `map1`. The given function will be invoked
|
||||
when there are duplicate keys; its arguments are `key` (the duplicate key),
|
||||
`value1` (the value of `key` in `map1`), and `value2` (the value of `key` in
|
||||
`map2`). The value returned by `fun` is used as the value under `key` in
|
||||
`map2`). The value returned by `callback` is used as the value under `key` in
|
||||
the resulting map.
|
||||
|
||||
## Examples
|
||||
@@ -532,19 +531,15 @@ defmodule Map do
|
||||
|
||||
"""
|
||||
@spec merge(map, map, (key, value, value -> value)) :: map
|
||||
def merge(map1, map2, fun) when is_function(fun, 3) do
|
||||
def merge(map1, map2, callback) when is_function(callback, 3) do
|
||||
if map_size(map1) > map_size(map2) do
|
||||
folder = fn key, val2, acc ->
|
||||
update(acc, key, val2, fn val1 -> fun.(key, val1, val2) end)
|
||||
end
|
||||
|
||||
:maps.fold(folder, map1, map2)
|
||||
:maps.fold fn key, val2, acc ->
|
||||
update(acc, key, val2, fn val1 -> callback.(key, val1, val2) end)
|
||||
end, map1, map2
|
||||
else
|
||||
folder = fn key, val2, acc ->
|
||||
update(acc, key, val2, fn val1 -> fun.(key, val2, val1) end)
|
||||
end
|
||||
|
||||
:maps.fold(folder, map2, map1)
|
||||
:maps.fold fn key, val2, acc ->
|
||||
update(acc, key, val2, fn val1 -> callback.(key, val2, val1) end)
|
||||
end, map2, map1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -569,10 +564,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => value} ->
|
||||
put(map, key, fun.(value))
|
||||
|
||||
%{} ->
|
||||
put(map, key, initial)
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other}, [map, key, initial, fun])
|
||||
end
|
||||
@@ -597,9 +590,13 @@ defmodule Map do
|
||||
"""
|
||||
@spec pop(map, key, value) :: {value, map}
|
||||
def pop(map, key, default \\ nil) do
|
||||
case :maps.take(key, map) do
|
||||
{_, _} = tuple -> tuple
|
||||
:error -> {default, map}
|
||||
case map do
|
||||
%{^key => value} ->
|
||||
{value, delete(map, key)}
|
||||
%{} ->
|
||||
{default, map}
|
||||
other ->
|
||||
:erlang.error({:badmap, other}, [map, key, default])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -632,10 +629,8 @@ defmodule Map do
|
||||
case map do
|
||||
%{^key => value} ->
|
||||
{value, delete(map, key)}
|
||||
|
||||
%{} ->
|
||||
{fun.(), map}
|
||||
|
||||
other ->
|
||||
:erlang.error({:badmap, other}, [map, key, fun])
|
||||
end
|
||||
@@ -652,12 +647,12 @@ defmodule Map do
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec drop(map, Enumerable.t()) :: map
|
||||
@spec drop(map, Enumerable.t) :: map
|
||||
def drop(map, keys)
|
||||
|
||||
def drop(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> drop_list(map)
|
||||
end
|
||||
|
||||
@@ -666,7 +661,6 @@ defmodule Map do
|
||||
end
|
||||
|
||||
defp drop_list([], acc), do: acc
|
||||
|
||||
defp drop_list([key | rest], acc) do
|
||||
drop_list(rest, delete(acc, key))
|
||||
end
|
||||
@@ -685,12 +679,12 @@ defmodule Map do
|
||||
{%{a: 1, c: 3}, %{b: 2}}
|
||||
|
||||
"""
|
||||
@spec split(map, Enumerable.t()) :: {map, map}
|
||||
@spec split(map, Enumerable.t) :: {map, map}
|
||||
def split(map, keys)
|
||||
|
||||
def split(map, keys) when is_map(map) do
|
||||
keys
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> split([], map)
|
||||
end
|
||||
|
||||
@@ -706,7 +700,6 @@ defmodule Map do
|
||||
case excluded do
|
||||
%{^key => value} ->
|
||||
split(rest, [{key, value} | included], delete(excluded, key))
|
||||
|
||||
_other ->
|
||||
split(rest, included, excluded)
|
||||
end
|
||||
@@ -773,10 +766,8 @@ defmodule Map do
|
||||
case fun.(current) do
|
||||
{get, update} ->
|
||||
{get, put(map, key, update)}
|
||||
|
||||
:pop ->
|
||||
{current, delete(map, key)}
|
||||
|
||||
other ->
|
||||
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
|
||||
end
|
||||
@@ -806,18 +797,15 @@ defmodule Map do
|
||||
{1, %{}}
|
||||
|
||||
"""
|
||||
@spec get_and_update!(map, key, (value -> {get, value} | :pop)) :: {get, map} | no_return
|
||||
when get: term
|
||||
@spec get_and_update!(map, key, (value -> {get, value})) :: {get, map} | no_return when get: term
|
||||
def get_and_update!(map, key, fun) when is_function(fun, 1) do
|
||||
value = fetch!(map, key)
|
||||
|
||||
case fun.(value) do
|
||||
{get, update} ->
|
||||
{get, put(map, key, update)}
|
||||
|
||||
:pop ->
|
||||
{value, delete(map, key)}
|
||||
|
||||
other ->
|
||||
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
|
||||
end
|
||||
|
||||
+40
-58
@@ -32,16 +32,11 @@ defmodule MapSet do
|
||||
structures: for example, see `MapSet.new/1` or `Enum.into/2`.
|
||||
"""
|
||||
|
||||
# MapSets have an underlying Map. MapSet elements are keys of said map,
|
||||
# and this empty list is their associated dummy value.
|
||||
@dummy_value []
|
||||
|
||||
@type value :: term
|
||||
|
||||
@opaque t(value) :: %__MODULE__{map: %{optional(value) => []}}
|
||||
@type t :: t(term)
|
||||
|
||||
# TODO: Remove version key on Elixir 2.0
|
||||
defstruct map: %{}, version: 2
|
||||
|
||||
@doc """
|
||||
@@ -67,15 +62,14 @@ defmodule MapSet do
|
||||
#MapSet<[1, 2, 3]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t()) :: t
|
||||
@spec new(Enum.t) :: t
|
||||
def new(enumerable)
|
||||
|
||||
def new(%__MODULE__{} = map_set), do: map_set
|
||||
|
||||
def new(enumerable) do
|
||||
map =
|
||||
enumerable
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> new_from_list([])
|
||||
|
||||
%MapSet{map: map}
|
||||
@@ -90,11 +84,11 @@ defmodule MapSet do
|
||||
#MapSet<[2, 4]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t(), (term -> val)) :: t(val) when val: value
|
||||
@spec new(Enum.t, (term -> val)) :: t(val) when val: value
|
||||
def new(enumerable, transform) when is_function(transform, 1) do
|
||||
map =
|
||||
enumerable
|
||||
|> Enum.to_list()
|
||||
|> Enum.to_list
|
||||
|> new_from_list_transform(transform, [])
|
||||
|
||||
%MapSet{map: map}
|
||||
@@ -105,15 +99,14 @@ defmodule MapSet do
|
||||
end
|
||||
|
||||
defp new_from_list([item | rest], acc) do
|
||||
new_from_list(rest, [{item, @dummy_value} | acc])
|
||||
new_from_list(rest, [{item, []} | acc])
|
||||
end
|
||||
|
||||
defp new_from_list_transform([], _fun, acc) do
|
||||
:maps.from_list(acc)
|
||||
end
|
||||
|
||||
defp new_from_list_transform([item | rest], fun, acc) do
|
||||
new_from_list_transform(rest, fun, [{fun.(item), @dummy_value} | acc])
|
||||
new_from_list_transform(rest, fun, [{fun.(item), []} | acc])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -150,11 +143,12 @@ defmodule MapSet do
|
||||
# If the first set is less than twice the size of the second map,
|
||||
# it is fastest to re-accumulate items in the first set that are not
|
||||
# present in the second set.
|
||||
def difference(%MapSet{map: map1}, %MapSet{map: map2}) when map_size(map1) < map_size(map2) * 2 do
|
||||
def difference(%MapSet{map: map1}, %MapSet{map: map2})
|
||||
when map_size(map1) < map_size(map2) * 2 do
|
||||
map =
|
||||
map1
|
||||
|> Map.keys()
|
||||
|> filter_not_in(map2, [])
|
||||
|> Map.keys
|
||||
|> filter_not_in(map2)
|
||||
|
||||
%MapSet{map: map}
|
||||
end
|
||||
@@ -166,13 +160,16 @@ defmodule MapSet do
|
||||
%{map_set | map: Map.drop(map1, Map.keys(map2))}
|
||||
end
|
||||
|
||||
defp filter_not_in(keys, map2, acc \\ [])
|
||||
defp filter_not_in([], _map2, acc), do: :maps.from_list(acc)
|
||||
|
||||
defp filter_not_in([key | rest], map2, acc) do
|
||||
case map2 do
|
||||
%{^key => _} -> filter_not_in(rest, map2, acc)
|
||||
_ -> filter_not_in(rest, map2, [{key, @dummy_value} | acc])
|
||||
end
|
||||
acc =
|
||||
if Map.has_key?(map2, key) do
|
||||
acc
|
||||
else
|
||||
[{key, []} | acc]
|
||||
end
|
||||
filter_not_in(rest, map2, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -191,18 +188,17 @@ defmodule MapSet do
|
||||
{map1, map2} = order_by_size(map1, map2)
|
||||
|
||||
map1
|
||||
|> Map.keys()
|
||||
|> Map.keys
|
||||
|> none_in?(map2)
|
||||
end
|
||||
|
||||
defp none_in?([], _) do
|
||||
true
|
||||
end
|
||||
|
||||
defp none_in?([key | rest], map2) do
|
||||
case map2 do
|
||||
%{^key => _} -> false
|
||||
_ -> none_in?(rest, map2)
|
||||
case Map.has_key?(map2, key) do
|
||||
true -> false
|
||||
false -> none_in?(rest, map2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -261,7 +257,7 @@ defmodule MapSet do
|
||||
"""
|
||||
@spec member?(t, value) :: boolean
|
||||
def member?(%MapSet{map: map}, value) do
|
||||
match?(%{^value => _}, map)
|
||||
Map.has_key?(map, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -277,7 +273,7 @@ defmodule MapSet do
|
||||
"""
|
||||
@spec put(t(val), new_val) :: t(val | new_val) when val: value, new_val: value
|
||||
def put(%MapSet{map: map} = map_set, value) do
|
||||
%{map_set | map: Map.put(map, value, @dummy_value)}
|
||||
%{map_set | map: Map.put(map, value, [])}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -311,7 +307,7 @@ defmodule MapSet do
|
||||
def subset?(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
if map_size(map1) <= map_size(map2) do
|
||||
map1
|
||||
|> Map.keys()
|
||||
|> Map.keys
|
||||
|> map_subset?(map2)
|
||||
else
|
||||
false
|
||||
@@ -319,9 +315,12 @@ defmodule MapSet do
|
||||
end
|
||||
|
||||
defp map_subset?([], _), do: true
|
||||
|
||||
defp map_subset?([key | rest], map2) do
|
||||
match?(%{^key => _}, map2) and map_subset?(rest, map2)
|
||||
if Map.has_key?(map2, key) do
|
||||
map_subset?(rest, map2)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -353,43 +352,26 @@ defmodule MapSet do
|
||||
def union(%MapSet{map: map1, version: version} = map_set, %MapSet{map: map2, version: version}) do
|
||||
%{map_set | map: Map.merge(map1, map2)}
|
||||
end
|
||||
|
||||
def union(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
map = new_from_list(Map.keys(map1) ++ Map.keys(map2), [])
|
||||
%MapSet{map: map}
|
||||
new_from_list(Map.keys(map1) ++ Map.keys(map2), [])
|
||||
end
|
||||
|
||||
@compile {:inline, [order_by_size: 2]}
|
||||
defp order_by_size(map1, map2) when map_size(map1) > map_size(map2), do: {map2, map1}
|
||||
defp order_by_size(map1, map2), do: {map1, map2}
|
||||
|
||||
defimpl Enumerable do
|
||||
def count(map_set) do
|
||||
{:ok, MapSet.size(map_set)}
|
||||
end
|
||||
|
||||
def member?(map_set, val) do
|
||||
{:ok, MapSet.member?(map_set, val)}
|
||||
end
|
||||
|
||||
def slice(map_set) do
|
||||
{:ok, MapSet.size(map_set), &Enumerable.List.slice(MapSet.to_list(map_set), &1, &2)}
|
||||
end
|
||||
|
||||
def reduce(map_set, acc, fun) do
|
||||
Enumerable.List.reduce(MapSet.to_list(map_set), acc, fun)
|
||||
end
|
||||
def reduce(map_set, acc, fun), do: Enumerable.List.reduce(MapSet.to_list(map_set), acc, fun)
|
||||
def member?(map_set, val), do: {:ok, MapSet.member?(map_set, val)}
|
||||
def count(map_set), do: {:ok, MapSet.size(map_set)}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def into(map_set) do
|
||||
fun = fn
|
||||
list, {:cont, x} -> [{x, []} | list]
|
||||
list, :done -> %{map_set | map: Map.merge(map_set.map, Map.new(list))}
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map_set, {:cont, x} -> MapSet.put(map_set, x)
|
||||
map_set, :done -> map_set
|
||||
_, :halt -> :ok
|
||||
end
|
||||
|
||||
{[], fun}
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -397,7 +379,7 @@ defmodule MapSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(map_set, opts) do
|
||||
concat(["#MapSet<", Inspect.List.inspect(MapSet.to_list(map_set), opts), ">"])
|
||||
concat ["#MapSet<", Inspect.List.inspect(MapSet.to_list(map_set), opts), ">"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+287
-579
File diff suppressed because it is too large
Load Diff
@@ -4,126 +4,165 @@
|
||||
#
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses ets to track all dependencies
|
||||
# resembling a graph. The graph has the following vertices:
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with one main vertex:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
# * `{:import, name, arity}` - an invoked function/arity import
|
||||
# * `:reattach` - points to reattached functions
|
||||
# * `:local` - points to local functions
|
||||
#
|
||||
# Those vertices can associate to other vertices as described
|
||||
# below:
|
||||
# We can also have the following vertices:
|
||||
#
|
||||
# * `{name, arity}`
|
||||
# * in neighbours: `:reattach`, `{name, arity}`
|
||||
# * out neighbours: `{:import, name, arity}`
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
# * `{:import, name, arity}` - an invoked function/arity import
|
||||
#
|
||||
# * `{:import, name, arity}`
|
||||
# * in neighbours: `{name, arity}`
|
||||
# * out neighbours: `Module`
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{name, arity}`
|
||||
# * in neighbours: `:local`, `{name, arity}`
|
||||
# * out neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{:import, name, arity}`
|
||||
# * in neighbours: `{name, arity}`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer` conveniences.
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: {name, arity}
|
||||
|
||||
@type local :: {name, arity}
|
||||
@type import :: {:import, name, arity}
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Starts the tracker table.
|
||||
Returns all imported modules that had the given
|
||||
`{name, arity}` invoked.
|
||||
"""
|
||||
def init do
|
||||
:ets.new(__MODULE__, [:bag, :public])
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, {name, arity}) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, {:import, name, arity})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the tracker table.
|
||||
Returns all locals that are reachable.
|
||||
|
||||
By default, all public functions are reachable.
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
def delete(d) do
|
||||
:ets.delete(d)
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
ref
|
||||
|> to_pid()
|
||||
|> :gen_server.call(:digraph, @timeout)
|
||||
|> reachable_from(:local)
|
||||
|> :sets.to_list()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds and tracks defaults for a definition into the tracker.
|
||||
"""
|
||||
def add_defaults(d, _kind, {name, arity}, defaults) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
put_edge(d, {name, i}, {name, arity})
|
||||
end
|
||||
|
||||
:ok
|
||||
defp reachable_from(d, starting) do
|
||||
reduce_reachable(d, starting, :sets.new)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a local dispatch from-to the given target.
|
||||
"""
|
||||
def add_local(d, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
put_edge(d, from, to)
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
|
||||
remaining = :sets.subtract(neighbours, vertices)
|
||||
vertices = :sets.union(neighbours, vertices)
|
||||
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an import dispatch to the given target.
|
||||
"""
|
||||
def add_import(d, function, module, {name, arity}) when is_tuple(function) and is_atom(module) do
|
||||
tuple = {:import, name, arity}
|
||||
put_edge(d, tuple, module)
|
||||
put_edge(d, function, tuple)
|
||||
:ok
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
:ets.lookup_element(table, {:elixir, :locals_tracker}, 2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
"""
|
||||
def yank(d, local) do
|
||||
{[], take_out_neighbours(d, local)}
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its PID.
|
||||
@doc false
|
||||
def start_link do
|
||||
:gen_server.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reattach a previously yanked node.
|
||||
"""
|
||||
def reattach(d, tuple, _kind, function, {in_neigh, out_neigh}) do
|
||||
# Reattach the old function
|
||||
for from <- in_neigh do
|
||||
put_edge(d, from, function)
|
||||
end
|
||||
# Adds a definition into the tracker. A public
|
||||
# definition is connected with the :local node
|
||||
# while a private one is left unreachable until
|
||||
# a call is made to.
|
||||
@doc false
|
||||
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, {:add_definition, kind, tuple})
|
||||
end
|
||||
|
||||
for to <- out_neigh do
|
||||
put_edge(d, function, to)
|
||||
end
|
||||
# Adds and tracks defaults for a definition into the tracker.
|
||||
@doc false
|
||||
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, {:add_defaults, kind, tuple, defaults})
|
||||
end
|
||||
|
||||
# Make a call from the old function to the new one
|
||||
if function != tuple do
|
||||
put_edge(d, function, tuple)
|
||||
end
|
||||
# Adds a local dispatch to the given target.
|
||||
def add_local(pid, to) when is_tuple(to) do
|
||||
:gen_server.cast(pid, {:add_local, :local, to})
|
||||
end
|
||||
|
||||
# Finally marked the new one as reattached
|
||||
put_edge(d, :reattach, tuple)
|
||||
:ok
|
||||
# Adds a local dispatch from-to the given target.
|
||||
@doc false
|
||||
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
:gen_server.cast(pid, {:add_local, from, to})
|
||||
end
|
||||
|
||||
# Adds an import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, {:add_import, function, module, target})
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), {:yank, local}, @timeout)
|
||||
end
|
||||
|
||||
# Reattach a previously yanked node
|
||||
@doc false
|
||||
def reattach(pid, tuple, kind, function, neighbours) do
|
||||
:gen_server.cast(to_pid(pid), {:reattach, tuple, kind, function, neighbours})
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@doc false
|
||||
def collect_imports_conflicts(d, all_defined) do
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
for {{name, arity}, _, meta, _} <- all_defined,
|
||||
n = out_neighbours(d, {:import, name, arity}),
|
||||
:digraph.in_neighbours(d, {:import, name, arity}) != [],
|
||||
n = :digraph.out_neighbours(d, {:import, name, arity}),
|
||||
n != [] do
|
||||
{meta, {n, name, arity}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Collect all unused definitions based on the private
|
||||
given, also accounting the expected number of default
|
||||
clauses a private function have.
|
||||
"""
|
||||
def collect_unused_locals(d, all_defined, private) do
|
||||
reachable =
|
||||
Enum.reduce(all_defined, %{}, fn {pair, kind, _, _}, acc ->
|
||||
if kind in [:def, :defmacro] do
|
||||
reachable_from(d, pair, acc)
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
reattached = out_neighbours(d, :reattach)
|
||||
# Collect all unused definitions based on the private
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(ref, private) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reachable = reachable_from(d, :local)
|
||||
reattached = :digraph.out_neighbours(d, :reattach)
|
||||
{unreachable(reachable, reattached, private), collect_warnings(reachable, private)}
|
||||
end
|
||||
|
||||
@@ -136,11 +175,10 @@ defmodule Module.LocalsTracker do
|
||||
defp reachable?(tuple, :defmacrop, reachable, reattached) do
|
||||
# All private micros are unreachable unless they have been
|
||||
# reattached and they are reachable.
|
||||
:lists.member(tuple, reattached) and Map.has_key?(reachable, tuple)
|
||||
:lists.member(tuple, reattached) and :sets.is_element(tuple, reachable)
|
||||
end
|
||||
|
||||
defp reachable?(tuple, :defp, reachable, _reattached) do
|
||||
Map.has_key?(reachable, tuple)
|
||||
:sets.is_element(tuple, reachable)
|
||||
end
|
||||
|
||||
defp collect_warnings(reachable, private) do
|
||||
@@ -152,7 +190,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp collect_warnings({tuple, kind, meta, 0}, acc, reachable) do
|
||||
if Map.has_key?(reachable, tuple) do
|
||||
if :sets.is_element(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{meta, {:unused_def, tuple, kind}} | acc]
|
||||
@@ -173,59 +211,140 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp min_reachable_default(max, min, last, name, reachable) when max >= min do
|
||||
case Map.has_key?(reachable, {name, max}) do
|
||||
case :sets.is_element({name, max}, reachable) do
|
||||
true -> min_reachable_default(max - 1, min, max, name, reachable)
|
||||
false -> min_reachable_default(max - 1, min, last, name, reachable)
|
||||
end
|
||||
end
|
||||
|
||||
defp min_reachable_default(_max, _min, last, _name, _reachable) do
|
||||
last
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all local nodes reachable from `vertex`.
|
||||
|
||||
By default, all public functions are reachable.
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
def reachable_from(d, vertex) do
|
||||
d
|
||||
|> reachable_from(vertex, %{})
|
||||
|> Map.keys()
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
defp reachable_from(d, vertex, vertices) do
|
||||
vertices = Map.put(vertices, vertex, true)
|
||||
# Callbacks
|
||||
|
||||
Enum.reduce(out_neighbours(d, vertex), vertices, fn
|
||||
{_, _} = local, acc ->
|
||||
case acc do
|
||||
%{^local => true} -> acc
|
||||
_ -> reachable_from(d, local, acc)
|
||||
end
|
||||
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :reattach)
|
||||
{:ok, d}
|
||||
end
|
||||
|
||||
## Lightweight digraph implementation
|
||||
|
||||
defp put_edge(d, from, to) do
|
||||
:ets.insert(d, {from, to})
|
||||
def handle_call({:yank, local}, _from, d) do
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_edges(d, :digraph.out_edges(d, local))
|
||||
{:reply, {[], out_vertices}, d}
|
||||
end
|
||||
|
||||
defp out_neighbours(d, from) do
|
||||
try do
|
||||
:ets.lookup_element(d, from, 2)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{:reply, d, d}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, d) do
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({:add_local, from, to}, d) do
|
||||
handle_add_local(d, from, to)
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, function, module, {name, arity}}, d) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({:add_definition, kind, tuple}, d) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, d) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, {name, i})
|
||||
handle_add_local(d, {name, i}, {name, arity})
|
||||
end
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
defp take_out_neighbours(d, from) do
|
||||
Keyword.values(:ets.take(d, from))
|
||||
def handle_cast({:reattach, tuple, kind, function, {in_neigh, out_neigh}}, d) do
|
||||
# Reattach the old function
|
||||
for from <- in_neigh do
|
||||
:digraph.add_vertex(d, from)
|
||||
replace_edge!(d, from, function)
|
||||
end
|
||||
|
||||
for to <- out_neigh do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, function, to)
|
||||
end
|
||||
|
||||
# Add the new definition
|
||||
handle_add_definition(d, kind, tuple)
|
||||
|
||||
# Make a call from the old function to the new one
|
||||
if function != tuple do
|
||||
handle_add_local(d, function, tuple)
|
||||
end
|
||||
|
||||
# Finally marked the new one as reattached
|
||||
replace_edge!(d, :reattach, tuple)
|
||||
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{:stop, :normal, d}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = {:import, name, arity}
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, from, to)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, :local, tuple)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e" | _] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
+30
-18
@@ -16,7 +16,8 @@ defmodule Node do
|
||||
This functionality starts the `:net_kernel` and other
|
||||
related processes.
|
||||
"""
|
||||
@spec start(node, :longnames | :shortnames, non_neg_integer) :: {:ok, pid} | {:error, term}
|
||||
@spec start(node, :longnames | :shortnames, non_neg_integer) ::
|
||||
{:ok, pid} | {:error, term}
|
||||
def start(name, type \\ :longnames, tick_time \\ 15000) do
|
||||
:net_kernel.start([name, type, tick_time])
|
||||
end
|
||||
@@ -71,9 +72,10 @@ defmodule Node do
|
||||
The result returned when the argument is a list, is the list of nodes
|
||||
satisfying the disjunction(s) of the list elements.
|
||||
|
||||
For more information, see `:erlang.nodes/1`.
|
||||
For more information, see
|
||||
[`:erlang.nodes/1`](http://www.erlang.org/doc/man/erlang.html#nodes-1).
|
||||
"""
|
||||
@type state :: :visible | :hidden | :connected | :this | :known
|
||||
@typep state :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(state | [state]) :: [t]
|
||||
def list(args) do
|
||||
:erlang.nodes(args)
|
||||
@@ -85,9 +87,11 @@ defmodule Node do
|
||||
If `flag` is `true`, monitoring is turned on.
|
||||
If `flag` is `false`, monitoring is turned off.
|
||||
|
||||
For more information, see `:erlang.monitor_node/2`.
|
||||
|
||||
For monitoring status changes of all nodes, see `:net_kernel.monitor_nodes/3`.
|
||||
For more information, see
|
||||
[`:erlang.monitor_node/2`](http://www.erlang.org/doc/man/erlang.html#monitor_node-2).
|
||||
|
||||
For monitoring status changes of all nodes, see
|
||||
[`:net_kernel.monitor_nodes/3`](http://www.erlang.org/doc/man/net_kernel.html#monitor_nodes-2).
|
||||
"""
|
||||
@spec monitor(t, boolean) :: true
|
||||
def monitor(node, flag) do
|
||||
@@ -98,9 +102,11 @@ defmodule Node do
|
||||
Behaves as `monitor/2` except that it allows an extra
|
||||
option to be given, namely `:allow_passive_connect`.
|
||||
|
||||
For more information, see `:erlang.monitor_node/3`.
|
||||
|
||||
For monitoring status changes of all nodes, see `:net_kernel.monitor_nodes/3`.
|
||||
For more information, see
|
||||
[`:erlang.monitor_node/3`](http://www.erlang.org/doc/man/erlang.html#monitor_node-3).
|
||||
|
||||
For monitoring status changes of all nodes, see
|
||||
[`:net_kernel.monitor_nodes/3`](http://www.erlang.org/doc/man/net_kernel.html#monitor_nodes-2).
|
||||
"""
|
||||
@spec monitor(t, boolean, [:allow_passive_connect]) :: true
|
||||
def monitor(node, flag, options) do
|
||||
@@ -131,7 +137,8 @@ defmodule Node do
|
||||
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
|
||||
If the local node is not alive, the function returns `:ignored`.
|
||||
|
||||
For more information, see `:erlang.disconnect_node/1`.
|
||||
For more information, see
|
||||
[`:erlang.disconnect_node/1`](http://www.erlang.org/doc/man/erlang.html#disconnect_node-1).
|
||||
"""
|
||||
@spec disconnect(t) :: boolean | :ignored
|
||||
def disconnect(node) do
|
||||
@@ -144,7 +151,8 @@ defmodule Node do
|
||||
Returns `true` if successful, `false` if not, and the atom
|
||||
`:ignored` if the local node is not alive.
|
||||
|
||||
For more information, see `:net_kernel.connect_node/1`.
|
||||
For more information, see
|
||||
[`:net_kernel.connect_node/1`](http://www.erlang.org/doc/man/net_kernel.html#connect_node-1).
|
||||
"""
|
||||
@spec connect(t) :: boolean | :ignored
|
||||
def connect(node) do
|
||||
@@ -155,7 +163,8 @@ defmodule Node do
|
||||
Returns the PID of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless PID is returned.
|
||||
|
||||
For the list of available options, see `:erlang.spawn/2`.
|
||||
For the list of available options, see
|
||||
[`:erlang.spawn/2`](http://www.erlang.org/doc/man/erlang.html#spawn-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -170,11 +179,12 @@ defmodule Node do
|
||||
|
||||
If `node` does not exist, a useless PID is returned.
|
||||
|
||||
For the list of available options, see `:erlang.spawn_opt/3`.
|
||||
For the list of available options, see
|
||||
[`:erlang.spawn_opt/3`](http://www.erlang.org/doc/man/erlang.html#spawn_opt-3).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any), Process.spawn_opts()) :: pid | {pid, reference}
|
||||
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, fun, opts) do
|
||||
:erlang.spawn_opt(node, fun, opts)
|
||||
end
|
||||
@@ -185,7 +195,8 @@ defmodule Node do
|
||||
|
||||
If `node` does not exist, a useless PID is returned.
|
||||
|
||||
For the list of available options, see `:erlang.spawn/4`.
|
||||
For the list of available options, see
|
||||
[`:erlang.spawn/4`](http://www.erlang.org/doc/man/erlang.html#spawn-4).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -200,11 +211,12 @@ defmodule Node do
|
||||
|
||||
If `node` does not exist, a useless PID is returned.
|
||||
|
||||
For the list of available options, see `:erlang.spawn/5`.
|
||||
For the list of available options, see
|
||||
[`:erlang.spawn/5`](http://www.erlang.org/doc/man/erlang.html#spawn_opt-5).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any], Process.spawn_opts()) :: pid | {pid, reference}
|
||||
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, module, fun, args, opts) do
|
||||
:erlang.spawn_opt(node, module, fun, args, opts)
|
||||
end
|
||||
@@ -246,7 +258,7 @@ defmodule Node do
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
def set_cookie(node \\ Node.self(), cookie) when is_atom(cookie) do
|
||||
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ defmodule OptionParser do
|
||||
This module contains functions to parse command line options.
|
||||
"""
|
||||
|
||||
@type argv :: [String.t()]
|
||||
@type parsed :: keyword
|
||||
@type errors :: [{String.t(), String.t() | nil}]
|
||||
@type argv :: [String.t]
|
||||
@type parsed :: keyword
|
||||
@type errors :: [{String.t, String.t | nil}]
|
||||
@type options :: [switches: keyword, strict: keyword, aliases: keyword]
|
||||
|
||||
defmodule ParseError do
|
||||
@@ -119,6 +119,7 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{[debug: true], [], []}
|
||||
|
||||
|
||||
Switches followed by a value will be assigned the value, as a string. Switches
|
||||
without an argument, like `--debug` in the examples above, will automatically be
|
||||
set to `true`.
|
||||
@@ -296,7 +297,7 @@ defmodule OptionParser do
|
||||
case next_with_config(argv, config) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exists and it was successfully parsed
|
||||
kinds = List.wrap(Keyword.get(switches, option))
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
new_opts = store_option(opts, option, value, kinds)
|
||||
do_parse(rest, config, new_opts, args, invalid, all?)
|
||||
|
||||
@@ -341,10 +342,10 @@ defmodule OptionParser do
|
||||
|
||||
"""
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv}
|
||||
| {:invalid, String.t(), String.t() | nil, argv}
|
||||
| {:undefined, String.t(), String.t() | nil, argv}
|
||||
| {:error, argv}
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, String.t, String.t | nil, argv} |
|
||||
{:undefined, String.t, String.t | nil, argv} |
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
next_with_config(argv, build_config(opts))
|
||||
@@ -382,21 +383,17 @@ defmodule OptionParser do
|
||||
cond do
|
||||
is_nil(value) and negative_number?(original) ->
|
||||
{:error, argv}
|
||||
|
||||
String.contains?(option, ["-", "_"]) ->
|
||||
{:undefined, original, value, rest}
|
||||
|
||||
String.length(option) > 1 ->
|
||||
key = get_option_key(option, allow_nonexistent_atoms?)
|
||||
option_key = aliases[key]
|
||||
|
||||
if key && option_key do
|
||||
IO.warn("multi-letter aliases are deprecated, got: #{inspect(key)}")
|
||||
IO.warn "multi-letter aliases are deprecated, got: #{inspect(key)}"
|
||||
next_tagged({:default, option_key}, value, original, rest, config)
|
||||
else
|
||||
next_with_config(expand_multiletter_alias(option, value) ++ rest, config)
|
||||
end
|
||||
|
||||
true ->
|
||||
# We have a regular one-letter alias here
|
||||
tagged = tag_oneletter_alias(option, config)
|
||||
@@ -414,10 +411,9 @@ defmodule OptionParser do
|
||||
else
|
||||
{option, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict?)
|
||||
|
||||
case validate_option(value, kinds) do
|
||||
{:ok, new_value} -> {:ok, option, new_value, rest}
|
||||
:invalid -> {:invalid, original, value, rest}
|
||||
:invalid -> {:invalid, original, value, rest}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -450,13 +446,12 @@ defmodule OptionParser do
|
||||
["--number", "--number"]
|
||||
|
||||
"""
|
||||
@spec to_argv(Enumerable.t(), options) :: argv
|
||||
@spec to_argv(Enumerable.t, options) :: argv
|
||||
def to_argv(enum, opts \\ []) do
|
||||
switches = Keyword.get(opts, :switches, [])
|
||||
|
||||
Enum.flat_map(enum, fn
|
||||
{_key, nil} -> []
|
||||
{key, true} -> [to_switch(key)]
|
||||
{_key, nil} -> []
|
||||
{key, true} -> [to_switch(key)]
|
||||
{key, false} -> [to_switch(key, "--no-")]
|
||||
{key, value} -> to_argv(key, value, switches)
|
||||
end)
|
||||
@@ -489,7 +484,7 @@ defmodule OptionParser do
|
||||
["foo", "bar baz"]
|
||||
|
||||
"""
|
||||
@spec split(String.t()) :: argv
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) when is_binary(string) do
|
||||
do_split(String.trim_leading(string, " "), "", [], nil)
|
||||
end
|
||||
@@ -503,7 +498,8 @@ defmodule OptionParser do
|
||||
do: do_split(t, buffer, acc, quote)
|
||||
|
||||
# If we have a quote and we were inside it, close it
|
||||
defp do_split(<<quote, t::binary>>, buffer, acc, quote), do: do_split(t, buffer, acc, nil)
|
||||
defp do_split(<<quote, t::binary>>, buffer, acc, quote),
|
||||
do: do_split(t, buffer, acc, nil)
|
||||
|
||||
# If we have an escaped quote/space, simply remove the escape as long as we are not inside a quote
|
||||
defp do_split(<<?\\, h, t::binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
|
||||
@@ -519,9 +515,11 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
# Finish the string expecting a nil marker
|
||||
defp do_split(<<>>, "", acc, nil), do: Enum.reverse(acc)
|
||||
defp do_split(<<>>, "", acc, nil),
|
||||
do: Enum.reverse(acc)
|
||||
|
||||
defp do_split(<<>>, buffer, acc, nil), do: Enum.reverse([buffer | acc])
|
||||
defp do_split(<<>>, buffer, acc, nil),
|
||||
do: Enum.reverse([buffer | acc])
|
||||
|
||||
# Otherwise raise
|
||||
defp do_split(<<>>, _, _acc, marker) do
|
||||
@@ -531,20 +529,16 @@ defmodule OptionParser do
|
||||
## Helpers
|
||||
|
||||
defp build_config(opts) do
|
||||
{switches, strict?} =
|
||||
cond do
|
||||
opts[:switches] && opts[:strict] ->
|
||||
raise ArgumentError, ":switches and :strict cannot be given together"
|
||||
|
||||
switches = opts[:switches] ->
|
||||
{switches, false}
|
||||
|
||||
strict = opts[:strict] ->
|
||||
{strict, true}
|
||||
|
||||
true ->
|
||||
{[], false}
|
||||
end
|
||||
{switches, strict?} = cond do
|
||||
opts[:switches] && opts[:strict] ->
|
||||
raise ArgumentError, ":switches and :strict cannot be given together"
|
||||
switches = opts[:switches] ->
|
||||
{switches, false}
|
||||
strict = opts[:strict] ->
|
||||
{strict, true}
|
||||
true ->
|
||||
{[], false}
|
||||
end
|
||||
|
||||
%{
|
||||
aliases: opts[:aliases] || [],
|
||||
@@ -559,32 +553,27 @@ defmodule OptionParser do
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{true, value}
|
||||
|
||||
:boolean in kinds ->
|
||||
case value do
|
||||
t when t in [true, "true"] -> {false, true}
|
||||
f when f in [false, "false"] -> {false, false}
|
||||
_ -> {true, value}
|
||||
end
|
||||
|
||||
:count in kinds ->
|
||||
case value do
|
||||
1 -> {false, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {false, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {false, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
|
||||
true ->
|
||||
{false, value}
|
||||
end
|
||||
@@ -599,28 +588,20 @@ defmodule OptionParser do
|
||||
defp store_option(dict, option, value, kinds) do
|
||||
cond do
|
||||
:count in kinds ->
|
||||
Keyword.update(dict, option, value, &(&1 + 1))
|
||||
|
||||
Keyword.update(dict, option, value, & &1 + 1)
|
||||
:keep in kinds ->
|
||||
[{option, value} | dict]
|
||||
|
||||
true ->
|
||||
[{option, value} | Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option("no-" <> option = original, %{
|
||||
switches: switches,
|
||||
allow_nonexistent_atoms?: allow_nonexistent_atoms?
|
||||
}) do
|
||||
defp tag_option("no-" <> option = original, %{switches: switches, allow_nonexistent_atoms?: allow_nonexistent_atoms?}) do
|
||||
cond do
|
||||
(negated = get_option_key(option, allow_nonexistent_atoms?)) &&
|
||||
:boolean in List.wrap(switches[negated]) ->
|
||||
(negated = get_option_key(option, allow_nonexistent_atoms?)) && :boolean in List.wrap(switches[negated]) ->
|
||||
{:negated, negated}
|
||||
|
||||
option_key = get_option_key(original, allow_nonexistent_atoms?) ->
|
||||
{:default, option_key}
|
||||
|
||||
true ->
|
||||
:unknown
|
||||
end
|
||||
@@ -634,11 +615,7 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_oneletter_alias(alias, %{
|
||||
aliases: aliases,
|
||||
allow_nonexistent_atoms?: allow_nonexistent_atoms?
|
||||
})
|
||||
when is_binary(alias) do
|
||||
defp tag_oneletter_alias(alias, %{aliases: aliases, allow_nonexistent_atoms?: allow_nonexistent_atoms?}) when is_binary(alias) do
|
||||
if option_key = aliases[to_existing_key(alias, allow_nonexistent_atoms?)] do
|
||||
{:default, option_key}
|
||||
else
|
||||
@@ -652,7 +629,6 @@ defmodule OptionParser do
|
||||
|> String.codepoints()
|
||||
|> Enum.map(&("-" <> &1))
|
||||
|> List.pop_at(-1)
|
||||
|
||||
expanded ++ [last <> if(value, do: "=" <> value, else: "")]
|
||||
end
|
||||
|
||||
@@ -688,20 +664,15 @@ defmodule OptionParser do
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
|
||||
:count in kinds ->
|
||||
{1, kinds, t}
|
||||
|
||||
value_in_tail?(t) ->
|
||||
[h | t] = t
|
||||
{h, kinds, t}
|
||||
|
||||
kinds == [] and strict? ->
|
||||
{nil, kinds, t}
|
||||
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
|
||||
true ->
|
||||
{nil, [:invalid], t}
|
||||
end
|
||||
@@ -711,24 +682,29 @@ defmodule OptionParser do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
defp value_in_tail?(["-" | _]), do: true
|
||||
defp value_in_tail?(["- " <> _ | _]), do: true
|
||||
defp value_in_tail?(["-" | _]), do: true
|
||||
defp value_in_tail?(["- " <> _ | _]), do: true
|
||||
defp value_in_tail?(["-" <> arg | _]), do: negative_number?("-" <> arg)
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> {h, nil}
|
||||
[h] -> {h, nil}
|
||||
[h, t] -> {h, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option), do: to_underscore(option, <<>>)
|
||||
defp to_underscore("_" <> _rest, _acc), do: nil
|
||||
defp to_underscore("-" <> rest, acc), do: to_underscore(rest, acc <> "_")
|
||||
defp to_underscore(<<c>> <> rest, acc), do: to_underscore(rest, <<acc::binary, c>>)
|
||||
defp to_underscore(<<>>, acc), do: acc
|
||||
defp to_underscore(option),
|
||||
do: to_underscore(option, <<>>)
|
||||
defp to_underscore("_" <> _rest, _acc),
|
||||
do: nil
|
||||
defp to_underscore("-" <> rest, acc),
|
||||
do: to_underscore(rest, acc <> "_")
|
||||
defp to_underscore(<<c>> <> rest, acc),
|
||||
do: to_underscore(rest, <<acc::binary, c>>)
|
||||
defp to_underscore(<<>>, acc),
|
||||
do: acc
|
||||
|
||||
defp get_option_key(option, allow_nonexistent_atoms?) do
|
||||
if string = to_underscore(option) do
|
||||
@@ -736,8 +712,8 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp to_existing_key(option, true), do: String.to_atom(option)
|
||||
|
||||
defp to_existing_key(option, true),
|
||||
do: String.to_atom(option)
|
||||
defp to_existing_key(option, false) do
|
||||
try do
|
||||
String.to_existing_atom(option)
|
||||
@@ -754,7 +730,6 @@ defmodule OptionParser do
|
||||
types = opts[:switches] || opts[:strict]
|
||||
error_count = length(errors)
|
||||
error = if error_count == 1, do: "error", else: "errors"
|
||||
|
||||
"#{error_count} #{error} found!\n" <>
|
||||
Enum.map_join(errors, "\n", &format_error(&1, opts, types))
|
||||
end
|
||||
@@ -769,7 +744,7 @@ defmodule OptionParser do
|
||||
|
||||
defp format_error({option, value}, opts, types) do
|
||||
type = get_type(option, opts, types)
|
||||
"#{option} : Expected type #{type}, got #{inspect(value)}"
|
||||
"#{option} : Expected type #{type}, got #{inspect value}"
|
||||
end
|
||||
|
||||
defp get_type(option, opts, types) do
|
||||
|
||||
+93
-111
@@ -38,7 +38,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec absname(t) :: binary
|
||||
def absname(path) do
|
||||
absname(path, System.cwd!())
|
||||
absname(path, System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -62,14 +62,9 @@ defmodule Path do
|
||||
@spec absname(t, t) :: binary
|
||||
def absname(path, relative_to) do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
case type(path) do
|
||||
:relative ->
|
||||
absname_join(relative_to, path)
|
||||
|
||||
:absolute ->
|
||||
absname_join([path])
|
||||
|
||||
:relative -> absname_join(relative_to, path)
|
||||
:absolute -> absname_join([path])
|
||||
:volumerelative ->
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
@@ -77,7 +72,8 @@ defmodule Path do
|
||||
end
|
||||
|
||||
# Absolute path on current drive
|
||||
defp absname_vr(["/" | rest], [volume | _], _relative), do: absname_join([volume | rest])
|
||||
defp absname_vr(["/" | rest], [volume | _], _relative),
|
||||
do: absname_join([volume | rest])
|
||||
|
||||
# Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>> | rest], [<<x, _::binary>> | _], relative),
|
||||
@@ -87,56 +83,49 @@ defmodule Path do
|
||||
defp absname_vr([<<x, ?:>> | name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
|
||||
absname(absname_join(name), cwd)
|
||||
end
|
||||
|
||||
# Joins a list
|
||||
defp absname_join([name1, name2 | rest]), do: absname_join([absname_join(name1, name2) | rest])
|
||||
|
||||
defp absname_join([name]),
|
||||
do: do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
defp absname_join([name1, name2 | rest]), do:
|
||||
absname_join([absname_join(name1, name2) | rest])
|
||||
defp absname_join([name]), do:
|
||||
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
# Joins two paths
|
||||
defp absname_join(left, right),
|
||||
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32)
|
||||
when uc_letter in ?A..?Z do
|
||||
defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_absname_join(rest, relativename, [?:, uc_letter + ?a - ?A], :win32)
|
||||
end
|
||||
defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32), do:
|
||||
do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/ | result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/ | result], os_type)
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?/ | result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/ | result], os_type)
|
||||
defp do_absname_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(reverse_maybe_remove_dir_sep(result, os_type))
|
||||
defp do_absname_join(<<>>, relativename, [?: | rest], :win32), do:
|
||||
do_absname_join(relativename, <<>>, [?: | rest], :win32)
|
||||
defp do_absname_join(<<>>, relativename, [?/ | result], os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/ | result], os_type)
|
||||
defp do_absname_join(<<>>, relativename, result, os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/ | result], os_type)
|
||||
defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type), do:
|
||||
do_absname_join(rest, relativename, [char | result], os_type)
|
||||
|
||||
defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32),
|
||||
do: do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)
|
||||
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/ | result], os_type),
|
||||
do: do_absname_join(rest, relativename, [?/ | result], os_type)
|
||||
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?/ | result], os_type),
|
||||
do: do_absname_join(rest, relativename, [?/ | result], os_type)
|
||||
|
||||
defp do_absname_join(<<>>, <<>>, result, os_type),
|
||||
do: IO.iodata_to_binary(reverse_maybe_remove_dir_sep(result, os_type))
|
||||
|
||||
defp do_absname_join(<<>>, relativename, [?: | rest], :win32),
|
||||
do: do_absname_join(relativename, <<>>, [?: | rest], :win32)
|
||||
|
||||
defp do_absname_join(<<>>, relativename, [?/ | result], os_type),
|
||||
do: do_absname_join(relativename, <<>>, [?/ | result], os_type)
|
||||
|
||||
defp do_absname_join(<<>>, relativename, result, os_type),
|
||||
do: do_absname_join(relativename, <<>>, [?/ | result], os_type)
|
||||
|
||||
defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type),
|
||||
do: do_absname_join(rest, relativename, [char | result], os_type)
|
||||
|
||||
defp reverse_maybe_remove_dir_sep([?/, ?:, letter], :win32), do: [letter, ?:, ?/]
|
||||
defp reverse_maybe_remove_dir_sep([?/], _), do: [?/]
|
||||
defp reverse_maybe_remove_dir_sep([?/ | name], _), do: :lists.reverse(name)
|
||||
defp reverse_maybe_remove_dir_sep(name, _), do: :lists.reverse(name)
|
||||
defp reverse_maybe_remove_dir_sep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp reverse_maybe_remove_dir_sep([?/], _), do:
|
||||
[?/]
|
||||
defp reverse_maybe_remove_dir_sep([?/ | name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp reverse_maybe_remove_dir_sep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Converts the path to an absolute one and expands
|
||||
@@ -150,7 +139,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
expand_dot(absname(expand_home(path), System.cwd!()))
|
||||
expand_dot absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -178,7 +167,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
expand_dot(absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!()))
|
||||
expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,53 +225,54 @@ defmodule Path do
|
||||
defp relative(name, os_type) do
|
||||
pathtype(name, os_type)
|
||||
|> elem(1)
|
||||
|> IO.chardata_to_string()
|
||||
|> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp pathtype(name, os_type) do
|
||||
case os_type do
|
||||
:win32 -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end
|
||||
end
|
||||
|
||||
defp unix_pathtype(path) when path in ["/", '/'], do: {:absolute, "."}
|
||||
defp unix_pathtype(<<?/, relative::binary>>), do: {:absolute, relative}
|
||||
defp unix_pathtype([?/ | relative]), do: {:absolute, relative}
|
||||
defp unix_pathtype([list | rest]) when is_list(list), do: unix_pathtype(list ++ rest)
|
||||
defp unix_pathtype(relative), do: {:relative, relative}
|
||||
defp unix_pathtype(path) when path in ["/", '/'],
|
||||
do: {:absolute, "."}
|
||||
defp unix_pathtype(<<?/, relative::binary>>),
|
||||
do: {:absolute, relative}
|
||||
defp unix_pathtype([?/ | relative]),
|
||||
do: {:absolute, relative}
|
||||
defp unix_pathtype([list | rest]) when is_list(list),
|
||||
do: unix_pathtype(list ++ rest)
|
||||
defp unix_pathtype(relative),
|
||||
do: {:relative, relative}
|
||||
|
||||
@slash [?/, ?\\]
|
||||
|
||||
defp win32_pathtype([list | rest]) when is_list(list), do: win32_pathtype(list ++ rest)
|
||||
|
||||
defp win32_pathtype([list | rest]) when is_list(list),
|
||||
do: win32_pathtype(list ++ rest)
|
||||
defp win32_pathtype([char, list | rest]) when is_list(list),
|
||||
do: win32_pathtype([char | list ++ rest])
|
||||
|
||||
defp win32_pathtype(<<c1, c2, relative::binary>>) when c1 in @slash and c2 in @slash,
|
||||
do: {:absolute, relative}
|
||||
|
||||
defp win32_pathtype(<<char, relative::binary>>) when char in @slash,
|
||||
do: {:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype(<<_letter, ?:, char, relative::binary>>) when char in @slash,
|
||||
do: {:absolute, relative}
|
||||
|
||||
defp win32_pathtype(<<_letter, ?:, relative::binary>>), do: {:volumerelative, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, relative::binary>>),
|
||||
do: {:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash,
|
||||
do: {:absolute, relative}
|
||||
|
||||
defp win32_pathtype([char | relative]) when char in @slash, do: {:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype([char | relative]) when char in @slash,
|
||||
do: {:volumerelative, relative}
|
||||
defp win32_pathtype([c1, c2, list | rest]) when is_list(list),
|
||||
do: win32_pathtype([c1, c2 | list ++ rest])
|
||||
|
||||
defp win32_pathtype([_letter, ?:, char | relative]) when char in @slash,
|
||||
do: {:absolute, relative}
|
||||
|
||||
defp win32_pathtype([_letter, ?: | relative]), do: {:volumerelative, relative}
|
||||
defp win32_pathtype(relative), do: {:relative, relative}
|
||||
defp win32_pathtype([_letter, ?: | relative]),
|
||||
do: {:volumerelative, relative}
|
||||
defp win32_pathtype(relative),
|
||||
do: {:relative, relative}
|
||||
|
||||
@doc """
|
||||
Returns the given `path` relative to the given `from` path.
|
||||
@@ -334,7 +324,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative_to_cwd(t) :: binary
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd() do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
|
||||
_ -> path
|
||||
end
|
||||
@@ -478,8 +468,10 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec join(nonempty_list(t)) :: binary
|
||||
def join([name1, name2 | rest]), do: join([join(name1, name2) | rest])
|
||||
def join([name]), do: IO.chardata_to_string(name)
|
||||
def join([name1, name2 | rest]), do:
|
||||
join([join(name1, name2) | rest])
|
||||
def join([name]), do:
|
||||
IO.chardata_to_string(name)
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
@@ -495,35 +487,22 @@ defmodule Path do
|
||||
iex> Path.join("/foo", "/bar/")
|
||||
"/foo/bar"
|
||||
|
||||
The functions in this module support chardata, so giving a list will
|
||||
treat it as a single entity:
|
||||
|
||||
iex> Path.join("foo", ["bar", "fiz"])
|
||||
"foo/barfiz"
|
||||
|
||||
iex> Path.join(["foo", "bar"], "fiz")
|
||||
"foobar/fiz"
|
||||
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right) do
|
||||
left = IO.chardata_to_string(left)
|
||||
left = IO.chardata_to_string(left)
|
||||
os_type = major_os_type()
|
||||
do_join(left, right, os_type) |> remove_dir_sep(os_type)
|
||||
end
|
||||
|
||||
defp do_join("", right, os_type), do: relative(right, os_type)
|
||||
defp do_join("/", right, os_type), do: "/" <> relative(right, os_type)
|
||||
|
||||
defp do_join(left, right, os_type),
|
||||
do: remove_dir_sep(left, os_type) <> "/" <> relative(right, os_type)
|
||||
defp do_join("", right, os_type), do: relative(right, os_type)
|
||||
defp do_join("/", right, os_type), do: "/" <> relative(right, os_type)
|
||||
defp do_join(left, right, os_type), do: remove_dir_sep(left, os_type) <> "/" <> relative(right, os_type)
|
||||
|
||||
defp remove_dir_sep("", _os_type), do: ""
|
||||
defp remove_dir_sep("/", _os_type), do: "/"
|
||||
|
||||
defp remove_dir_sep(bin, os_type) do
|
||||
last = :binary.last(bin)
|
||||
|
||||
if last == ?/ or (last == ?\\ and os_type == :win32) do
|
||||
binary_part(bin, 0, byte_size(bin) - 1)
|
||||
else
|
||||
@@ -572,7 +551,6 @@ defmodule Path do
|
||||
case call({:list_dir, dir}) do
|
||||
{:ok, files} ->
|
||||
{:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
@@ -636,7 +614,6 @@ defmodule Path do
|
||||
@spec wildcard(t, keyword) :: [binary]
|
||||
def wildcard(glob, opts \\ []) do
|
||||
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
|
||||
|
||||
glob
|
||||
|> chardata_to_list!()
|
||||
|> :filelib.wildcard(mod)
|
||||
@@ -647,8 +624,7 @@ defmodule Path do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
result when is_list(result) ->
|
||||
if 0 in result do
|
||||
raise ArgumentError,
|
||||
"cannot execute Path.wildcard/2 for path with null byte, got: #{inspect(chardata)}"
|
||||
raise ArgumentError, "cannot execute Path.wildcard/2 for path with null byte, got: #{inspect chardata}"
|
||||
else
|
||||
result
|
||||
end
|
||||
@@ -664,42 +640,48 @@ defmodule Path do
|
||||
defp expand_home(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> resolve_home(rest)
|
||||
rest -> rest
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp resolve_home(""), do: System.user_home!()
|
||||
defp resolve_home(""), do: System.user_home!
|
||||
|
||||
defp resolve_home(rest) do
|
||||
case {rest, major_os_type()} do
|
||||
{"\\" <> _, :win32} ->
|
||||
System.user_home!() <> rest
|
||||
|
||||
System.user_home! <> rest
|
||||
{"/" <> _, _} ->
|
||||
System.user_home!() <> rest
|
||||
|
||||
_ ->
|
||||
rest
|
||||
System.user_home! <> rest
|
||||
_ -> rest
|
||||
end
|
||||
end
|
||||
|
||||
# expand_dot the given path by expanding "..", "." and "~".
|
||||
defp expand_dot(<<"/", rest::binary>>), do: "/" <> do_expand_dot(rest)
|
||||
|
||||
defp expand_dot(<<"/", rest::binary>>),
|
||||
do: "/" <> do_expand_dot(rest)
|
||||
defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
|
||||
do: <<letter, ":/">> <> do_expand_dot(rest)
|
||||
defp expand_dot(path),
|
||||
do: do_expand_dot(path)
|
||||
|
||||
defp expand_dot(path), do: do_expand_dot(path)
|
||||
defp do_expand_dot(path),
|
||||
do: do_expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
|
||||
defp do_expand_dot(path), do: do_expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
defp do_expand_dot([".." | t], [_, _ | acc]), do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([".." | t], []), do: do_expand_dot(t, [])
|
||||
defp do_expand_dot(["." | t], acc), do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([h | t], acc), do: do_expand_dot(t, ["/", h | acc])
|
||||
defp do_expand_dot([], []), do: ""
|
||||
defp do_expand_dot([], ["/" | acc]), do: IO.iodata_to_binary(:lists.reverse(acc))
|
||||
defp do_expand_dot([".." | t], [_, _ | acc]),
|
||||
do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([".." | t], []),
|
||||
do: do_expand_dot(t, [])
|
||||
defp do_expand_dot(["." | t], acc),
|
||||
do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([h | t], acc),
|
||||
do: do_expand_dot(t, ["/", h | acc])
|
||||
defp do_expand_dot([], []),
|
||||
do: ""
|
||||
defp do_expand_dot([], ["/" | acc]),
|
||||
do: IO.iodata_to_binary(:lists.reverse(acc))
|
||||
|
||||
defp major_os_type do
|
||||
:os.type() |> elem(0)
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
end
|
||||
|
||||
+27
-56
@@ -84,16 +84,16 @@ defmodule Port do
|
||||
The `:spawn` tuple receives a binary that is going to be executed as a
|
||||
full invocation. For example, we can use it to invoke "echo hello" directly:
|
||||
|
||||
iex> port = Port.open({:spawn, "echo hello"}, [:binary])
|
||||
iex> port = Port.open({:spawn, "echo oops"}, [:binary])
|
||||
iex> flush()
|
||||
{#Port<0.1444>, {:data, "hello\n"}}
|
||||
{#Port<0.1444>, {:data, "oops\n"}}
|
||||
|
||||
`:spawn` will retrieve the program name from the argument and traverse your
|
||||
OS `$PATH` environment variable looking for a matching program.
|
||||
|
||||
Although the above is handy, it means it is impossible to invoke an executable
|
||||
that has whitespaces on its name or in any of its arguments. For those reasons,
|
||||
most times it is preferable to execute `:spawn_executable`.
|
||||
most times it is preferrable to execute `:spawn_executable`.
|
||||
|
||||
### spawn_executable
|
||||
|
||||
@@ -110,6 +110,14 @@ defmodule Port do
|
||||
the `:args` option as done above. For the full list of options, see the
|
||||
documentation for the Erlang function `:erlang.open_port/2`.
|
||||
|
||||
### spawn_driver
|
||||
|
||||
Spawn driver is used to start Port Drivers, which are programs written in
|
||||
C that implements a specific communication protocols and are dynamically
|
||||
linked to the Erlang VM. Port drivers are an advanced topic and one of the
|
||||
mechanisms for integrating C code, alongside NIFs. For more information,
|
||||
[please check the Erlang docs](http://erlang.org/doc/reference_manual/ports.html).
|
||||
|
||||
### fd
|
||||
|
||||
The `:fd` name option allows developers to access `in` and `out` file
|
||||
@@ -139,6 +147,7 @@ defmodule Port do
|
||||
done
|
||||
kill -KILL $pid
|
||||
|
||||
|
||||
Now instead of:
|
||||
|
||||
Port.open({:spawn_executable, "/path/to/program"},
|
||||
@@ -151,11 +160,10 @@ defmodule Port do
|
||||
|
||||
"""
|
||||
|
||||
@type name ::
|
||||
{:spawn, charlist | binary}
|
||||
| {:spawn_driver, charlist | binary}
|
||||
| {:spawn_executable, charlist | atom}
|
||||
| {:fd, non_neg_integer, non_neg_integer}
|
||||
@type name :: {:spawn, charlist | binary} |
|
||||
{:spawn_driver, charlist | binary} |
|
||||
{:spawn_executable, charlist | atom} |
|
||||
{:fd, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
Opens a port given a tuple `name` and a list of `options`.
|
||||
@@ -172,7 +180,8 @@ defmodule Port do
|
||||
* `{:fd, fd_in, fd_out}` - accesses file descriptors, `fd_in` and `fd_out`
|
||||
opened by the VM.
|
||||
|
||||
For more information and the list of options, see `:erlang.open_port/2`.
|
||||
For more information and the list of options, see
|
||||
[`:erlang.open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -184,7 +193,7 @@ defmodule Port do
|
||||
@doc """
|
||||
Closes the `port`.
|
||||
|
||||
For more information, see `:erlang.port_close/1`.
|
||||
For more information, see [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -196,7 +205,7 @@ defmodule Port do
|
||||
@doc """
|
||||
Sends `data` to the port driver `port`.
|
||||
|
||||
For more information, see `:erlang.port_command/2`.
|
||||
For more information, see [`:erlang.port_command/2`](http://www.erlang.org/doc/man/erlang.html#port_command-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -208,7 +217,7 @@ defmodule Port do
|
||||
@doc """
|
||||
Associates the `port` identifier with a `pid`.
|
||||
|
||||
For more information, see `:erlang.port_connect/2`.
|
||||
For more information, see [`:erlang.port_connect/2`](http://www.erlang.org/doc/man/erlang.html#port_connect-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -220,16 +229,16 @@ defmodule Port do
|
||||
@doc """
|
||||
Returns information about the `port` or `nil` if the port is closed.
|
||||
|
||||
For more information, see `:erlang.port_info/1`.
|
||||
For more information, see [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1).
|
||||
"""
|
||||
def info(port) do
|
||||
nillify(:erlang.port_info(port))
|
||||
nillify :erlang.port_info(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the `port` or `nil` if the port is closed.
|
||||
|
||||
For more information, see `:erlang.port_info/2`.
|
||||
For more information, see [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2).
|
||||
"""
|
||||
@spec info(port, atom) :: {atom, term} | nil
|
||||
def info(port, spec)
|
||||
@@ -242,47 +251,9 @@ defmodule Port do
|
||||
end
|
||||
|
||||
def info(port, item) do
|
||||
nillify(:erlang.port_info(port, item))
|
||||
nillify :erlang.port_info(port, item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts monitoring the given `port` from the calling process.
|
||||
|
||||
Once the monitored port process dies, a message is delivered to the
|
||||
monitoring process in the shape of:
|
||||
|
||||
{:DOWN, ref, :port, object, reason}
|
||||
|
||||
where:
|
||||
|
||||
* `ref` is a monitor reference returned by this function;
|
||||
* `object` is either the `port` being monitored (when monitoring by port id)
|
||||
or `{name, node}` (when monitoring by a port name);
|
||||
* `reason` is the exit reason.
|
||||
|
||||
See `:erlang.monitor/2` for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monitor(port | {name :: atom, node :: atom} | name :: atom) :: reference
|
||||
def monitor(port) do
|
||||
:erlang.monitor(:port, port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Demonitors the monitor identified by the given `reference`.
|
||||
|
||||
If `monitor_ref` is a reference which the calling process
|
||||
obtained by calling `monitor/1`, that monitoring is turned off.
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See `:erlang.demonitor/2` for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
|
||||
defdelegate demonitor(monitor_ref, options \\ []), to: :erlang
|
||||
|
||||
@doc """
|
||||
Returns a list of all ports in the current node.
|
||||
|
||||
@@ -290,10 +261,10 @@ defmodule Port do
|
||||
"""
|
||||
@spec list :: [port]
|
||||
def list do
|
||||
:erlang.ports()
|
||||
:erlang.ports
|
||||
end
|
||||
|
||||
@compile {:inline, nillify: 1}
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
+48
-54
@@ -12,11 +12,6 @@ defmodule Process do
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
|
||||
While this module provides low-level conveniences to work with processes,
|
||||
developers typically use abstractions such as `Agent`, `GenServer`,
|
||||
`Registry`, `Supervisor` and `Task` for building their systems and
|
||||
resort to this module for gathering information, trapping exits, links
|
||||
and monitoring.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -48,8 +43,10 @@ defmodule Process do
|
||||
@spec get(term, default :: term) :: term
|
||||
def get(key, default \\ nil) do
|
||||
case :erlang.get(key) do
|
||||
:undefined -> default
|
||||
value -> value
|
||||
:undefined ->
|
||||
default
|
||||
value ->
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,7 +83,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec put(term, term) :: term | nil
|
||||
def put(key, value) do
|
||||
nillify(:erlang.put(key, value))
|
||||
nillify :erlang.put(key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -106,7 +103,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec delete(term) :: term | nil
|
||||
def delete(key) do
|
||||
nillify(:erlang.erase(key))
|
||||
nillify :erlang.erase(key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -337,13 +334,10 @@ defmodule Process do
|
||||
@spec read_timer(reference) :: non_neg_integer | false
|
||||
defdelegate read_timer(timer_ref), to: :erlang
|
||||
|
||||
@type spawn_opt ::
|
||||
:link
|
||||
| :monitor
|
||||
| {:priority, :low | :normal | :high}
|
||||
| {:fullsweep_after, non_neg_integer}
|
||||
| {:min_heap_size, non_neg_integer}
|
||||
| {:min_bin_vheap_size, non_neg_integer}
|
||||
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
|
||||
{:fullsweep_after, non_neg_integer} |
|
||||
{:min_heap_size, non_neg_integer} |
|
||||
{:min_bin_vheap_size, non_neg_integer}
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
@doc """
|
||||
@@ -355,7 +349,7 @@ defmodule Process do
|
||||
just the spawned process PID.
|
||||
|
||||
More options are available; for the comprehensive list of available options
|
||||
check `:erlang.spawn_opt/4`.
|
||||
check [`:erlang.spawn_opt/4`](http://www.erlang.org/doc/man/erlang.html#spawn_opt-4).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -372,7 +366,7 @@ defmodule Process do
|
||||
just the spawned process PID.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check `:erlang.spawn_opt/4`.
|
||||
check [`:erlang.spawn_opt/4`](http://www.erlang.org/doc/man/erlang.html#spawn_opt-4).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -395,23 +389,24 @@ defmodule Process do
|
||||
* `reason` is the exit reason.
|
||||
|
||||
See [the need for monitoring](http://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
|
||||
for an example. See `:erlang.monitor/2` for more info.
|
||||
for an example.
|
||||
See [`:erlang.monitor/2`](http://www.erlang.org/doc/man/erlang.html#monitor-2) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monitor(pid | {name :: atom, node :: atom} | name :: atom) :: reference
|
||||
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
|
||||
def monitor(item) do
|
||||
:erlang.monitor(:process, item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Demonitors the monitor identified by the given `reference`.
|
||||
Demonitors the monitor identifies by the given `reference`.
|
||||
|
||||
If `monitor_ref` is a reference which the calling process
|
||||
obtained by calling `monitor/1`, that monitoring is turned off.
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See `:erlang.demonitor/2` for more info.
|
||||
See [`:erlang.demonitor/2`](http://www.erlang.org/doc/man/erlang.html#demonitor-2) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -426,7 +421,7 @@ defmodule Process do
|
||||
alive. This means that for such process, `alive?/1` will return `false` but
|
||||
its PID will be part of the list of PIDs returned by this function.
|
||||
|
||||
See `:erlang.processes/0` for more info.
|
||||
See [`:erlang.processes/0`](http://www.erlang.org/doc/man/erlang.html#processes-0) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -451,7 +446,7 @@ defmodule Process do
|
||||
emit an exit signal to all its other linked processes. The behaviour when
|
||||
`pid1` is trapping exits is described in `exit/2`.
|
||||
|
||||
See `:erlang.link/1` for more info.
|
||||
See [`:erlang.link/1`](http://www.erlang.org/doc/man/erlang.html#link-1) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -459,6 +454,7 @@ defmodule Process do
|
||||
defdelegate link(pid_or_port), to: :erlang
|
||||
|
||||
@doc """
|
||||
|
||||
Removes the link between the calling process and the given item (process or
|
||||
port).
|
||||
|
||||
@@ -467,7 +463,7 @@ defmodule Process do
|
||||
|
||||
The return value of this function is always `true`.
|
||||
|
||||
See `:erlang.unlink/1` for more info.
|
||||
See [`:erlang.unlink/1`](http://www.erlang.org/doc/man/erlang.html#unlink-1) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -496,23 +492,22 @@ defmodule Process do
|
||||
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid_or_port, name)
|
||||
when is_atom(name) and name not in [nil, false, true, :undefined] do
|
||||
def register(pid_or_port, name) when is_atom(name) and name not in [nil, false, true, :undefined] do
|
||||
:erlang.register(name, pid_or_port)
|
||||
catch
|
||||
:error, :badarg when node(pid_or_port) != node() ->
|
||||
message = "could not register #{inspect(pid_or_port)} because it belongs to another node"
|
||||
:erlang.error(ArgumentError.exception(message), [pid_or_port, name])
|
||||
|
||||
:error, :badarg when node(pid_or_port) != node() ->
|
||||
message = "could not register the #{pid_or_port pid_or_port} because it belongs to another node"
|
||||
:erlang.error ArgumentError.exception(message), [pid_or_port, name]
|
||||
:error, :badarg ->
|
||||
message =
|
||||
"could not register #{inspect(pid_or_port)} with " <>
|
||||
"name #{inspect(name)} because it is not alive, the name is already " <>
|
||||
"taken, or it has already been given another name"
|
||||
|
||||
:erlang.error(ArgumentError.exception(message), [pid_or_port, name])
|
||||
message = "could not register the #{pid_or_port pid_or_port} with " <>
|
||||
"name #{inspect name}. Or it is not alive, or the name is already " <>
|
||||
"taken, or it has already been given another name"
|
||||
:erlang.error ArgumentError.exception(message), [pid_or_port, name]
|
||||
end
|
||||
|
||||
defp pid_or_port(pid) when is_pid(pid), do: "pid #{inspect pid}"
|
||||
defp pid_or_port(port) when is_port(port), do: "port #{inspect port}"
|
||||
|
||||
@doc """
|
||||
Removes the registered `name`, associated with a PID
|
||||
or a port identifier.
|
||||
@@ -529,11 +524,11 @@ defmodule Process do
|
||||
Returns the PID or port identifier registered under `name` or `nil` if the
|
||||
name is not registered.
|
||||
|
||||
See `:erlang.whereis/1` for more info.
|
||||
See [`:erlang.whereis/1`](http://www.erlang.org/doc/man/erlang.html#whereis-1) for more info.
|
||||
"""
|
||||
@spec whereis(atom) :: pid | port | nil
|
||||
def whereis(name) do
|
||||
nillify(:erlang.whereis(name))
|
||||
nillify :erlang.whereis(name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -565,9 +560,8 @@ defmodule Process do
|
||||
@spec registered() :: [atom]
|
||||
defdelegate registered(), to: :erlang
|
||||
|
||||
@typep heap_size ::
|
||||
non_neg_integer
|
||||
| %{size: non_neg_integer, kill: boolean, error_logger: boolean}
|
||||
@typep heap_size :: non_neg_integer |
|
||||
%{size: non_neg_integer, kill: boolean, error_logger: boolean}
|
||||
|
||||
@typep priority_level :: :low | :normal | :high | :max
|
||||
|
||||
@@ -576,7 +570,7 @@ defmodule Process do
|
||||
|
||||
Returns the old value of `flag`.
|
||||
|
||||
See `:erlang.process_flag/2` for more info.
|
||||
See [`:erlang.process_flag/2`](http://www.erlang.org/doc/man/erlang.html#process_flag-2) for more info.
|
||||
|
||||
Note that `flag` values `:max_heap_size` and `:message_queue_data` are only available since OTP 19.
|
||||
|
||||
@@ -584,13 +578,13 @@ defmodule Process do
|
||||
"""
|
||||
@spec flag(:error_handler, module) :: module
|
||||
@spec flag(:max_heap_size, heap_size) :: heap_size
|
||||
@spec flag(:message_queue_data, :erlang.message_queue_data()) :: :erlang.message_queue_data()
|
||||
@spec flag(:message_queue_data, :erlang.message_queue_data) :: :erlang.message_queue_data
|
||||
@spec flag(:min_bin_vheap_size, non_neg_integer) :: non_neg_integer
|
||||
@spec flag(:min_heap_size, non_neg_integer) :: non_neg_integer
|
||||
@spec flag(:monitor_nodes, term) :: term
|
||||
@spec flag({:monitor_nodes, term()}, term) :: term
|
||||
@spec flag(:priority, priority_level) :: priority_level
|
||||
@spec flag(:save_calls, 0..10000) :: 0..10000
|
||||
@spec flag(:save_calls, 0..10_000) :: 0..10_000
|
||||
@spec flag(:sensitive, boolean) :: boolean
|
||||
@spec flag(:trap_exit, boolean) :: boolean
|
||||
defdelegate flag(flag, value), to: :erlang, as: :process_flag
|
||||
@@ -605,11 +599,11 @@ defmodule Process do
|
||||
The allowed values for `flag` are only a subset of those allowed in `flag/2`,
|
||||
namely `:save_calls`.
|
||||
|
||||
See `:erlang.process_flag/3` for more info.
|
||||
See [`:erlang.process_flag/3`](http://www.erlang.org/doc/man/erlang.html#process_flag-3) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec flag(pid, :save_calls, 0..10000) :: 0..10000
|
||||
@spec flag(pid, :save_calls, 0..10_000) :: 0..10_000
|
||||
defdelegate flag(pid, flag, value), to: :erlang, as: :process_flag
|
||||
|
||||
@doc """
|
||||
@@ -618,20 +612,20 @@ defmodule Process do
|
||||
|
||||
Use this only for debugging information.
|
||||
|
||||
See `:erlang.process_info/1` for more info.
|
||||
See [`:erlang.process_info/1`](http://www.erlang.org/doc/man/erlang.html#process_info-1) for more info.
|
||||
"""
|
||||
@spec info(pid) :: keyword
|
||||
def info(pid) do
|
||||
nillify(:erlang.process_info(pid))
|
||||
nillify :erlang.process_info(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by `pid`,
|
||||
or returns `nil` if the process is not alive.
|
||||
|
||||
See `:erlang.process_info/2` for more info.
|
||||
See [`:erlang.process_info/2`](http://www.erlang.org/doc/man/erlang.html#process_info-2) for more info.
|
||||
"""
|
||||
@spec info(pid, atom | [atom]) :: {atom, term} | [{atom, term}] | nil
|
||||
@spec info(pid, atom | [atom]) :: {atom, term} | [{atom, term}] | nil
|
||||
def info(pid, spec)
|
||||
|
||||
def info(pid, :registered_name) do
|
||||
@@ -643,7 +637,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
def info(pid, spec) when is_atom(spec) or is_list(spec) do
|
||||
nillify(:erlang.process_info(pid, spec))
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -654,7 +648,7 @@ defmodule Process do
|
||||
which is useful if the process does not expect to receive any messages
|
||||
in the near future.
|
||||
|
||||
See `:erlang.hibernate/3` for more info.
|
||||
See [`:erlang.hibernate/3`](http://www.erlang.org/doc/man/erlang.html#hibernate-3) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -663,5 +657,5 @@ defmodule Process do
|
||||
|
||||
@compile {:inline, nillify: 1}
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
+202
-311
@@ -19,23 +19,23 @@ defmodule Protocol do
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = :lists.map(fn _ -> quote(do: term) end, :lists.seq(2, arity))
|
||||
type_args = :lists.map(fn _ -> quote(do: term) end,
|
||||
:lists.seq(2, arity))
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
varify = fn pos -> Macro.var(String.to_atom("var" <> Integer.to_string(pos)), __MODULE__) end
|
||||
|
||||
call_args = :lists.map(varify, :lists.seq(2, arity))
|
||||
call_args = :lists.map(fn pos -> Macro.var(String.to_atom("var" <> Integer.to_string(pos)), __MODULE__) end,
|
||||
:lists.seq(2, arity))
|
||||
call_args = [quote(do: term) | call_args]
|
||||
|
||||
quote do
|
||||
name = unquote(name)
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity} | @functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def(unquote(name)(unquote_splicing(args)))
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
# Generate the actual implementation
|
||||
Kernel.def unquote(name)(unquote_splicing(call_args)) do
|
||||
@@ -66,14 +66,14 @@ defmodule Protocol do
|
||||
defp assert_protocol!(module, extra) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> :ok
|
||||
_ -> raise ArgumentError, "#{inspect(module)} is not available" <> extra
|
||||
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:module)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect(module)} is not a protocol" <> extra
|
||||
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -95,76 +95,27 @@ defmodule Protocol do
|
||||
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError, "#{inspect(impl)} is not available" <> extra
|
||||
_ -> raise ArgumentError,
|
||||
"#{inspect impl} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
impl.__impl__(:protocol)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect(impl)} is not an implementation of a protocol" <> extra
|
||||
raise ArgumentError,
|
||||
"#{inspect impl} is not an implementation of a protocol" <> extra
|
||||
else
|
||||
^protocol ->
|
||||
:ok
|
||||
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected #{inspect(impl)} to be an implementation of #{inspect(protocol)}" <>
|
||||
", got: #{inspect(other)}" <> extra
|
||||
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Derives the `protocol` for `module` with the given options.
|
||||
|
||||
If your implementation passes options or if you are generating
|
||||
custom code based on the struct, you will also need to implement
|
||||
a macro defined as `__deriving__(module, struct, options)`
|
||||
to get the options that were passed.
|
||||
|
||||
## Examples
|
||||
|
||||
defprotocol Derivable do
|
||||
def ok(a)
|
||||
end
|
||||
|
||||
defimpl Derivable, for: Any do
|
||||
defmacro __deriving__(module, struct, options) do
|
||||
quote do
|
||||
defimpl Derivable, for: unquote(module) do
|
||||
def ok(arg) do
|
||||
{:ok, arg, unquote(Macro.escape(struct)), unquote(options)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ok(arg) do
|
||||
{:ok, arg}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ImplStruct do
|
||||
@derive [Derivable]
|
||||
defstruct a: 0, b: 0
|
||||
|
||||
defimpl Sample do
|
||||
def ok(struct) do
|
||||
Unknown.undefined(struct)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Explicit derivations can now be called via `__deriving__`:
|
||||
|
||||
# Explicitly derived via `__deriving__`
|
||||
Derivable.ok(%ImplStruct{a: 1, b: 1})
|
||||
|
||||
# Explicitly derived by API via `__deriving__`
|
||||
require Protocol
|
||||
Protocol.derive(Derivable, ImplStruct, :oops)
|
||||
Derivable.ok(%ImplStruct{a: 1, b: 1})
|
||||
|
||||
"""
|
||||
defmacro derive(protocol, module, options \\ []) do
|
||||
quote do
|
||||
@@ -193,14 +144,15 @@ defmodule Protocol do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([charlist | String.t()]) :: [atom]
|
||||
@spec extract_protocols([charlist | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute(paths, 'Elixir.', fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _] -> module
|
||||
_ -> nil
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -222,23 +174,23 @@ defmodule Protocol do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [charlist | String.t()]) :: [atom]
|
||||
@spec extract_impls(module, [charlist | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_charlist(protocol) ++ '.'
|
||||
|
||||
extract_matching_by_attribute(paths, prefix, fn _mod, attributes ->
|
||||
case attributes[:protocol_impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:protocol_impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
@@ -260,9 +212,8 @@ defmodule Protocol do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -299,9 +250,9 @@ defmodule Protocol do
|
||||
it will be loaded.
|
||||
"""
|
||||
@spec consolidate(module, [module]) ::
|
||||
{:ok, binary}
|
||||
| {:error, :not_a_protocol}
|
||||
| {:error, :no_beam_info}
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def consolidate(protocol, types) when is_atom(protocol) do
|
||||
with {:ok, ast_info, chunks_info} <- beam_protocol(protocol),
|
||||
{:ok, code} <- change_debug_info(ast_info, types),
|
||||
@@ -309,27 +260,19 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:abstract_code, :attributes, :compile_info, 'ExDc', 'ExDp']
|
||||
chunk_ids = [:abstract_code, :attributes, :compile_info, 'ExDc']
|
||||
opts = [:allow_missing_chunks]
|
||||
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
{:ok, {^protocol, entries}} ->
|
||||
[
|
||||
{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{:compile_info, compile_info},
|
||||
{'ExDc', docs},
|
||||
{'ExDp', deprecated}
|
||||
] = entries
|
||||
|
||||
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{:compile_info, compile_info},
|
||||
{'ExDc', docs}]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any] ->
|
||||
{:ok, {protocol, any, abstract_code}, {compile_info, docs, deprecated}}
|
||||
|
||||
{:ok, {protocol, any, abstract_code}, {compile_info, docs}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
@@ -337,117 +280,79 @@ defmodule Protocol do
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
[_ | _] = file -> file
|
||||
_ -> module
|
||||
atom when is_atom(atom) -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- __builtin__(), do: mod
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- __builtin__(), do: mod
|
||||
structs = types -- all
|
||||
|
||||
case change_impl_for(code, protocol, types, structs, false, []) do
|
||||
{:ok, ret} -> {:ok, ret}
|
||||
other -> other
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp change_impl_for(
|
||||
[{:function, line, :__protocol__, 1, clauses} | tail],
|
||||
protocol,
|
||||
types,
|
||||
structs,
|
||||
_,
|
||||
acc
|
||||
) do
|
||||
defp change_impl_for([{:function, line, :__protocol__, 1, clauses} | tail], protocol, types, structs, _, acc) do
|
||||
abstract_types = :erl_parse.abstract(:lists.usort(types))
|
||||
|
||||
clauses =
|
||||
Enum.map(clauses, fn
|
||||
{:clause, l, [{:atom, _, :consolidated?}], [], [{:atom, _, _}]} ->
|
||||
{:clause, l, [{:atom, 0, :consolidated?}], [], [{:atom, 0, true}]}
|
||||
clauses = :lists.map(fn
|
||||
{:clause, l, [{:atom, _, :consolidated?}], [], [{:atom, _, _}]} ->
|
||||
{:clause, l, [{:atom, 0, :consolidated?}], [], [{:atom, 0, true}]}
|
||||
{:clause, l, [{:atom, _, :impls}], [], [{:atom, _, _}]} ->
|
||||
{:clause, l, [{:atom, 0, :impls}], [], [{:tuple, 0, [{:atom, 0, :consolidated}, abstract_types]}]}
|
||||
{:clause, _, _, _, _} = c ->
|
||||
c
|
||||
end, clauses)
|
||||
|
||||
{:clause, l, [{:atom, _, :impls}], [], [{:atom, _, _}]} ->
|
||||
tuple = {:tuple, 0, [{:atom, 0, :consolidated}, abstract_types]}
|
||||
{:clause, l, [{:atom, 0, :impls}], [], [tuple]}
|
||||
|
||||
{:clause, _, _, _, _} = c ->
|
||||
c
|
||||
end)
|
||||
|
||||
acc = [{:function, line, :__protocol__, 1, clauses} | acc]
|
||||
change_impl_for(tail, protocol, types, structs, true, acc)
|
||||
change_impl_for(tail, protocol, types, structs, true,
|
||||
[{:function, line, :__protocol__, 1, clauses} | acc])
|
||||
end
|
||||
|
||||
defp change_impl_for(
|
||||
[{:function, line, :impl_for, 1, _} | tail],
|
||||
protocol,
|
||||
types,
|
||||
structs,
|
||||
protocol?,
|
||||
acc
|
||||
) do
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _} | tail], protocol, types, structs, protocol?, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any)
|
||||
|
||||
clauses =
|
||||
for {guard, mod} <- __builtin__(),
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
clauses = for {guard, mod} <- __builtin__(),
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses =
|
||||
[struct_clause_for(line) | clauses] ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
clauses = [struct_clause_for(line) | clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
acc = [{:function, line, :impl_for, 1, clauses} | acc]
|
||||
change_impl_for(tail, protocol, types, structs, protocol?, acc)
|
||||
change_impl_for(tail, protocol, types, structs, protocol?,
|
||||
[{:function, line, :impl_for, 1, clauses} | acc])
|
||||
end
|
||||
|
||||
defp change_impl_for(
|
||||
[{:function, line, :struct_impl_for, 1, _} | tail],
|
||||
protocol,
|
||||
types,
|
||||
structs,
|
||||
protocol?,
|
||||
acc
|
||||
) do
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _} | tail], protocol, types, structs, protocol?, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any)
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
acc = [{:function, line, :struct_impl_for, 1, clauses} | acc]
|
||||
change_impl_for(tail, protocol, types, structs, protocol?, acc)
|
||||
change_impl_for(tail, protocol, types, structs, protocol?,
|
||||
[{:function, line, :struct_impl_for, 1, clauses} | acc])
|
||||
end
|
||||
|
||||
defp change_impl_for(
|
||||
[{:attribute, line, :spec, {{:__protocol__, 1}, funspecs}} | tail],
|
||||
protocol,
|
||||
types,
|
||||
structs,
|
||||
protocol?,
|
||||
acc
|
||||
) do
|
||||
new_specs =
|
||||
for spec <- funspecs do
|
||||
case spec do
|
||||
{:type, line, :fun, [{:type, _, :product, [{:atom, _, :consolidated?}]}, _]} ->
|
||||
product = {:type, line, :product, [{:atom, 0, :consolidated?}]}
|
||||
{:type, line, :fun, [product, {:atom, 0, true}]}
|
||||
|
||||
{:type, line, :fun, [{:type, _, :product, [{:atom, _, :impls}]}, _]} ->
|
||||
impls = for mod <- types, do: {:atom, 0, mod}
|
||||
list = {:type, 0, :list, [{:type, 0, :union, impls}]}
|
||||
tuple = {:type, 0, :tuple, [{:atom, 0, :consolidated}, list]}
|
||||
{:type, line, :fun, [{:type, line, :product, [{:atom, 0, :impls}]}, tuple]}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
defp change_impl_for([{:attribute, line, :spec, {{:__protocol__, 1}, funspecs}} | tail], protocol, types, structs, protocol?, acc) do
|
||||
new_specs = for spec <- funspecs do
|
||||
case spec do
|
||||
{:type, line, :fun, [{:type, _, :product, [{:atom, _, :consolidated?}]}, _]} ->
|
||||
{:type, line, :fun,
|
||||
[{:type, line, :product, [{:atom, 0, :consolidated?}]},
|
||||
{:atom, 0, true}]}
|
||||
{:type, line, :fun, [{:type, _, :product, [{:atom, _, :impls}]}, _]} ->
|
||||
{:type, line, :fun,
|
||||
[{:type, line, :product, [{:atom, 0, :impls}]},
|
||||
{:type, 0, :tuple,
|
||||
[{:atom, 0, :consolidated},
|
||||
{:type, 0, :list, [{:type, 0, :module, []}]}]}]}
|
||||
other -> other
|
||||
end
|
||||
|
||||
acc = [{:attribute, line, :spec, {{:__protocol__, 1}, new_specs}} | acc]
|
||||
change_impl_for(tail, protocol, types, structs, protocol?, acc)
|
||||
end
|
||||
change_impl_for(tail, protocol, types, structs, protocol?, [{:attribute, line, :spec, {{:__protocol__, 1}, new_specs}} | acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([head | tail], protocol, info, types, protocol?, acc) do
|
||||
@@ -463,29 +368,37 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
remote = {:remote, line, {:atom, line, :erlang}, {:atom, line, guard}}
|
||||
guard = {:call, line, remote, [{:var, line, :x}]}
|
||||
body = {:atom, line, load_impl(protocol, mod)}
|
||||
{:clause, line, [{:var, line, :x}], [[guard]], [body]}
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, load_impl(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
map_field_exact = {:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
arg = {:map, line, [map_field_exact]}
|
||||
|
||||
is_atom = {:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}}
|
||||
guard = {:call, line, is_atom, [{:var, line, :x}]}
|
||||
|
||||
body = {:call, line, {:atom, line, :struct_impl_for}, [{:var, line, :x}]}
|
||||
{:clause, line, [arg], [[guard]], [body]}
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(struct, protocol, line) do
|
||||
{:clause, line, [{:atom, line, struct}], [], [{:atom, line, load_impl(protocol, struct)}]}
|
||||
{:clause, line, [{:atom, line, struct}], [],
|
||||
[{:atom, line, load_impl(protocol, struct)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [], [{:atom, line, value}]}
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
defp load_impl(protocol, for) do
|
||||
@@ -493,35 +406,28 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile(protocol, code, {compile_info, docs, deprecated}) do
|
||||
defp compile(protocol, code, {compile_info, docs}) do
|
||||
opts = Keyword.take(compile_info, [:source])
|
||||
opts = if Code.compiler_options()[:debug_info], do: [:debug_info | opts], else: opts
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info | opts], else: opts
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return | opts])
|
||||
|
||||
case docs do
|
||||
:missing_chunk -> {:ok, binary}
|
||||
_ -> {:ok, :elixir_erl.add_beam_chunks(binary, [{"ExDc", docs}, {"ExDp", deprecated}])}
|
||||
end
|
||||
{:ok,
|
||||
case docs do
|
||||
:missing_chunk -> binary
|
||||
_ -> :elixir_erl.add_beam_chunks(binary, [{"ExDc", docs}])
|
||||
end}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
|
||||
@doc false
|
||||
def __protocol__(name, do: block) do
|
||||
def __protocol__(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel,
|
||||
except: [
|
||||
defmacrop: 1,
|
||||
defmacrop: 2,
|
||||
defmacro: 1,
|
||||
defmacro: 2,
|
||||
defp: 1,
|
||||
defp: 2,
|
||||
def: 1,
|
||||
def: 2
|
||||
]
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol, only: [def: 1]
|
||||
@@ -544,16 +450,9 @@ defmodule Protocol do
|
||||
|
||||
defp after_defprotocol do
|
||||
quote bind_quoted: [builtin: __builtin__()] do
|
||||
any_impl_for =
|
||||
if @fallback_to_any do
|
||||
quote do: unquote(__MODULE__.Any).__impl__(:target)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec impl_for(term) :: atom | nil
|
||||
Kernel.def(impl_for(data))
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
@@ -564,20 +463,16 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Define the implementation for built-ins
|
||||
:lists.foreach(
|
||||
fn {guard, mod} ->
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
:lists.foreach(fn {guard, mod} ->
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case Code.ensure_compiled?(unquote(target)) and
|
||||
function_exported?(unquote(target), :__impl__, 1) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> unquote(any_impl_for)
|
||||
end
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> any_impl_for()
|
||||
end
|
||||
end,
|
||||
builtin
|
||||
)
|
||||
end
|
||||
end, builtin)
|
||||
|
||||
# Define a catch-all impl_for/1 clause to pacify Dialyzer (since
|
||||
# destructuring opaque types is illegal, Dialyzer will think none of the
|
||||
@@ -586,33 +481,39 @@ defmodule Protocol do
|
||||
# since it relies on Dialyzer not being smart enough to conclude that all
|
||||
# opaque types will get the any_impl_for/0 implementation.
|
||||
Kernel.def impl_for(_) do
|
||||
unquote(any_impl_for)
|
||||
any_impl_for()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec impl_for!(term) :: atom
|
||||
if any_impl_for do
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data)
|
||||
end
|
||||
@spec impl_for!(term) :: atom | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for(), do: __MODULE__.Any.__impl__(:target)
|
||||
else
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
Kernel.defp any_impl_for(), do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
|
||||
case Code.ensure_compiled?(target) and function_exported?(target, :__impl__, 1) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> unquote(any_impl_for)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> any_impl_for()
|
||||
end
|
||||
end
|
||||
|
||||
# Inline struct implementation for performance
|
||||
@compile {:inline, struct_impl_for: 1}
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
@@ -628,16 +529,16 @@ defmodule Protocol do
|
||||
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
|
||||
@spec __protocol__(:consolidated?) :: false
|
||||
@spec __protocol__(:impls) :: :not_consolidated
|
||||
Kernel.def(__protocol__(:module), do: __MODULE__)
|
||||
Kernel.def(__protocol__(:functions), do: unquote(:lists.sort(@functions)))
|
||||
Kernel.def(__protocol__(:consolidated?), do: false)
|
||||
Kernel.def(__protocol__(:impls), do: :not_consolidated)
|
||||
Kernel.def __protocol__(:module), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
Kernel.def __protocol__(:consolidated?), do: false
|
||||
Kernel.def __protocol__(:impls), do: :not_consolidated
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __functions_spec__([]), do: []
|
||||
|
||||
def __functions_spec__([]),
|
||||
do: []
|
||||
def __functions_spec__([head | tail]),
|
||||
do: [:lists.foldl(&{:|, [], [&1, &2]}, head, tail), quote(do: ...)]
|
||||
|
||||
@@ -646,11 +547,11 @@ defmodule Protocol do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, do: block, for: for) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, do: block, for: f)
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, do: block, for: for) do
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
# Unquote the implementation just later
|
||||
# when all variables will already be injected
|
||||
# into the module body.
|
||||
@@ -660,23 +561,23 @@ defmodule Protocol do
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
@spec __impl__(:target) :: __MODULE__
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:for), do: unquote(for)
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
end
|
||||
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol!(protocol)
|
||||
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
|
||||
|
||||
defmodule name do
|
||||
@behaviour protocol
|
||||
@protocol protocol
|
||||
@for for
|
||||
@protocol protocol
|
||||
@for for
|
||||
|
||||
unquote(block)
|
||||
|
||||
@@ -692,41 +593,39 @@ defmodule Protocol do
|
||||
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
|
||||
struct =
|
||||
if for == env.module do
|
||||
Module.get_attribute(for, :struct) || raise "struct is not defined for #{inspect(for)}"
|
||||
Module.get_attribute(for, :struct) ||
|
||||
raise "struct is not defined for #{inspect for}"
|
||||
else
|
||||
for.__struct__
|
||||
end
|
||||
|
||||
foreach = fn
|
||||
:lists.foreach(fn
|
||||
proto when is_atom(proto) ->
|
||||
derive(proto, for, struct, [], env)
|
||||
|
||||
{proto, opts} when is_atom(proto) ->
|
||||
derive(proto, for, struct, opts, env)
|
||||
end
|
||||
|
||||
:lists.foreach(foreach, :lists.flatten(derives))
|
||||
end, :lists.flatten(derives))
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
extra = ", cannot derive #{inspect(protocol)} for #{inspect(for)}"
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
__ensure_defimpl__(protocol, for, env)
|
||||
assert_impl!(protocol, Any, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
impl = Module.concat(protocol, Any)
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn mod, fun, args ->
|
||||
if function_exported?(mod, fun, length(args)) do
|
||||
apply(mod, fun, args)
|
||||
else
|
||||
quoted =
|
||||
quote do
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
if function_exported?(mod, fun, length(args)) do
|
||||
apply(mod, fun, args)
|
||||
else
|
||||
Module.create(Module.concat(protocol, for), quote do
|
||||
Module.register_attribute(__MODULE__, :protocol_impl, persist: true)
|
||||
@protocol_impl [protocol: unquote(protocol), for: unquote(for)]
|
||||
|
||||
@@ -734,13 +633,11 @@ defmodule Protocol do
|
||||
@spec __impl__(:target) :: unquote(impl)
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end
|
||||
|
||||
Module.create(Module.concat(protocol, for), quoted, Macro.Env.location(env))
|
||||
end
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end, Macro.Env.location(env))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -748,14 +645,10 @@ defmodule Protocol do
|
||||
def __ensure_defimpl__(protocol, for, env) do
|
||||
if Protocol.consolidated?(protocol) do
|
||||
message =
|
||||
"the #{inspect(protocol)} protocol has already been consolidated, an " <>
|
||||
"implementation for #{inspect(for)} has no effect. If you want to " <>
|
||||
"implement protocols after compilation or during tests, check the " <>
|
||||
"\"Consolidation\" section in the documentation for Kernel.defprotocol/2"
|
||||
|
||||
"the #{inspect protocol} protocol has already been consolidated" <>
|
||||
", an implementation for #{inspect for} has no effect"
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -763,34 +656,32 @@ defmodule Protocol do
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
|
||||
mapper = fn {:spec, expr, pos} ->
|
||||
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Module.store_typespec(module, :callback, {:callback, expr, pos})
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
found = :lists.map(mapper, specs)
|
||||
:lists.any(&(&1 == true), found)
|
||||
found =
|
||||
:lists.map(fn {:spec, expr, pos} ->
|
||||
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Module.store_typespec(module, :callback, {:callback, expr, pos})
|
||||
true
|
||||
end
|
||||
end, specs)
|
||||
|
||||
:lists.any(& &1 == true, found)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def __builtin__ do
|
||||
[
|
||||
is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference
|
||||
]
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
end
|
||||
|
||||
+16
-19
@@ -43,6 +43,7 @@ defmodule Range do
|
||||
@type t :: %Range{first: integer, last: integer}
|
||||
@type t(first, last) :: %Range{first: first, last: last}
|
||||
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
@@ -53,12 +54,22 @@ defmodule Range do
|
||||
|
||||
def new(first, last) do
|
||||
raise ArgumentError,
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{inspect(first)}..#{inspect(last)}"
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{inspect first}..#{inspect last}"
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
@doc false
|
||||
@doc """
|
||||
Returns `true` if the given `term` is a valid range.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Range.range?(1..3)
|
||||
true
|
||||
|
||||
iex> Range.range?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec range?(term) :: boolean
|
||||
def range?(term)
|
||||
def range?(first..last) when is_integer(first) and is_integer(last), do: true
|
||||
@@ -109,26 +120,12 @@ defimpl Enumerable, for: Range do
|
||||
{:ok, first - last + 1}
|
||||
end
|
||||
end
|
||||
|
||||
def slice(first..last) do
|
||||
if first <= last do
|
||||
{:ok, last - first + 1, &slice_asc(first + &1, &2)}
|
||||
else
|
||||
{:ok, first - last + 1, &slice_desc(first - &1, &2)}
|
||||
end
|
||||
end
|
||||
|
||||
defp slice_asc(current, 1), do: [current]
|
||||
defp slice_asc(current, remaining), do: [current | slice_asc(current + 1, remaining - 1)]
|
||||
|
||||
defp slice_desc(current, 1), do: [current]
|
||||
defp slice_desc(current, remaining), do: [current | slice_desc(current - 1, remaining - 1)]
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(first..last, opts) do
|
||||
concat([to_doc(first, opts), "..", to_doc(last, opts)])
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+57
-62
@@ -54,20 +54,11 @@ defmodule Record do
|
||||
that contains the record definition to extract; with this option, this
|
||||
function uses the same path lookup used by the `-include` attribute used in
|
||||
Erlang modules.
|
||||
|
||||
* `:from_lib` - (binary representing a path to a file) path to the Erlang
|
||||
file that contains the record definition to extract; with this option,
|
||||
this function uses the same path lookup used by the `-include_lib`
|
||||
attribute used in Erlang modules.
|
||||
|
||||
* `:includes` - (a list of directories as binaries) if the record being
|
||||
extracted depends on relative includes, this option allows developers
|
||||
to specify the directory those relative includes exist
|
||||
|
||||
* `:macros` - (keyword list of macro names and values) if the record
|
||||
being extract depends on the values of macros, this option allows
|
||||
the value of those macros to be set
|
||||
|
||||
These options are expected to be literals (including the binary values) at
|
||||
compile time.
|
||||
|
||||
@@ -126,9 +117,21 @@ defmodule Record do
|
||||
true
|
||||
|
||||
"""
|
||||
defguard is_record(data, kind)
|
||||
when is_atom(kind) and is_tuple(data) and tuple_size(data) > 0 and
|
||||
elem(data, 0) == kind
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_atom(unquote(kind)) and is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0 and
|
||||
elem(unquote(data), 0) == unquote(kind)
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
kind = unquote(kind)
|
||||
is_atom(kind) and is_tuple(result) and tuple_size(result) > 0 and elem(result, 0) == kind
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record.
|
||||
@@ -145,8 +148,20 @@ defmodule Record do
|
||||
false
|
||||
|
||||
"""
|
||||
defguard is_record(data)
|
||||
when is_tuple(data) and tuple_size(data) > 0 and is_atom(elem(data, 0))
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0 and
|
||||
is_atom(elem(unquote(data), 0))
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
is_tuple(result) and tuple_size(result) > 0 and is_atom(elem(result, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a set of macros to create, access, and pattern match
|
||||
@@ -230,7 +245,7 @@ defmodule Record do
|
||||
|
||||
Record.defrecord :my_rec, Record.extract(...)
|
||||
#=> ** (ArgumentError) invalid value for record field fun_field,
|
||||
#=> cannot escape #Function<12.90072148/2 in :erl_eval.expr/5>.
|
||||
cannot escape #Function<12.90072148/2 in :erl_eval.expr/5>.
|
||||
|
||||
To work around this error, redefine the field with your own &M.f/a function,
|
||||
like so:
|
||||
@@ -247,11 +262,11 @@ defmodule Record do
|
||||
tag = tag || name
|
||||
fields = Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro unquote(name)(args \\ []) do
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro unquote(name)(record, args) do
|
||||
defmacro(unquote(name)(record, args)) do
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
@@ -265,11 +280,11 @@ defmodule Record do
|
||||
tag = tag || name
|
||||
fields = Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop unquote(name)(args \\ []) do
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
defmacrop(unquote(name)(record, args)) do
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
@@ -278,7 +293,7 @@ defmodule Record do
|
||||
# Normalizes of record fields to have default values.
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
normalizer_fun = fn
|
||||
:lists.map(fn
|
||||
{key, value} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(value)
|
||||
@@ -288,15 +303,11 @@ defmodule Record do
|
||||
else
|
||||
value -> {key, value}
|
||||
end
|
||||
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
|
||||
other ->
|
||||
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect(other)}"
|
||||
end
|
||||
|
||||
:lists.map(normalizer_fun, fields)
|
||||
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
# Callback invoked from record/0 and record/1 macros.
|
||||
@@ -305,23 +316,18 @@ defmodule Record do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(tag, fields, args)
|
||||
|
||||
Keyword.keyword?(args) ->
|
||||
create(tag, fields, args, caller)
|
||||
|
||||
true ->
|
||||
fields = Macro.escape(fields)
|
||||
|
||||
case Macro.expand(args, caller) do
|
||||
{:{}, _, [^tag | list]} when length(list) == length(fields) ->
|
||||
record = List.to_tuple([tag | list])
|
||||
Record.__keyword__(tag, fields, record)
|
||||
|
||||
{^tag, arg} when length(fields) == 1 ->
|
||||
Record.__keyword__(tag, fields, {tag, arg})
|
||||
|
||||
_ ->
|
||||
quote(do: Record.__keyword__(unquote(tag), unquote(fields), unquote(args)))
|
||||
quote do: Record.__keyword__(unquote(tag), unquote(fields), unquote(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -332,24 +338,20 @@ defmodule Record do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(tag, fields, record, args)
|
||||
|
||||
Keyword.keyword?(args) ->
|
||||
update(tag, fields, record, args, caller)
|
||||
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
"expected arguments to be a compile time atom or a keyword list, got: " <>
|
||||
Macro.to_string(args)
|
||||
msg = "expected arguments to be a compile time atom or a keyword list, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the index of field.
|
||||
defp index(tag, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
# Convert to Elixir index
|
||||
index - 1
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, "record #{inspect(tag)} does not have the key: #{inspect(field)}"
|
||||
raise ArgumentError, "record #{inspect tag} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -359,7 +361,7 @@ defmodule Record do
|
||||
keyword = apply_underscore(fields, keyword)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn {field, default}, each_keyword ->
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.fetch(each_keyword, field) do
|
||||
{:ok, value} -> value
|
||||
@@ -373,10 +375,9 @@ defmodule Record do
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [tag | match]}
|
||||
|
||||
_ ->
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, "record #{inspect(tag)} does not have the key: #{inspect(hd(keys))}"
|
||||
raise ArgumentError, "record #{inspect tag} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -388,29 +389,27 @@ defmodule Record do
|
||||
|
||||
keyword = apply_underscore(fields, keyword)
|
||||
|
||||
Enum.reduce(keyword, var, fn {key, value}, acc ->
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect(tag)} does not have the key: #{inspect(key)}"
|
||||
raise ArgumentError, "record #{inspect tag} does not have the key: #{inspect key}"
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
# Gets a record key from the given var.
|
||||
defp get(tag, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect(tag)} does not have the key: #{inspect(key)}"
|
||||
raise ArgumentError, "record #{inspect tag} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -423,29 +422,26 @@ defmodule Record do
|
||||
def __keyword__(tag, fields, record) do
|
||||
if is_record(record, tag) do
|
||||
[_tag | values] = Tuple.to_list(record)
|
||||
|
||||
case join_keyword(fields, values, []) do
|
||||
kv when is_list(kv) ->
|
||||
kv
|
||||
|
||||
expected_fields ->
|
||||
raise ArgumentError,
|
||||
"expected argument to be a #{inspect(tag)} record with " <>
|
||||
"#{expected_fields} fields, got: " <> inspect(record)
|
||||
msg = "expected argument to be a #{inspect tag} record with #{expected_fields} fields, got: #{inspect record}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
else
|
||||
raise ArgumentError,
|
||||
"expected argument to be a literal atom, literal keyword or " <>
|
||||
"a #{inspect(tag)} record, got runtime: " <> inspect(record)
|
||||
msg = "expected argument to be a literal atom, literal keyword or a #{inspect tag} record, got runtime: #{inspect record}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a keyword list, or expected number of fields on size mismatch
|
||||
defp join_keyword([{field, _default} | fields], [value | values], acc),
|
||||
do: join_keyword(fields, values, [{field, value} | acc])
|
||||
|
||||
defp join_keyword([], [], acc), do: :lists.reverse(acc)
|
||||
defp join_keyword(rest_fields, _rest_values, acc), do: length(acc) + length(rest_fields)
|
||||
defp join_keyword([], [], acc),
|
||||
do: :lists.reverse(acc)
|
||||
defp join_keyword(rest_fields, _rest_values, acc),
|
||||
do: length(acc) + length(rest_fields) # expected fields
|
||||
|
||||
defp apply_underscore(fields, keyword) do
|
||||
case Keyword.fetch(keyword, :_) do
|
||||
@@ -454,7 +450,6 @@ defmodule Record do
|
||||
|> Enum.map(fn {k, _} -> {k, default} end)
|
||||
|> Keyword.merge(keyword)
|
||||
|> Keyword.delete(:_)
|
||||
|
||||
:error ->
|
||||
keyword
|
||||
end
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
defmodule Record.Extractor do
|
||||
@moduledoc false
|
||||
|
||||
def extract(name, opts) do
|
||||
extract_record(name, from_or_from_lib_file(opts))
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
extract_record(name, from_file(file))
|
||||
end
|
||||
|
||||
def extract_all(opts) do
|
||||
extract_all_records(from_or_from_lib_file(opts))
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
extract_record(name, from_lib_file(file))
|
||||
end
|
||||
|
||||
defp from_or_from_lib_file(opts) do
|
||||
cond do
|
||||
file = opts[:from] ->
|
||||
{from_file(file), Keyword.delete(opts, :from)}
|
||||
# Retrieve all records definitions from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract_all(from: file) when is_binary(file) do
|
||||
extract_all_records(from_file(file))
|
||||
end
|
||||
|
||||
file = opts[:from_lib] ->
|
||||
{from_lib_file(file), Keyword.delete(opts, :from_lib)}
|
||||
|
||||
true ->
|
||||
raise ArgumentError, "expected :from or :from_lib to be given as option"
|
||||
end
|
||||
# Retrieve all records definitions from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract_all(from_lib: file) when is_binary(file) do
|
||||
extract_all_records(from_lib_file(file))
|
||||
end
|
||||
|
||||
# Find file using the same lookup as the *include* attribute from Erlang modules.
|
||||
defp from_file(file) do
|
||||
file = String.to_charlist(file)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> file
|
||||
realfile -> realfile
|
||||
@@ -35,33 +37,28 @@ defmodule Record.Extractor do
|
||||
# Find file using the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
defp from_lib_file(file) do
|
||||
[app | path] = :filename.split(String.to_charlist(file))
|
||||
|
||||
case :code.lib_dir(List.to_atom(app)) do
|
||||
{:error, _} ->
|
||||
raise ArgumentError, "lib file #{file} could not be found"
|
||||
|
||||
libpath ->
|
||||
:filename.join([libpath | path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp extract_record(name, {file, opts}) do
|
||||
form = read_file(file, opts)
|
||||
defp extract_record(name, file) do
|
||||
form = read_file(file)
|
||||
records = extract_records(form)
|
||||
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"no record #{name} found at #{file}. Or the record does not exist or " <>
|
||||
"its entry is malformed or depends on other include files"
|
||||
raise ArgumentError, "no record #{name} found at #{file}"
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve all records from the given file
|
||||
defp extract_all_records({file, opts}) do
|
||||
form = read_file(file, opts)
|
||||
defp extract_all_records(file) do
|
||||
form = read_file(file)
|
||||
records = extract_records(form)
|
||||
for rec = {name, _fields} <- records, do: {name, parse_record(rec, form)}
|
||||
end
|
||||
@@ -75,11 +72,10 @@ defmodule Record.Extractor do
|
||||
# includes record but with macros and other attributes expanded,
|
||||
# such as "-include(...)" and "-include_lib(...)". This is done
|
||||
# by using Erlang's epp.
|
||||
defp read_file(file, opts) do
|
||||
case :epp.parse_file(file, opts) do
|
||||
defp read_file(file) do
|
||||
case :epp.parse_file(file, []) do
|
||||
{:ok, form} ->
|
||||
form
|
||||
|
||||
other ->
|
||||
raise "error parsing file #{file}, got: #{inspect(other)}"
|
||||
end
|
||||
@@ -89,7 +85,9 @@ defmodule Record.Extractor do
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({_name, fields}, form) do
|
||||
cons = List.foldr(fields, {nil, 0}, fn f, acc -> {:cons, 0, parse_field(f), acc} end)
|
||||
cons = List.foldr fields, {nil, 0}, fn f, acc ->
|
||||
{:cons, 0, parse_field(f), acc}
|
||||
end
|
||||
eval_record(cons, form)
|
||||
end
|
||||
|
||||
@@ -106,10 +104,12 @@ defmodule Record.Extractor do
|
||||
end
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++ [{:function, 0, :hello, 0, [{:clause, 0, [], [], [cons]}]}]
|
||||
form = form ++
|
||||
[{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [cons]}]}]
|
||||
|
||||
{:function, 0, :hello, 0, [{:clause, 0, [], [], [record_ast]}]} =
|
||||
:erl_expand_records.module(form, []) |> List.last()
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
|
||||
+47
-61
@@ -26,7 +26,7 @@ defmodule Regex do
|
||||
|
||||
~r/(?<foo>.)(?<bar>.)/ == ~r/(?<foo>.)(?<bar>.)/
|
||||
|
||||
may return `true` or `false` depending on your machine, endianness,
|
||||
may return `true` or `false` depending on your machine, endianess,
|
||||
available optimizations and others. You can, however, retrieve the source
|
||||
of a compiled regular expression by accessing the `source` field, and then
|
||||
compare those directly:
|
||||
@@ -40,11 +40,11 @@ defmodule Regex do
|
||||
different OTP releases, as OTP releases may update the underlying regular
|
||||
expression engine at any time.
|
||||
|
||||
For such reasons, we always recommend precompiling Elixir projects using
|
||||
For such reasons, we always recomend precompiling Elixir projects using
|
||||
the OTP version meant to run in production. In case cross-compilation is
|
||||
really necessary, you can manually invoke `Regex.recompile/1` or
|
||||
`Regex.recompile!/1` to perform a runtime version check and recompile the
|
||||
regex if necessary.
|
||||
really necessary, you can manually invoke `Regex.recompile/1` or `Regex.
|
||||
recompile!/1` to perform a runtime version check and recompile the regex
|
||||
if necessary.
|
||||
|
||||
## Modifiers
|
||||
|
||||
@@ -155,7 +155,6 @@ defmodule Regex do
|
||||
case :re.compile(source, opts) do
|
||||
{:ok, re_pattern} ->
|
||||
{:ok, %Regex{re_pattern: re_pattern, re_version: version, source: source, opts: doc_opts}}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -186,7 +185,6 @@ defmodule Regex do
|
||||
case Map.get(regex, :re_version, :error) do
|
||||
^version ->
|
||||
{:ok, regex}
|
||||
|
||||
_ ->
|
||||
%{source: source, opts: opts} = regex
|
||||
compile(source, opts, version)
|
||||
@@ -228,7 +226,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t()) :: boolean
|
||||
@spec match?(t, String.t) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
@@ -277,12 +275,12 @@ defmodule Regex do
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
|
||||
case :re.run(string, compiled, [{:capture, captures, return}]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
:match -> []
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
@@ -304,7 +302,7 @@ defmodule Regex do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec named_captures(t, String.t(), [term]) :: map | nil
|
||||
@spec named_captures(t, String.t, [term]) :: map | nil
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
@@ -329,7 +327,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec source(t) :: String.t()
|
||||
@spec source(t) :: String.t
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
@@ -343,7 +341,7 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
@spec opts(t) :: String.t()
|
||||
@spec opts(t) :: String.t
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
@@ -357,7 +355,7 @@ defmodule Regex do
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t()]
|
||||
@spec names(t) :: [String.t]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
@@ -391,13 +389,13 @@ defmodule Regex do
|
||||
[["$"], ["£"], ["€"]]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t(), [term]) :: [[String.t()]]
|
||||
@spec scan(t, String.t, [term]) :: [[String.t]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
options = [{:capture, captures, return}, :global]
|
||||
options = [{:capture, captures, return}, :global]
|
||||
|
||||
case :re.run(string, compiled, options) do
|
||||
:match -> []
|
||||
@@ -418,7 +416,6 @@ defmodule Regex do
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when `true`, removes empty strings (`""`) from the result.
|
||||
Defaults to `false`.
|
||||
|
||||
* `:on` - specifies which captures to split the string on, and in what
|
||||
order. Defaults to `:first` which means captures inside the regex do not
|
||||
@@ -439,7 +436,7 @@ defmodule Regex do
|
||||
["abc"]
|
||||
|
||||
iex> Regex.split(~r{}, "abc")
|
||||
["", "a", "b", "c", ""]
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> Regex.split(~r{a(?<second>b)c}, "abc")
|
||||
["", ""]
|
||||
@@ -454,7 +451,7 @@ defmodule Regex do
|
||||
["a", "b", "c"]
|
||||
|
||||
"""
|
||||
@spec split(t, String.t(), [term]) :: [String.t()]
|
||||
@spec split(t, String.t, [term]) :: [String.t]
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{}, "", opts) do
|
||||
@@ -467,29 +464,24 @@ defmodule Regex do
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) and is_list(opts) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
index = parts_to_index(Keyword.get(opts, :parts, :infinity))
|
||||
trim = Keyword.get(opts, :trim, false)
|
||||
include_captures = Keyword.get(opts, :include_captures, false)
|
||||
do_split(matches, string, 0, index, trim, include_captures)
|
||||
|
||||
do_split(matches, string, 0,
|
||||
parts_to_index(Keyword.get(opts, :parts, :infinity)),
|
||||
Keyword.get(opts, :trim, false),
|
||||
Keyword.get(opts, :include_captures, false))
|
||||
:match ->
|
||||
[string]
|
||||
|
||||
:nomatch ->
|
||||
[string]
|
||||
end
|
||||
end
|
||||
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp do_split(_, string, offset, _counter, true, _with_captures)
|
||||
when byte_size(string) <= offset do
|
||||
[]
|
||||
end
|
||||
defp do_split(_, string, offset, _counter, true, _with_captures) when byte_size(string) <= offset,
|
||||
do: []
|
||||
|
||||
defp do_split(_, string, offset, 1, _trim, _with_captures),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
@@ -497,10 +489,8 @@ defmodule Regex do
|
||||
defp do_split([], string, offset, _counter, _trim, _with_captures),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([[{pos, _} | h] | t], string, offset, counter, trim, with_captures)
|
||||
when pos - offset < 0 do
|
||||
do_split([h | t], string, offset, counter, trim, with_captures)
|
||||
end
|
||||
defp do_split([[{pos, _} | h] | t], string, offset, counter, trim, with_captures) when pos - offset < 0,
|
||||
do: do_split([h | t], string, offset, counter, trim, with_captures)
|
||||
|
||||
defp do_split([[] | t], string, offset, counter, trim, with_captures),
|
||||
do: do_split(t, string, offset, counter, trim, with_captures)
|
||||
@@ -509,13 +499,16 @@ defmodule Regex do
|
||||
new_offset = pos + length
|
||||
keep = pos - offset
|
||||
|
||||
<<_::binary-size(offset), part::binary-size(keep), match::binary-size(length), _::binary>> =
|
||||
string
|
||||
|
||||
if keep == 0 and trim do
|
||||
[match | do_split([h | t], string, new_offset, counter - 1, trim, true)]
|
||||
if keep == 0 and length == 0 do
|
||||
do_split([h | t], string, new_offset, counter, trim, true)
|
||||
else
|
||||
[part, match | do_split([h | t], string, new_offset, counter - 1, trim, true)]
|
||||
<<_::binary-size(offset), part::binary-size(keep), match::binary-size(length), _::binary>> = string
|
||||
|
||||
if keep == 0 and (length == 0 or trim) do
|
||||
[match | do_split([h | t], string, new_offset, counter - 1, trim, true)]
|
||||
else
|
||||
[part, match | do_split([h | t], string, new_offset, counter - 1, trim, true)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -523,7 +516,7 @@ defmodule Regex do
|
||||
new_offset = pos + length
|
||||
keep = pos - offset
|
||||
|
||||
if keep == 0 and trim do
|
||||
if keep == 0 and (length == 0 or trim) do
|
||||
do_split([h | t], string, new_offset, counter, trim, false)
|
||||
else
|
||||
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
|
||||
@@ -576,7 +569,7 @@ defmodule Regex do
|
||||
"Abcadc"
|
||||
|
||||
"""
|
||||
@spec replace(t, String.t(), String.t() | (... -> String.t()), [term]) :: String.t()
|
||||
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options)
|
||||
@@ -585,7 +578,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
def replace(regex, string, replacement, options)
|
||||
when is_binary(string) and is_function(replacement) and is_list(options) do
|
||||
when is_binary(string) and is_function(replacement) and is_list(options) do
|
||||
{:arity, arity} = :erlang.fun_info(replacement, :arity)
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
@@ -597,16 +590,15 @@ defmodule Regex do
|
||||
case :re.run(string, compiled, opts) do
|
||||
:nomatch ->
|
||||
string
|
||||
|
||||
{:match, [mlist | t]} when is_list(mlist) ->
|
||||
apply_list(string, replacement, [mlist | t]) |> IO.iodata_to_binary()
|
||||
|
||||
apply_list(string, replacement, [mlist | t]) |> IO.iodata_to_binary
|
||||
{:match, slist} ->
|
||||
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary()
|
||||
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
defp precompile_replacement(""), do: []
|
||||
defp precompile_replacement(""),
|
||||
do: []
|
||||
|
||||
defp precompile_replacement(<<?\\, ?g, ?{, rest::binary>>) when byte_size(rest) > 0 do
|
||||
{ns, <<?}, rest::binary>>} = pick_int(rest)
|
||||
@@ -626,7 +618,6 @@ defmodule Regex do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head::binary>> | t]
|
||||
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
@@ -680,10 +671,8 @@ defmodule Regex do
|
||||
cond do
|
||||
is_binary(part) ->
|
||||
part
|
||||
|
||||
part >= tuple_size(indexes) ->
|
||||
""
|
||||
|
||||
true ->
|
||||
get_index(string, elem(indexes, part))
|
||||
end
|
||||
@@ -723,11 +712,11 @@ defmodule Regex do
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t()) :: String.t()
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) when is_binary(string) do
|
||||
string
|
||||
|> escape(_length = 0, string)
|
||||
|> IO.iodata_to_binary()
|
||||
|> IO.iodata_to_binary
|
||||
end
|
||||
|
||||
@escapable '.^$*+?()[]{}|#-\\\t\n\v\f\r\s'
|
||||
@@ -762,7 +751,7 @@ defmodule Regex do
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(?a), do: ?\a
|
||||
def unescape_map(_), do: false
|
||||
def unescape_map(_), do: false
|
||||
|
||||
# Private Helpers
|
||||
|
||||
@@ -771,15 +760,12 @@ defmodule Regex do
|
||||
defp translate_options(<<?x, t::binary>>, acc), do: translate_options(t, [:extended | acc])
|
||||
defp translate_options(<<?f, t::binary>>, acc), do: translate_options(t, [:firstline | acc])
|
||||
defp translate_options(<<?U, t::binary>>, acc), do: translate_options(t, [:ungreedy | acc])
|
||||
|
||||
defp translate_options(<<?s, t::binary>>, acc),
|
||||
do: translate_options(t, [:dotall, {:newline, :anycrlf} | acc])
|
||||
|
||||
defp translate_options(<<?s, t::binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf} | acc])
|
||||
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline | acc])
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
defp translate_options(<<?r, t::binary>>, acc) do
|
||||
IO.warn("the /r modifier in regular expressions is deprecated, please use /U instead")
|
||||
IO.warn "the /r modifier in regular expressions is deprecated, please use /U instead"
|
||||
translate_options(t, [:ungreedy | acc])
|
||||
end
|
||||
|
||||
|
||||
+45
-117
@@ -178,15 +178,12 @@ defmodule Registry do
|
||||
case key_info!(registry) do
|
||||
{:unique, partitions, key_ets} ->
|
||||
key_ets = key_ets || key_ets!(registry, key, partitions)
|
||||
|
||||
case safe_lookup_second(key_ets, key) do
|
||||
{pid, _} ->
|
||||
if Process.alive?(pid), do: pid, else: :undefined
|
||||
|
||||
_ ->
|
||||
:undefined
|
||||
end
|
||||
|
||||
{kind, _, _} ->
|
||||
raise ArgumentError, ":via is not supported for #{kind} registries"
|
||||
end
|
||||
@@ -259,54 +256,37 @@ defmodule Registry do
|
||||
* `:meta` - a keyword list of metadata to be attached to the registry.
|
||||
|
||||
"""
|
||||
@spec start_link(
|
||||
keys: keys,
|
||||
name: registry,
|
||||
partitions: pos_integer,
|
||||
listeners: [atom],
|
||||
meta: meta
|
||||
) :: {:ok, pid} | {:error, term}
|
||||
when meta: [{meta_key, meta_value}]
|
||||
@spec start_link(keys: keys, name: registry, partitions: pos_integer, listeners: [atom],
|
||||
meta: [{meta_key, meta_value}]) :: {:ok, pid} | {:error, term}
|
||||
def start_link(options) do
|
||||
keys = Keyword.get(options, :keys)
|
||||
|
||||
unless keys in @keys do
|
||||
raise ArgumentError,
|
||||
"expected :keys to be given and be one of :unique or :duplicate, got: #{inspect(keys)}"
|
||||
raise ArgumentError, "expected :keys to be given and be one of :unique or :duplicate, got: #{inspect keys}"
|
||||
end
|
||||
|
||||
name = Keyword.get(options, :name)
|
||||
|
||||
unless is_atom(name) do
|
||||
raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect(name)}"
|
||||
raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect name}"
|
||||
end
|
||||
|
||||
meta = Keyword.get(options, :meta, [])
|
||||
|
||||
unless Keyword.keyword?(meta) do
|
||||
raise ArgumentError, "expected :meta to be a keyword list, got: #{inspect(meta)}"
|
||||
raise ArgumentError, "expected :meta to be a keyword list, got: #{inspect meta}"
|
||||
end
|
||||
|
||||
partitions = Keyword.get(options, :partitions, 1)
|
||||
|
||||
unless is_integer(partitions) and partitions >= 1 do
|
||||
raise ArgumentError,
|
||||
"expected :partitions to be a positive integer, got: #{inspect(partitions)}"
|
||||
raise ArgumentError, "expected :partitions to be a positive integer, got: #{inspect partitions}"
|
||||
end
|
||||
|
||||
listeners = Keyword.get(options, :listeners, [])
|
||||
|
||||
unless is_list(listeners) and Enum.all?(listeners, &is_atom/1) do
|
||||
raise ArgumentError,
|
||||
"expected :listeners to be a list of named processes, got: #{inspect(listeners)}"
|
||||
raise ArgumentError, "expected :listeners to be a list of named processes, got: #{inspect listeners}"
|
||||
end
|
||||
|
||||
# The @info format must be kept in sync with Registry.Partition optimization.
|
||||
entries = [
|
||||
{@all_info, {keys, partitions, nil, nil, listeners}},
|
||||
{@key_info, {keys, partitions, nil}} | meta
|
||||
]
|
||||
|
||||
entries = [{@all_info, {keys, partitions, nil, nil, listeners}},
|
||||
{@key_info, {keys, partitions, nil}} | meta]
|
||||
Registry.Supervisor.start_link(keys, name, partitions, listeners, entries)
|
||||
end
|
||||
|
||||
@@ -324,7 +304,7 @@ defmodule Registry do
|
||||
@doc false
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: Keyword.get(opts, :name, Registry),
|
||||
id: Registry,
|
||||
start: {Registry, :start_link, [opts]},
|
||||
type: :supervisor
|
||||
}
|
||||
@@ -350,13 +330,11 @@ defmodule Registry do
|
||||
[{self(), 2}]
|
||||
|
||||
"""
|
||||
@spec update_value(registry, key, (value -> value)) ::
|
||||
{new_value :: term, old_value :: term} | :error
|
||||
@spec update_value(registry, key, (value -> value)) :: {new_value :: term, old_value :: term} | :error
|
||||
def update_value(registry, key, callback) when is_atom(registry) and is_function(callback, 1) do
|
||||
case key_info!(registry) do
|
||||
{:unique, partitions, key_ets} ->
|
||||
key_ets = key_ets || key_ets!(registry, key, partitions)
|
||||
|
||||
try do
|
||||
:ets.lookup_element(key_ets, key, 2)
|
||||
catch
|
||||
@@ -366,11 +344,9 @@ defmodule Registry do
|
||||
new_value = callback.(old_value)
|
||||
:ets.insert(key_ets, {key, {pid, new_value}})
|
||||
{new_value, old_value}
|
||||
|
||||
{_, _} ->
|
||||
:error
|
||||
end
|
||||
|
||||
{kind, _, _} ->
|
||||
raise ArgumentError, "Registry.update_value/3 is not supported for #{kind} registries"
|
||||
end
|
||||
@@ -403,12 +379,10 @@ defmodule Registry do
|
||||
|> safe_lookup_second(key)
|
||||
|> List.wrap()
|
||||
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
|
||||
|
||||
{:duplicate, 1, key_ets} ->
|
||||
key_ets
|
||||
|> safe_lookup_second(key)
|
||||
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
|
||||
|
||||
{:duplicate, partitions, _} ->
|
||||
if Keyword.get(opts, :parallel, false) do
|
||||
registry
|
||||
@@ -418,55 +392,44 @@ defmodule Registry do
|
||||
dispatch_serial(registry, key, mfa_or_fun, partitions)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp dispatch_serial(_registry, _key, _mfa_or_fun, 0) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp dispatch_serial(registry, key, mfa_or_fun, partition) do
|
||||
partition = partition - 1
|
||||
|
||||
registry
|
||||
|> key_ets!(partition)
|
||||
|> safe_lookup_second(key)
|
||||
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
|
||||
|
||||
dispatch_serial(registry, key, mfa_or_fun, partition)
|
||||
end
|
||||
|
||||
defp dispatch_parallel(_registry, _key, _mfa_or_fun, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp dispatch_parallel(registry, key, mfa_or_fun, partition) do
|
||||
partition = partition - 1
|
||||
parent = self()
|
||||
|
||||
task =
|
||||
Task.async(fn ->
|
||||
registry
|
||||
|> key_ets!(partition)
|
||||
|> safe_lookup_second(key)
|
||||
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
|
||||
|
||||
Process.unlink(parent)
|
||||
:ok
|
||||
end)
|
||||
|
||||
task = Task.async(fn ->
|
||||
registry
|
||||
|> key_ets!(partition)
|
||||
|> safe_lookup_second(key)
|
||||
|> apply_non_empty_to_mfa_or_fun(mfa_or_fun)
|
||||
Process.unlink(parent)
|
||||
:ok
|
||||
end)
|
||||
[task | dispatch_parallel(registry, key, mfa_or_fun, partition)]
|
||||
end
|
||||
|
||||
defp apply_non_empty_to_mfa_or_fun([], _mfa_or_fun) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp apply_non_empty_to_mfa_or_fun(entries, {module, function, args}) do
|
||||
apply(module, function, [entries | args])
|
||||
end
|
||||
|
||||
defp apply_non_empty_to_mfa_or_fun(entries, fun) do
|
||||
fun.(entries)
|
||||
end
|
||||
@@ -511,11 +474,9 @@ defmodule Registry do
|
||||
case key_info!(registry) do
|
||||
{:unique, partitions, key_ets} ->
|
||||
key_ets = key_ets || key_ets!(registry, key, partitions)
|
||||
|
||||
case safe_lookup_second(key_ets, key) do
|
||||
{_, _} = pair ->
|
||||
[pair]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
@@ -571,7 +532,7 @@ defmodule Registry do
|
||||
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
|
||||
|
||||
"""
|
||||
@spec match(registry, key, match_pattern :: term, guards :: list()) :: [{pid, term}]
|
||||
@spec match(registry, key, match_pattern :: atom() | tuple(), guards :: list()) :: [{pid, term}]
|
||||
def match(registry, key, pattern, guards \\ []) when is_atom(registry) and is_list(guards) do
|
||||
guards = [{:"=:=", {:element, 1, :"$_"}, {:const, key}} | guards]
|
||||
spec = [{{:_, {:_, pattern}}, guards, [{:element, 2, :"$_"}]}]
|
||||
@@ -628,13 +589,12 @@ defmodule Registry do
|
||||
{kind, partitions, _, pid_ets, _} = info!(registry)
|
||||
{_, pid_ets} = pid_ets || pid_ets!(registry, pid, partitions)
|
||||
|
||||
keys =
|
||||
try do
|
||||
spec = [{{pid, :"$1", :"$2"}, [], [{{:"$1", :"$2"}}]}]
|
||||
:ets.select(pid_ets, spec)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
end
|
||||
keys = try do
|
||||
spec = [{{pid, :'$1', :'$2'}, [], [{{:'$1', :'$2'}}]}]
|
||||
:ets.select(pid_ets, spec)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
end
|
||||
|
||||
# Handle the possibility of fake keys
|
||||
keys = gather_keys(keys, [], false)
|
||||
@@ -648,15 +608,12 @@ defmodule Registry do
|
||||
defp gather_keys([{key, {_, remaining}} | rest], acc, _fake) do
|
||||
gather_keys(rest, [key | acc], {key, remaining})
|
||||
end
|
||||
|
||||
defp gather_keys([{key, _} | rest], acc, fake) do
|
||||
gather_keys(rest, [key | acc], fake)
|
||||
end
|
||||
|
||||
defp gather_keys([], acc, {key, remaining}) do
|
||||
List.duplicate(key, remaining) ++ Enum.reject(acc, &(&1 === key))
|
||||
List.duplicate(key, remaining) ++ Enum.reject(acc, & &1 === key)
|
||||
end
|
||||
|
||||
defp gather_keys([], acc, false) do
|
||||
acc
|
||||
end
|
||||
@@ -773,8 +730,7 @@ defmodule Registry do
|
||||
total = :ets.select_count(key_ets, total_spec)
|
||||
|
||||
# We only want to delete things that match the pattern
|
||||
delete_spec = [{{:_, {self, pattern}}, [underscore_guard | guards], [true]}]
|
||||
|
||||
delete_spec = [{{:_, {self, pattern}}, [underscore_guard | guards] ,[true]}]
|
||||
case :ets.select_delete(key_ets, delete_spec) do
|
||||
# We deleted everything, we can just delete the object
|
||||
^total ->
|
||||
@@ -855,22 +811,18 @@ defmodule Registry do
|
||||
{pid_server, pid_ets} = pid_ets || pid_ets!(registry, pid_partition)
|
||||
|
||||
# Notice we write first to the pid ets table because it will
|
||||
# always be able to do the cleanup. If we register first to the
|
||||
# always be able to do the clean up. If we register first to the
|
||||
# key one and the process crashes, the key will stay there forever.
|
||||
Process.link(pid_server)
|
||||
true = :ets.insert(pid_ets, {self, key, key_ets})
|
||||
|
||||
case register_key(kind, pid_server, key_ets, key, {key, {self, value}}) do
|
||||
{:ok, _} = ok ->
|
||||
for listener <- listeners do
|
||||
Kernel.send(listener, {:register, registry, key, self, value})
|
||||
end
|
||||
|
||||
ok
|
||||
|
||||
{:error, {:already_registered, ^self}} = error ->
|
||||
error
|
||||
|
||||
{:error, _} = error ->
|
||||
true = :ets.delete_object(pid_ets, {self, key, key_ets})
|
||||
unlink_if_unregistered(pid_server, pid_ets, self)
|
||||
@@ -882,7 +834,6 @@ defmodule Registry do
|
||||
true = :ets.insert(key_ets, entry)
|
||||
{:ok, pid_server}
|
||||
end
|
||||
|
||||
defp register_key(:unique, pid_server, key_ets, key, entry) do
|
||||
if :ets.insert_new(key_ets, entry) do
|
||||
{:ok, pid_server}
|
||||
@@ -897,7 +848,6 @@ defmodule Registry do
|
||||
:ets.delete_object(key_ets, current)
|
||||
register_key(:unique, pid_server, key_ets, key, entry)
|
||||
end
|
||||
|
||||
[] ->
|
||||
register_key(:unique, pid_server, key_ets, key, entry)
|
||||
end
|
||||
@@ -924,7 +874,7 @@ defmodule Registry do
|
||||
:ets.lookup(registry, key)
|
||||
catch
|
||||
:error, :badarg ->
|
||||
raise ArgumentError, "unknown registry: #{inspect(registry)}"
|
||||
raise ArgumentError, "unknown registry: #{inspect registry}"
|
||||
else
|
||||
[{^key, value}] -> {:ok, value}
|
||||
_ -> :error
|
||||
@@ -956,7 +906,7 @@ defmodule Registry do
|
||||
:ok
|
||||
catch
|
||||
:error, :badarg ->
|
||||
raise ArgumentError, "unknown registry: #{inspect(registry)}"
|
||||
raise ArgumentError, "unknown registry: #{inspect registry}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -973,7 +923,7 @@ defmodule Registry do
|
||||
:ets.lookup_element(registry, @all_info, 2)
|
||||
catch
|
||||
:error, :badarg ->
|
||||
raise ArgumentError, "unknown registry: #{inspect(registry)}"
|
||||
raise ArgumentError, "unknown registry: #{inspect registry}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -982,7 +932,7 @@ defmodule Registry do
|
||||
:ets.lookup_element(registry, @key_info, 2)
|
||||
catch
|
||||
:error, :badarg ->
|
||||
raise ArgumentError, "unknown registry: #{inspect(registry)}"
|
||||
raise ArgumentError, "unknown registry: #{inspect registry}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1013,7 +963,6 @@ defmodule Registry do
|
||||
defp partitions(:unique, key, pid, partitions) do
|
||||
{hash(key, partitions), hash(pid, partitions)}
|
||||
end
|
||||
|
||||
defp partitions(:duplicate, _key, pid, partitions) do
|
||||
partition = hash(pid, partitions)
|
||||
{partition, partition}
|
||||
@@ -1031,8 +980,7 @@ defmodule Registry.Supervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(kind, registry, partitions, listeners, entries) do
|
||||
arg = {kind, registry, partitions, listeners, entries}
|
||||
Supervisor.start_link(__MODULE__, arg, name: registry)
|
||||
Supervisor.start_link(__MODULE__, {kind, registry, partitions, listeners, entries}, name: registry)
|
||||
end
|
||||
|
||||
def init({kind, registry, partitions, listeners, entries}) do
|
||||
@@ -1040,7 +988,7 @@ defmodule Registry.Supervisor do
|
||||
true = :ets.insert(registry, entries)
|
||||
|
||||
children =
|
||||
for i <- 0..(partitions - 1) do
|
||||
for i <- 0..partitions - 1 do
|
||||
key_partition = Registry.Partition.key_name(registry, i)
|
||||
pid_partition = Registry.Partition.pid_name(registry, i)
|
||||
arg = {kind, registry, i, partitions, key_partition, pid_partition, listeners}
|
||||
@@ -1107,11 +1055,9 @@ defmodule Registry.Partition do
|
||||
# If we have only one partition, we do an optimization which
|
||||
# is to write the table information alongside the registry info.
|
||||
if partitions == 1 do
|
||||
entries = [
|
||||
{@key_info, {kind, partitions, key_ets}},
|
||||
{@all_info, {kind, partitions, key_ets, {self(), pid_ets}, listeners}}
|
||||
]
|
||||
|
||||
entries =
|
||||
[{@key_info, {kind, partitions, key_ets}},
|
||||
{@all_info, {kind, partitions, key_ets, {self(), pid_ets}, listeners}}]
|
||||
true = :ets.insert(registry, entries)
|
||||
else
|
||||
true = :ets.insert(registry, {i, key_ets, {self(), pid_ets}})
|
||||
@@ -1125,25 +1071,14 @@ defmodule Registry.Partition do
|
||||
defp init_key_ets(:unique, key_partition) do
|
||||
:ets.new(key_partition, [:set, :public, read_concurrency: true, write_concurrency: true])
|
||||
end
|
||||
|
||||
defp init_key_ets(:duplicate, key_partition) do
|
||||
:ets.new(key_partition, [
|
||||
:duplicate_bag,
|
||||
:public,
|
||||
read_concurrency: true,
|
||||
write_concurrency: true
|
||||
])
|
||||
:ets.new(key_partition, [:duplicate_bag, :public, read_concurrency: true, write_concurrency: true])
|
||||
end
|
||||
|
||||
# A process can always have multiple keys, so the
|
||||
# pid partition is always a duplicate bag.
|
||||
defp init_pid_ets(_, pid_partition) do
|
||||
:ets.new(pid_partition, [
|
||||
:duplicate_bag,
|
||||
:public,
|
||||
read_concurrency: true,
|
||||
write_concurrency: true
|
||||
])
|
||||
:ets.new(pid_partition, [:duplicate_bag, :public, read_concurrency: true, write_concurrency: true])
|
||||
end
|
||||
|
||||
def handle_call(:sync, _, state) do
|
||||
@@ -1152,17 +1087,12 @@ defmodule Registry.Partition do
|
||||
|
||||
def handle_info({:EXIT, pid, _reason}, ets) do
|
||||
entries = :ets.take(ets, pid)
|
||||
|
||||
for {_pid, key, key_ets} <- entries do
|
||||
key_ets =
|
||||
case key_ets do
|
||||
# In case the fake key ets is being used. See unregister_match/2.
|
||||
{key_ets, _} ->
|
||||
key_ets
|
||||
|
||||
_ ->
|
||||
key_ets
|
||||
end
|
||||
key_ets = case key_ets do
|
||||
# In case the fake key ets is being used. See unregister_match/2.
|
||||
{key_ets, _} -> key_ets
|
||||
_ -> key_ets
|
||||
end
|
||||
|
||||
try do
|
||||
:ets.match_delete(key_ets, {key, {pid, :_}})
|
||||
@@ -1170,10 +1100,8 @@ defmodule Registry.Partition do
|
||||
:error, :badarg -> :badarg
|
||||
end
|
||||
end
|
||||
|
||||
{:noreply, ets}
|
||||
end
|
||||
|
||||
def handle_info(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
+15
-15
@@ -15,8 +15,10 @@ defmodule Set do
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
case unquote(set) do
|
||||
%module{} -> module
|
||||
set -> unsupported_set(set)
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,8 +36,7 @@ defmodule Set do
|
||||
else
|
||||
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.delete(acc, v)}
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,10 +50,9 @@ defmodule Set do
|
||||
Enumerable.reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,6 +77,7 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def intersection(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -86,15 +87,16 @@ defmodule Set do
|
||||
else
|
||||
Enumerable.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def member?(set, value) do
|
||||
target(set).member?(set, value)
|
||||
end
|
||||
|
||||
|
||||
def put(set, value) do
|
||||
target(set).put(set, value)
|
||||
end
|
||||
@@ -127,8 +129,7 @@ defmodule Set do
|
||||
else
|
||||
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.put(acc, v)}
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,13 +137,12 @@ defmodule Set do
|
||||
Enumerable.reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
raise ArgumentError, "unsupported set: #{inspect(set)}"
|
||||
raise ArgumentError, "unsupported set: #{inspect set}"
|
||||
end
|
||||
end
|
||||
|
||||
+178
-276
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,7 @@ defmodule Stream.Reducers do
|
||||
|
||||
def chunk_every(chunk_by, enumerable, count, step, leftover) do
|
||||
limit = :erlang.max(count, step)
|
||||
|
||||
chunk_fun = fn entry, {acc_buffer, acc_count} ->
|
||||
chunk_by.(enumerable, {[], 0}, fn entry, {acc_buffer, acc_count} ->
|
||||
acc_buffer = [entry | acc_buffer]
|
||||
acc_count = acc_count + 1
|
||||
|
||||
@@ -22,44 +21,46 @@ defmodule Stream.Reducers do
|
||||
else
|
||||
{:cont, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
after_fun = fn {acc_buffer, acc_count} ->
|
||||
if leftover == :discard or acc_count == 0 or (step > count and acc_count >= count) do
|
||||
end, fn {acc_buffer, acc_count} ->
|
||||
if leftover == :discard or acc_count == 0 do
|
||||
{:cont, []}
|
||||
else
|
||||
{:cont, :lists.reverse(acc_buffer, Enum.take(leftover, count - acc_count)), []}
|
||||
end
|
||||
end
|
||||
|
||||
chunk_by.(enumerable, {[], 0}, chunk_fun, after_fun)
|
||||
end)
|
||||
end
|
||||
|
||||
def chunk_by(chunk_by, enumerable, fun) do
|
||||
chunk_fun = fn
|
||||
chunk_by.(enumerable, nil, fn
|
||||
entry, nil ->
|
||||
{:cont, {[entry], fun.(entry)}}
|
||||
|
||||
entry, {acc, value} ->
|
||||
case fun.(entry) do
|
||||
^value -> {:cont, {[entry | acc], value}}
|
||||
new_value -> {:cont, :lists.reverse(acc), {[entry], new_value}}
|
||||
end
|
||||
end
|
||||
|
||||
after_fun = fn
|
||||
end, fn
|
||||
nil -> {:cont, :done}
|
||||
{acc, _value} -> {:cont, :lists.reverse(acc), :done}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
chunk_by.(enumerable, nil, chunk_fun, after_fun)
|
||||
defmacro chunk_while(callback, fun \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(head, acc, tail) ->
|
||||
case unquote(callback).(entry, acc) do
|
||||
{:cont, emit, acc} -> next_with_acc(unquote(fun), emit, head, acc, tail)
|
||||
{:cont, acc} -> skip(acc(head, acc, tail))
|
||||
{:halt, acc} -> {:halt, acc(head, acc, tail)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro dedup(callback, fun \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(head, prev, tail) = acc ->
|
||||
value = unquote(callback).(entry)
|
||||
|
||||
case prev do
|
||||
{:value, ^value} -> skip(acc)
|
||||
_ -> next_with_acc(unquote(fun), entry, head, {:value, value}, tail)
|
||||
@@ -73,7 +74,6 @@ defmodule Stream.Reducers do
|
||||
fn
|
||||
_entry, acc(head, amount, tail) when amount > 0 ->
|
||||
skip(acc(head, amount - 1, tail))
|
||||
|
||||
entry, acc(head, amount, tail) ->
|
||||
next_with_acc(unquote(fun), entry, head, amount, tail)
|
||||
end
|
||||
@@ -85,7 +85,6 @@ defmodule Stream.Reducers do
|
||||
fn
|
||||
entry, acc(head, curr, tail) when curr in [unquote(nth), :first] ->
|
||||
skip(acc(head, 1, tail))
|
||||
|
||||
entry, acc(head, curr, tail) ->
|
||||
next_with_acc(unquote(fun), entry, head, curr + 1, tail)
|
||||
end
|
||||
@@ -141,7 +140,6 @@ defmodule Stream.Reducers do
|
||||
fn
|
||||
entry, acc(head, curr, tail) when curr in [unquote(nth), :first] ->
|
||||
next_with_acc(unquote(fun), unquote(mapper).(entry), head, 1, tail)
|
||||
|
||||
entry, acc(head, curr, tail) ->
|
||||
next_with_acc(unquote(fun), entry, head, curr + 1, tail)
|
||||
end
|
||||
@@ -165,7 +163,6 @@ defmodule Stream.Reducers do
|
||||
fn
|
||||
entry, acc(head, :first, tail) ->
|
||||
next_with_acc(unquote(fun), entry, head, {:ok, entry}, tail)
|
||||
|
||||
entry, acc(head, {:ok, acc}, tail) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
next_with_acc(unquote(fun), value, head, {:ok, value}, tail)
|
||||
@@ -188,11 +185,9 @@ defmodule Stream.Reducers do
|
||||
case curr do
|
||||
0 ->
|
||||
{:halt, original}
|
||||
|
||||
1 ->
|
||||
{_, acc} = next_with_acc(unquote(fun), entry, head, 0, tail)
|
||||
{:halt, acc}
|
||||
|
||||
_ ->
|
||||
next_with_acc(unquote(fun), entry, head, curr - 1, tail)
|
||||
end
|
||||
@@ -205,7 +200,6 @@ defmodule Stream.Reducers do
|
||||
fn
|
||||
entry, acc(head, curr, tail) when curr in [unquote(nth), :first] ->
|
||||
next_with_acc(unquote(fun), entry, head, 1, tail)
|
||||
|
||||
entry, acc(head, curr, tail) ->
|
||||
skip(acc(head, curr + 1, tail))
|
||||
end
|
||||
@@ -228,7 +222,6 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn entry, acc(head, prev, tail) = original ->
|
||||
value = unquote(callback).(entry)
|
||||
|
||||
if Map.has_key?(prev, value) do
|
||||
skip(original)
|
||||
else
|
||||
|
||||
+84
-279
@@ -7,7 +7,7 @@ defmodule String do
|
||||
## Codepoints and grapheme cluster
|
||||
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 10.0.0.
|
||||
Standard, version 9.0.0.
|
||||
|
||||
As per the standard, a codepoint is a single Unicode Character,
|
||||
which may be represented by one or more bytes.
|
||||
@@ -206,9 +206,7 @@ defmodule String do
|
||||
@type t :: binary
|
||||
@type codepoint :: t
|
||||
@type grapheme :: t
|
||||
@type pattern :: t | [t] | :binary.cp()
|
||||
|
||||
@conditional_mappings [:greek]
|
||||
@type pattern :: t | [t] | :binary.cp
|
||||
|
||||
@doc """
|
||||
Checks if a string contains only printable characters.
|
||||
@@ -240,13 +238,11 @@ defmodule String do
|
||||
printable?(rest, decrement(counter))
|
||||
end
|
||||
end
|
||||
|
||||
for char <- '\n\r\t\v\b\f\e\d\a' do
|
||||
def printable?(<<unquote(char), rest::binary>>, counter) do
|
||||
printable?(rest, decrement(counter))
|
||||
end
|
||||
end
|
||||
|
||||
def printable?(<<char::utf8, rest::binary>>, counter)
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
@@ -257,7 +253,7 @@ defmodule String do
|
||||
def printable?(binary, _) when is_binary(binary), do: false
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc ~S"""
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
@@ -301,7 +297,7 @@ defmodule String do
|
||||
## Options
|
||||
|
||||
* `:parts` (positive integer or `:infinity`) - the string
|
||||
is split into at most as many parts as this option specifies.
|
||||
is split into at most as many parts as this options specifies.
|
||||
If `:infinity`, the string will be split into all possible
|
||||
parts. Defaults to `:infinity`.
|
||||
|
||||
@@ -343,19 +339,19 @@ defmodule String do
|
||||
iex> String.split("abc", ~r{b}, include_captures: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty string returns graphemes:
|
||||
Splitting on empty patterns returns graphemes:
|
||||
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "")
|
||||
["", "a", "b", "c", ""]
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("abc", "", parts: 1)
|
||||
["abc"]
|
||||
|
||||
iex> String.split("abc", "", parts: 3)
|
||||
["", "a", "bc"]
|
||||
iex> String.split("abc", "", parts: 2)
|
||||
["a", "bc"]
|
||||
|
||||
A precompiled pattern can also be given:
|
||||
|
||||
@@ -363,70 +359,34 @@ defmodule String do
|
||||
iex> String.split("1,2 3,4", pattern)
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
Note this function can split within or across grapheme boundaries.
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns true:
|
||||
|
||||
iex> String.split(String.normalize("é", :nfd), "e")
|
||||
["", "́"]
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return false:
|
||||
|
||||
iex> String.split(String.normalize("é", :nfc), "e")
|
||||
["é"]
|
||||
|
||||
"""
|
||||
@spec split(t, pattern | Regex.t(), keyword) :: [t]
|
||||
@spec split(t, pattern | Regex.t, keyword) :: [t]
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
def split(string, %Regex{} = pattern, options) when is_binary(string) do
|
||||
Regex.split(pattern, string, options)
|
||||
end
|
||||
|
||||
def split(string, "", options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
index = parts_to_index(parts)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
|
||||
if trim == false and index != 1 do
|
||||
["" | split_empty(string, trim, index - 1)]
|
||||
else
|
||||
split_empty(string, trim, index)
|
||||
end
|
||||
end
|
||||
|
||||
def split(string, pattern, []) when is_tuple(pattern) or is_binary(string) do
|
||||
def split(string, pattern, []) when is_binary(string) and pattern != "" do
|
||||
:binary.split(string, pattern, [:global])
|
||||
end
|
||||
|
||||
def split(string, pattern, options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
pattern = maybe_compile_pattern(pattern)
|
||||
split_each(string, pattern, trim, parts_to_index(parts))
|
||||
end
|
||||
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp split_empty("", true, 1), do: []
|
||||
defp split_empty(string, _, 1), do: [string]
|
||||
|
||||
defp split_empty(string, trim, count) do
|
||||
case next_grapheme(string) do
|
||||
{h, t} -> [h | split_empty(t, trim, count - 1)]
|
||||
nil -> split_empty("", trim, 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp split_each("", _pattern, true, 1), do: []
|
||||
defp split_each(string, _pattern, _trim, 1) when is_binary(string), do: [string]
|
||||
|
||||
defp split_each(string, pattern, trim, count) do
|
||||
case do_splitter(string, pattern, trim) do
|
||||
{h, t} -> [h | split_each(t, pattern, trim, count - 1)]
|
||||
nil -> []
|
||||
nil -> []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -451,37 +411,26 @@ defmodule String do
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
iex> String.splitter("abcd", "") |> Enum.take(10)
|
||||
["", "a", "b", "c", "d", ""]
|
||||
["a", "b", "c", "d", ""]
|
||||
|
||||
iex> String.splitter("abcd", "", trim: true) |> Enum.take(10)
|
||||
["a", "b", "c", "d"]
|
||||
|
||||
"""
|
||||
@spec splitter(t, pattern, keyword) :: Enumerable.t()
|
||||
def splitter(string, pattern, options \\ [])
|
||||
|
||||
def splitter(string, "", options) do
|
||||
if Keyword.get(options, :trim, false) do
|
||||
Stream.unfold(string, &next_grapheme/1)
|
||||
else
|
||||
Stream.unfold(:match, &do_empty_splitter(&1, string))
|
||||
end
|
||||
end
|
||||
|
||||
def splitter(string, pattern, options) do
|
||||
@spec splitter(t, pattern, keyword) :: Enumerable.t
|
||||
def splitter(string, pattern, options \\ []) do
|
||||
pattern = maybe_compile_pattern(pattern)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
Stream.unfold(string, &do_splitter(&1, pattern, trim))
|
||||
end
|
||||
|
||||
defp do_empty_splitter(:match, string), do: {"", string}
|
||||
defp do_empty_splitter(:nomatch, _string), do: nil
|
||||
defp do_empty_splitter("", _), do: {"", :nomatch}
|
||||
defp do_empty_splitter(string, _), do: next_grapheme(string)
|
||||
|
||||
defp do_splitter(:nomatch, _pattern, _), do: nil
|
||||
defp do_splitter("", _pattern, false), do: {"", :nomatch}
|
||||
defp do_splitter("", _pattern, true), do: nil
|
||||
defp do_splitter("", _pattern, true), do: nil
|
||||
defp do_splitter("", _pattern, false), do: {"", :nomatch}
|
||||
|
||||
defp do_splitter(bin, "", _trim) do
|
||||
next_grapheme(bin)
|
||||
end
|
||||
|
||||
defp do_splitter(bin, pattern, trim) do
|
||||
case :binary.split(bin, pattern) do
|
||||
@@ -491,6 +440,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_compile_pattern(""), do: ""
|
||||
defp maybe_compile_pattern(pattern) when is_tuple(pattern), do: pattern
|
||||
defp maybe_compile_pattern(pattern), do: :binary.compile_pattern(pattern)
|
||||
|
||||
@@ -533,9 +483,8 @@ defmodule String do
|
||||
|
||||
def split_at(string, position) when is_integer(position) and position < 0 do
|
||||
position = length(string) + position
|
||||
|
||||
case position >= 0 do
|
||||
true -> do_split_at(string, position)
|
||||
true -> do_split_at(string, position)
|
||||
false -> {"", string}
|
||||
end
|
||||
end
|
||||
@@ -606,12 +555,7 @@ defmodule String do
|
||||
defdelegate normalize(string, form), to: String.Normalizer
|
||||
|
||||
@doc """
|
||||
Converts all characters in the given string to uppercase according to `mode`.
|
||||
|
||||
`mode` may be `:default`, `:ascii` or `:greek`. The `:default` mode considers
|
||||
all non-conditional transformations outlined in the Unicode standard. `:ascii`
|
||||
uppercases only the letters a to z. `:greek` includes the context sensitive
|
||||
mappings found in Greek.
|
||||
Converts all characters in the given string to uppercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -624,42 +568,12 @@ defmodule String do
|
||||
iex> String.upcase("olá")
|
||||
"OLÁ"
|
||||
|
||||
The `:ascii` mode ignores Unicode characters and provides a more
|
||||
performant implementation when you know the string contains only
|
||||
ASCII characters:
|
||||
|
||||
iex> String.upcase("olá", :ascii)
|
||||
"OLá"
|
||||
|
||||
"""
|
||||
@spec upcase(t, :default | :ascii | :greek) :: t
|
||||
def upcase(string, mode \\ :default)
|
||||
|
||||
def upcase("", _mode) do
|
||||
""
|
||||
end
|
||||
|
||||
def upcase(string, :default) when is_binary(string) do
|
||||
String.Casing.upcase(string, [], :default)
|
||||
end
|
||||
|
||||
def upcase(string, :ascii) when is_binary(string) do
|
||||
for <<x <- string>>,
|
||||
do: if(x >= ?a and x <= ?z, do: <<x - 32>>, else: <<x>>),
|
||||
into: ""
|
||||
end
|
||||
|
||||
def upcase(string, mode) when mode in @conditional_mappings do
|
||||
String.Casing.upcase(string, [], mode)
|
||||
end
|
||||
@spec upcase(t) :: t
|
||||
defdelegate upcase(binary), to: String.Casing
|
||||
|
||||
@doc """
|
||||
Converts all characters in the given string to lowercase according to `mode`.
|
||||
|
||||
`mode` may be `:default`, `:ascii` or `:greek`. The `:default` mode considers
|
||||
all non-conditional transformations outlined in the Unicode standard. `:ascii`
|
||||
lowercases only the letters A to Z. `:greek` includes the context sensitive
|
||||
mappings found in Greek.
|
||||
Converts all characters in the given string to lowercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -672,51 +586,18 @@ defmodule String do
|
||||
iex> String.downcase("OLÁ")
|
||||
"olá"
|
||||
|
||||
The `:ascii` mode ignores Unicode characters and provides a more
|
||||
performant implementation when you know the string contains only
|
||||
ASCII characters:
|
||||
|
||||
iex> String.downcase("OLÁ", :ascii)
|
||||
"olÁ"
|
||||
|
||||
And `:greek` properly handles the context sensitive sigma in Greek:
|
||||
|
||||
iex> String.downcase("ΣΣ")
|
||||
"σσ"
|
||||
|
||||
iex> String.downcase("ΣΣ", :greek)
|
||||
"σς"
|
||||
|
||||
"""
|
||||
@spec downcase(t, :default | :ascii | :greek) :: t
|
||||
def downcase(string, mode \\ :default)
|
||||
|
||||
def downcase("", _mode) do
|
||||
""
|
||||
end
|
||||
|
||||
def downcase(string, :default) when is_binary(string) do
|
||||
String.Casing.downcase(string, [], :default)
|
||||
end
|
||||
|
||||
def downcase(string, :ascii) when is_binary(string) do
|
||||
for <<x <- string>>,
|
||||
do: if(x >= ?A and x <= ?Z, do: <<x + 32>>, else: <<x>>),
|
||||
into: ""
|
||||
end
|
||||
|
||||
def downcase(string, mode) when mode in @conditional_mappings do
|
||||
String.Casing.downcase(string, [], mode)
|
||||
end
|
||||
@spec downcase(t) :: t
|
||||
defdelegate downcase(binary), to: String.Casing
|
||||
|
||||
@doc """
|
||||
Converts the first character in the given string to
|
||||
uppercase and the remainder to lowercase according to `mode`.
|
||||
uppercase and the remainder to lowercase.
|
||||
|
||||
`mode` may be `:default`, `:ascii` or `:greek`. The `:default` mode considers
|
||||
all non-conditional transformations outlined in the Unicode standard. `:ascii`
|
||||
lowercases only the letters A to Z. `:greek` includes the context sensitive
|
||||
mappings found in Greek.
|
||||
This relies on the titlecase information provided
|
||||
by the Unicode Standard. Note this function makes
|
||||
no attempt to capitalize all words in the string
|
||||
(usually known as titlecase).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -730,17 +611,10 @@ defmodule String do
|
||||
"Olá"
|
||||
|
||||
"""
|
||||
@spec capitalize(t, :default | :ascii | :greek) :: t
|
||||
def capitalize(string, mode \\ :default)
|
||||
|
||||
def capitalize(<<char, rest::binary>>, :ascii) do
|
||||
char = if char >= ?a and char <= ?z, do: char - 32, else: char
|
||||
<<char>> <> downcase(rest, :ascii)
|
||||
end
|
||||
|
||||
def capitalize(string, mode) when is_binary(string) do
|
||||
{char, rest} = String.Casing.titlecase_once(string, mode)
|
||||
char <> downcase(rest, mode)
|
||||
@spec capitalize(t) :: t
|
||||
def capitalize(string) when is_binary(string) do
|
||||
{char, rest} = String.Casing.titlecase_once(string)
|
||||
char <> downcase(rest)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -790,19 +664,10 @@ defmodule String do
|
||||
replace_leading(string, match, replacement, prefix_size, suffix_size, 0)
|
||||
end
|
||||
|
||||
defp replace_leading(string, match, replacement, prefix_size, suffix_size, acc)
|
||||
when suffix_size >= 0 do
|
||||
defp replace_leading(string, match, replacement, prefix_size, suffix_size, acc) when suffix_size >= 0 do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::binary>> when prefix == match ->
|
||||
replace_leading(
|
||||
suffix,
|
||||
match,
|
||||
replacement,
|
||||
prefix_size,
|
||||
suffix_size - prefix_size,
|
||||
acc + 1
|
||||
)
|
||||
|
||||
replace_leading(suffix, match, replacement, prefix_size, suffix_size - prefix_size, acc + 1)
|
||||
_ ->
|
||||
prepend_unless_empty(duplicate(replacement, acc), string)
|
||||
end
|
||||
@@ -847,19 +712,10 @@ defmodule String do
|
||||
replace_trailing(string, match, replacement, prefix_size, suffix_size, 0)
|
||||
end
|
||||
|
||||
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc)
|
||||
when prefix_size >= 0 do
|
||||
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc) when prefix_size >= 0 do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::binary>> when suffix == match ->
|
||||
replace_trailing(
|
||||
prefix,
|
||||
match,
|
||||
replacement,
|
||||
prefix_size - suffix_size,
|
||||
suffix_size,
|
||||
acc + 1
|
||||
)
|
||||
|
||||
replace_trailing(prefix, match, replacement, prefix_size - suffix_size, suffix_size, acc + 1)
|
||||
_ ->
|
||||
append_unless_empty(string, duplicate(replacement, acc))
|
||||
end
|
||||
@@ -903,7 +759,6 @@ defmodule String do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::binary>> when prefix == match ->
|
||||
prepend_unless_empty(replacement, suffix)
|
||||
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
@@ -944,7 +799,6 @@ defmodule String do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::binary>> when suffix == match ->
|
||||
append_unless_empty(prefix, replacement)
|
||||
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
@@ -1164,17 +1018,15 @@ defmodule String do
|
||||
|
||||
defp pad(kind, string, count, padding) do
|
||||
string_len = length(string)
|
||||
|
||||
if string_len >= count do
|
||||
string
|
||||
else
|
||||
filler = build_filler(count - string_len, padding, padding, 0, [])
|
||||
|
||||
case kind do
|
||||
:leading -> [filler | string]
|
||||
:trailing -> [string | filler]
|
||||
end
|
||||
|> IO.iodata_to_binary()
|
||||
|> IO.iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1184,17 +1036,15 @@ defmodule String do
|
||||
rem_filler =
|
||||
rem(count, size)
|
||||
|> build_filler(source, source, 0, [])
|
||||
|
||||
filler =
|
||||
filler
|
||||
|> IO.iodata_to_binary()
|
||||
|> IO.iodata_to_binary
|
||||
|> duplicate(div(count, size) + 1)
|
||||
|
||||
[filler | rem_filler]
|
||||
end
|
||||
|
||||
defp build_filler(count, source, [elem | rest], size, filler)
|
||||
when is_binary(elem) do
|
||||
when is_binary(elem) do
|
||||
build_filler(count - 1, source, rest, size + 1, [filler | elem])
|
||||
end
|
||||
|
||||
@@ -1271,31 +1121,9 @@ defmodule String do
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
When an empty string is provided as a `pattern`, the function will treat it as
|
||||
an implicit empty string between each grapheme and the string will be
|
||||
interspersed. If an empty string is provided as `replacement` the `subject`
|
||||
will be returned:
|
||||
|
||||
iex> String.replace("ELIXIR", "", ".")
|
||||
".E.L.I.X.I.R."
|
||||
|
||||
iex> String.replace("ELIXIR", "", "")
|
||||
"ELIXIR"
|
||||
|
||||
"""
|
||||
@spec replace(t, pattern | Regex.t(), t, keyword) :: t
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
def replace(subject, "", "", _), do: subject
|
||||
|
||||
def replace(subject, "", replacement, options) do
|
||||
if Keyword.get(options, :global, true) do
|
||||
IO.iodata_to_binary([replacement | intersperse(subject, replacement)])
|
||||
else
|
||||
replacement <> subject
|
||||
end
|
||||
end
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_binary(replacement) do
|
||||
@spec replace(t, pattern | Regex.t, t, keyword) :: t
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
else
|
||||
@@ -1304,15 +1132,11 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
defp intersperse(subject, replacement) do
|
||||
case next_grapheme(subject) do
|
||||
{current, rest} -> [current, replacement | intersperse(rest, replacement)]
|
||||
nil -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
global = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
global =
|
||||
if Keyword.get(options, :global) != false,
|
||||
do: [:global],
|
||||
else: []
|
||||
|
||||
insert =
|
||||
if insert = Keyword.get(options, :insert_replaced),
|
||||
@@ -1521,7 +1345,6 @@ defmodule String do
|
||||
|
||||
defp do_chunk(string, acc, chunk, flag, pred_fn) do
|
||||
{cp, rest} = next_codepoint(string)
|
||||
|
||||
if pred_fn.(cp) != flag do
|
||||
do_chunk(rest, [chunk | acc], cp, not flag, pred_fn)
|
||||
else
|
||||
@@ -1575,7 +1398,7 @@ defmodule String do
|
||||
def next_grapheme(binary) do
|
||||
case next_grapheme_size(binary) do
|
||||
{size, rest} -> {:binary.part(binary, 0, size), rest}
|
||||
nil -> nil
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1685,16 +1508,15 @@ defmodule String do
|
||||
|
||||
def at(string, position) when is_integer(position) and position < 0 do
|
||||
position = length(string) + position
|
||||
|
||||
case position >= 0 do
|
||||
true -> do_at(string, position)
|
||||
true -> do_at(string, position)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp do_at(string, position) do
|
||||
case String.Unicode.split_at(string, position) do
|
||||
{_, nil} -> nil
|
||||
{_, nil} -> nil
|
||||
{_, rest} -> first(rest)
|
||||
end
|
||||
end
|
||||
@@ -1744,9 +1566,7 @@ defmodule String do
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
case String.Unicode.split_at(string, start) do
|
||||
{_, nil} ->
|
||||
""
|
||||
|
||||
{_, nil} -> ""
|
||||
{start_bytes, rest} ->
|
||||
{len_bytes, _} = String.Unicode.split_at(rest, len)
|
||||
binary_part(string, start_bytes, len_bytes)
|
||||
@@ -1755,9 +1575,8 @@ defmodule String do
|
||||
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
start = length(string) + start
|
||||
|
||||
case start >= 0 do
|
||||
true -> slice(string, start, len)
|
||||
true -> slice(string, start, len)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
@@ -1810,7 +1629,7 @@ defmodule String do
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t()) :: t
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
@@ -1820,7 +1639,6 @@ defmodule String do
|
||||
case String.Unicode.split_at(string, first) do
|
||||
{_, nil} ->
|
||||
""
|
||||
|
||||
{start_bytes, _} ->
|
||||
binary_part(string, start_bytes, byte_size(string) - start_bytes)
|
||||
end
|
||||
@@ -1838,12 +1656,12 @@ defmodule String do
|
||||
{bytes, length} = do_acc_bytes(next_grapheme_size(string), [], 0)
|
||||
|
||||
first = add_if_negative(first, length)
|
||||
last = add_if_negative(last, length)
|
||||
last = add_if_negative(last, length)
|
||||
|
||||
if first < 0 or first > last or first > length do
|
||||
""
|
||||
else
|
||||
last = min(last + 1, length)
|
||||
last = min(last + 1, length)
|
||||
bytes = Enum.drop(bytes, length - last)
|
||||
first = last - first
|
||||
{length_bytes, start_bytes} = Enum.split(bytes, first)
|
||||
@@ -1936,7 +1754,7 @@ defmodule String do
|
||||
string_size = byte_size(string)
|
||||
suffix_size = byte_size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
suffix_size <= string_size and :nomatch != :binary.match(string, suffix, scope: scope)
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1951,7 +1769,7 @@ defmodule String do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, Regex.t()) :: boolean
|
||||
@spec match?(t, Regex.t) :: boolean
|
||||
def match?(string, regex) do
|
||||
Regex.match?(regex, string)
|
||||
end
|
||||
@@ -1983,19 +1801,6 @@ defmodule String do
|
||||
iex> String.contains? "elixir of life", pattern
|
||||
true
|
||||
|
||||
Note this function can match within or across grapheme boundaries.
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns true:
|
||||
|
||||
iex> String.contains?(String.normalize("é", :nfd), "e")
|
||||
true
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return false:
|
||||
|
||||
iex> String.contains?(String.normalize("é", :nfc), "e")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec contains?(t, pattern) :: boolean
|
||||
def contains?(string, []) when is_binary(string) do
|
||||
@@ -2061,7 +1866,7 @@ defmodule String do
|
||||
:my_atom
|
||||
|
||||
"""
|
||||
@spec to_atom(String.t()) :: atom
|
||||
@spec to_atom(String.t) :: atom
|
||||
def to_atom(string) do
|
||||
:erlang.binary_to_atom(string, :utf8)
|
||||
end
|
||||
@@ -2084,7 +1889,7 @@ defmodule String do
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
@spec to_existing_atom(String.t()) :: atom
|
||||
@spec to_existing_atom(String.t) :: atom
|
||||
def to_existing_atom(string) do
|
||||
:erlang.binary_to_existing_atom(string, :utf8)
|
||||
end
|
||||
@@ -2100,7 +1905,7 @@ defmodule String do
|
||||
123
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t()) :: integer
|
||||
@spec to_integer(String.t) :: integer
|
||||
def to_integer(string) do
|
||||
:erlang.binary_to_integer(string)
|
||||
end
|
||||
@@ -2116,7 +1921,7 @@ defmodule String do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t(), 2..36) :: integer
|
||||
@spec to_integer(String.t, 2..36) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
@@ -2142,7 +1947,7 @@ defmodule String do
|
||||
#=> ** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
@spec to_float(String.t()) :: float
|
||||
@spec to_float(String.t) :: float
|
||||
def to_float(string) do
|
||||
:erlang.binary_to_float(string)
|
||||
end
|
||||
@@ -2174,11 +1979,11 @@ defmodule String do
|
||||
{chars2, len2} = chars_and_length(string2)
|
||||
|
||||
case match(chars1, len1, chars2, len2) do
|
||||
{0, _trans} ->
|
||||
0.0
|
||||
|
||||
{0, _trans} -> 0.0
|
||||
{comm, trans} ->
|
||||
(comm / len1 + comm / len2 + (comm - trans) / comm) / 3
|
||||
((comm / len1) +
|
||||
(comm / len2) +
|
||||
((comm - trans) / comm)) / 3
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2213,9 +2018,7 @@ defmodule String do
|
||||
|
||||
defp submatch(char, chars, {pre, _} = range, state, idx) do
|
||||
case detect(char, chars, range) do
|
||||
nil ->
|
||||
{chars, state}
|
||||
|
||||
nil -> {chars, state}
|
||||
{subidx, chars} ->
|
||||
{chars, proceed(state, idx - pre + subidx)}
|
||||
end
|
||||
@@ -2226,9 +2029,10 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp detect(_char, _chars, 0, _idx, _acc), do: nil
|
||||
defp detect(_char, [], _lim, _idx, _acc), do: nil
|
||||
defp detect(_char, [], _lim, _idx, _acc), do: nil
|
||||
|
||||
defp detect(char, [char | rest], _lim, idx, acc), do: {idx, Enum.reverse(acc, [nil | rest])}
|
||||
defp detect(char, [char | rest], _lim, idx, acc),
|
||||
do: {idx, Enum.reverse(acc, [nil | rest])}
|
||||
|
||||
defp detect(char, [other | rest], lim, idx, acc),
|
||||
do: detect(char, rest, lim - 1, idx + 1, [other | acc])
|
||||
@@ -2256,9 +2060,10 @@ defmodule String do
|
||||
"""
|
||||
@spec myers_difference(t, t) :: [{:eq | :ins | :del, t}] | nil
|
||||
def myers_difference(string1, string2) do
|
||||
graphemes(string1)
|
||||
|> List.myers_difference(graphemes(string2))
|
||||
|> Enum.map(fn {kind, chars} -> {kind, IO.iodata_to_binary(chars)} end)
|
||||
List.myers_difference(graphemes(string1), graphemes(string2))
|
||||
|> Enum.map(fn {kind, chars} ->
|
||||
{kind, IO.iodata_to_binary(chars)}
|
||||
end)
|
||||
end
|
||||
|
||||
# TODO: Remove by 2.0
|
||||
|
||||
@@ -18,7 +18,7 @@ defprotocol String.Chars do
|
||||
@doc """
|
||||
Converts `term` to a string.
|
||||
"""
|
||||
@spec to_string(t) :: String.t()
|
||||
@spec to_string(t) :: String.t
|
||||
def to_string(term)
|
||||
end
|
||||
|
||||
@@ -39,9 +39,9 @@ defimpl String.Chars, for: BitString do
|
||||
|
||||
def to_string(term) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: term,
|
||||
description: "cannot convert a bitstring to a string"
|
||||
protocol: @protocol,
|
||||
value: term,
|
||||
description: "cannot convert a bitstring to a string"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+104
-113
@@ -99,156 +99,150 @@ defmodule StringIO do
|
||||
|
||||
## callbacks
|
||||
|
||||
@impl true
|
||||
def init({string, options}) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:io_request, from, reply_as, req}, state) do
|
||||
state = io_request(from, reply_as, req, state)
|
||||
{:noreply, state}
|
||||
def handle_info({:io_request, from, reply_as, req}, s) do
|
||||
s = io_request(from, reply_as, req, s)
|
||||
{:noreply, s}
|
||||
end
|
||||
|
||||
def handle_info(message, state) do
|
||||
super(message, state)
|
||||
def handle_info(msg, s) do
|
||||
super(msg, s)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:contents, _from, %{input: input, output: output} = state) do
|
||||
{:reply, {input, output}, state}
|
||||
def handle_call(:contents, _from, %{input: input, output: output} = s) do
|
||||
{:reply, {input, output}, s}
|
||||
end
|
||||
|
||||
def handle_call(:flush, _from, %{output: output} = state) do
|
||||
{:reply, output, %{state | output: ""}}
|
||||
def handle_call(:flush, _from, %{output: output} = s) do
|
||||
{:reply, output, %{s | output: ""}}
|
||||
end
|
||||
|
||||
def handle_call(:close, _from, %{input: input, output: output} = state) do
|
||||
{:stop, :normal, {:ok, {input, output}}, state}
|
||||
def handle_call(:close, _from, %{input: input, output: output} = s) do
|
||||
{:stop, :normal, {:ok, {input, output}}, s}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
super(request, from, state)
|
||||
def handle_call(request, from, s) do
|
||||
super(request, from, s)
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, state) do
|
||||
{reply, state} = io_request(req, state)
|
||||
defp io_request(from, reply_as, req, s) do
|
||||
{reply, s} = io_request(req, s)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
state
|
||||
s
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, chars} = req, state) do
|
||||
put_chars(:latin1, chars, req, state)
|
||||
defp io_request({:put_chars, chars} = req, s) do
|
||||
put_chars(:latin1, chars, req, s)
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, mod, fun, args} = req, state) do
|
||||
put_chars(:latin1, apply(mod, fun, args), req, state)
|
||||
defp io_request({:put_chars, m, f, as} = req, s) do
|
||||
put_chars(:latin1, apply(m, f, as), req, s)
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, encoding, chars} = req, state) do
|
||||
put_chars(encoding, chars, req, state)
|
||||
defp io_request({:put_chars, encoding, chars} = req, s) do
|
||||
put_chars(encoding, chars, req, s)
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, encoding, mod, fun, args} = req, state) do
|
||||
put_chars(encoding, apply(mod, fun, args), req, state)
|
||||
defp io_request({:put_chars, encoding, mod, func, args} = req, s) do
|
||||
put_chars(encoding, apply(mod, func, args), req, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_chars, prompt, count}, state) when count >= 0 do
|
||||
io_request({:get_chars, :latin1, prompt, count}, state)
|
||||
defp io_request({:get_chars, prompt, n}, s) when n >= 0 do
|
||||
io_request({:get_chars, :latin1, prompt, n}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_chars, encoding, prompt, count}, state) when count >= 0 do
|
||||
get_chars(encoding, prompt, count, state)
|
||||
defp io_request({:get_chars, encoding, prompt, n}, s) when n >= 0 do
|
||||
get_chars(encoding, prompt, n, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_line, prompt}, state) do
|
||||
io_request({:get_line, :latin1, prompt}, state)
|
||||
defp io_request({:get_line, prompt}, s) do
|
||||
io_request({:get_line, :latin1, prompt}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_line, encoding, prompt}, state) do
|
||||
get_line(encoding, prompt, state)
|
||||
defp io_request({:get_line, encoding, prompt}, s) do
|
||||
get_line(encoding, prompt, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_until, prompt, mod, fun, args}, state) do
|
||||
io_request({:get_until, :latin1, prompt, mod, fun, args}, state)
|
||||
defp io_request({:get_until, prompt, mod, fun, args}, s) do
|
||||
io_request({:get_until, :latin1, prompt, mod, fun, args}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_until, encoding, prompt, mod, fun, args}, state) do
|
||||
get_until(encoding, prompt, mod, fun, args, state)
|
||||
defp io_request({:get_until, encoding, prompt, mod, fun, args}, s) do
|
||||
get_until(encoding, prompt, mod, fun, args, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_password, encoding}, state) do
|
||||
get_line(encoding, "", state)
|
||||
defp io_request({:get_password, encoding}, s) do
|
||||
get_line(encoding, "", s)
|
||||
end
|
||||
|
||||
defp io_request({:setopts, _opts}, state) do
|
||||
{{:error, :enotsup}, state}
|
||||
defp io_request({:setopts, _opts}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request(:getopts, state) do
|
||||
{{:ok, [binary: true, encoding: :unicode]}, state}
|
||||
defp io_request(:getopts, s) do
|
||||
{{:ok, [binary: true, encoding: :unicode]}, s}
|
||||
end
|
||||
|
||||
defp io_request({:get_geometry, :columns}, state) do
|
||||
{{:error, :enotsup}, state}
|
||||
defp io_request({:get_geometry, :columns}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({:get_geometry, :rows}, state) do
|
||||
{{:error, :enotsup}, state}
|
||||
defp io_request({:get_geometry, :rows}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({:requests, reqs}, state) do
|
||||
io_requests(reqs, {:ok, state})
|
||||
defp io_request({:requests, reqs}, s) do
|
||||
io_requests(reqs, {:ok, s})
|
||||
end
|
||||
|
||||
defp io_request(_, state) do
|
||||
{{:error, :request}, state}
|
||||
defp io_request(_, s) do
|
||||
{{:error, :request}, s}
|
||||
end
|
||||
|
||||
## put_chars
|
||||
|
||||
defp put_chars(encoding, chars, req, %{output: output} = state) do
|
||||
defp put_chars(encoding, chars, req, %{output: output} = s) do
|
||||
case :unicode.characters_to_binary(chars, encoding, :unicode) do
|
||||
string when is_binary(string) ->
|
||||
{:ok, %{state | output: output <> string}}
|
||||
|
||||
{:ok, %{s | output: output <> string}}
|
||||
{_, _, _} ->
|
||||
{{:error, req}, state}
|
||||
{{:error, req}, s}
|
||||
end
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
defp get_chars(encoding, prompt, count, %{input: input} = state) do
|
||||
case get_chars(input, encoding, count) do
|
||||
defp get_chars(encoding, prompt, n, %{input: input} = s) do
|
||||
case do_get_chars(input, encoding, n) do
|
||||
{:error, _} = error ->
|
||||
{error, state}
|
||||
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
{result, state_after_read(state, input, prompt, 1)}
|
||||
{result, state_after_read(s, input, prompt)}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_chars("", _encoding, _count) do
|
||||
defp do_get_chars("", _encoding, _n) do
|
||||
{:eof, ""}
|
||||
end
|
||||
|
||||
defp get_chars(input, :latin1, count) when byte_size(input) < count do
|
||||
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
|
||||
{input, ""}
|
||||
end
|
||||
|
||||
defp get_chars(input, :latin1, count) do
|
||||
<<chars::binary-size(count), rest::binary>> = input
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<<chars::binary-size(n), rest::binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
defp get_chars(input, encoding, count) do
|
||||
defp do_get_chars(input, encoding, n) do
|
||||
try do
|
||||
case :file_io_server.count_and_find(input, count, encoding) do
|
||||
{buf_count, split_pos} when buf_count < count or split_pos == :none ->
|
||||
case :file_io_server.count_and_find(input, n, encoding) do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
|
||||
{_buf_count, split_pos} ->
|
||||
<<chars::binary-size(split_pos), rest::binary>> = input
|
||||
{chars, rest}
|
||||
@@ -261,67 +255,64 @@ defmodule StringIO do
|
||||
|
||||
## get_line
|
||||
|
||||
defp get_line(encoding, prompt, %{input: input} = state) do
|
||||
defp get_line(encoding, prompt, %{input: input} = s) do
|
||||
case bytes_until_eol(input, encoding, 0) do
|
||||
{:split, 0} ->
|
||||
{:eof, state_after_read(state, "", prompt, 1)}
|
||||
|
||||
{:eof, state_after_read(s, "", prompt)}
|
||||
{:split, count} ->
|
||||
{result, remainder} = :erlang.split_binary(input, count)
|
||||
{result, state_after_read(state, remainder, prompt, 1)}
|
||||
|
||||
{result, state_after_read(s, remainder, prompt)}
|
||||
{:replace_split, count} ->
|
||||
{result, remainder} = :erlang.split_binary(input, count)
|
||||
result = binary_part(result, 0, byte_size(result) - 2) <> "\n"
|
||||
{result, state_after_read(state, remainder, prompt, 1)}
|
||||
|
||||
:error ->
|
||||
{{:error, :collect_line}, state}
|
||||
{binary_part(result, 0, byte_size(result) - 2) <> "\n", state_after_read(s, remainder, prompt)}
|
||||
:error
|
||||
-> {{:error, :collect_line}, s}
|
||||
end
|
||||
end
|
||||
|
||||
## get_until
|
||||
|
||||
defp get_until(encoding, prompt, mod, fun, args, %{input: input} = state) do
|
||||
case get_until(input, encoding, mod, fun, args, [], 0) do
|
||||
defp get_until(encoding, prompt, mod, fun, args, %{input: input} = s) do
|
||||
case do_get_until(input, encoding, mod, fun, args) do
|
||||
{result, input, count} ->
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
_ -> list_to_binary(input, encoding)
|
||||
end
|
||||
case input do
|
||||
:eof -> ""
|
||||
_ -> list_to_binary(input, encoding)
|
||||
end
|
||||
|
||||
{get_until_result(result, encoding), state_after_read(state, input, prompt, count)}
|
||||
{get_until_result(result, encoding), state_after_read(s, input, prompt, count)}
|
||||
|
||||
:error ->
|
||||
{:error, state}
|
||||
{:error, s}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_until("", encoding, mod, fun, args, continuation, count) do
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
|
||||
|
||||
defp do_get_until("", encoding, mod, fun, args, continuation, count) do
|
||||
case apply(mod, fun, [continuation, :eof | args]) do
|
||||
{:done, result, rest} ->
|
||||
{result, rest, count + 1}
|
||||
|
||||
{:more, next_continuation} ->
|
||||
get_until("", encoding, mod, fun, args, next_continuation, count + 1)
|
||||
do_get_until("", encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
case bytes_until_eol(chars, encoding, 0) do
|
||||
{kind, size} when kind in [:split, :replace_split] ->
|
||||
{line, rest} = :erlang.split_binary(chars, size)
|
||||
{r, c} when r in [:split, :replace_split] ->
|
||||
{line, rest} = :erlang.split_binary(chars, c)
|
||||
|
||||
case apply(mod, fun, [continuation, binary_to_list(line, encoding) | args]) do
|
||||
{:done, result, :eof} ->
|
||||
{result, rest, count + 1}
|
||||
|
||||
{:done, result, extra} ->
|
||||
{result, extra ++ binary_to_list(rest, encoding), count + 1}
|
||||
|
||||
{:more, next_continuation} ->
|
||||
get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
|
||||
:error ->
|
||||
@@ -329,23 +320,22 @@ defmodule StringIO do
|
||||
end
|
||||
end
|
||||
|
||||
defp binary_to_list(data, _) when is_list(data), do: data
|
||||
defp binary_to_list(data, :unicode) when is_binary(data), do: String.to_charlist(data)
|
||||
defp binary_to_list(data, :latin1) when is_binary(data), do: :erlang.binary_to_list(data)
|
||||
defp binary_to_list(l, _) when is_list(l), do: l
|
||||
defp binary_to_list(b, :unicode) when is_binary(b), do: to_charlist(b)
|
||||
defp binary_to_list(b, :latin1) when is_binary(b), do: :binary.bin_to_list(b)
|
||||
defp list_to_binary(b, _) when is_binary(b), do: b
|
||||
defp list_to_binary(l, :unicode) when is_list(l), do: to_string(l)
|
||||
defp list_to_binary(l, :latin1) when is_list(l), do: :binary.list_to_bin(l)
|
||||
|
||||
defp list_to_binary(data, _) when is_binary(data), do: data
|
||||
defp list_to_binary(data, :unicode) when is_list(data), do: List.to_string(data)
|
||||
defp list_to_binary(data, :latin1) when is_list(data), do: :erlang.list_to_binary(data)
|
||||
|
||||
# From http://erlang.org/doc/apps/stdlib/io_protocol.html: result can be any
|
||||
# From http://erlang.org/doc/apps/stdlib/io_protocol.html: Result can be any
|
||||
# Erlang term, but if it is a list(), the I/O server can convert it to a binary().
|
||||
defp get_until_result(data, encoding) when is_list(data), do: list_to_binary(data, encoding)
|
||||
defp get_until_result(data, _), do: data
|
||||
defp get_until_result(l, encoding) when is_list(l), do: list_to_binary(l, encoding)
|
||||
defp get_until_result(other, _), do: other
|
||||
|
||||
## io_requests
|
||||
|
||||
defp io_requests([req | rest], {:ok, state}) do
|
||||
io_requests(rest, io_request(req, state))
|
||||
defp io_requests([r | rs], {:ok, s}) do
|
||||
io_requests(rs, io_request(r, s))
|
||||
end
|
||||
|
||||
defp io_requests(_, result) do
|
||||
@@ -354,13 +344,14 @@ defmodule StringIO do
|
||||
|
||||
## helpers
|
||||
|
||||
defp state_after_read(%{capture_prompt: false} = state, remainder, _prompt, _count) do
|
||||
%{state | input: remainder}
|
||||
defp state_after_read(state, remainder, prompt, count \\ 1)
|
||||
|
||||
defp state_after_read(%{capture_prompt: false} = s, remainder, _prompt, _count) do
|
||||
%{s | input: remainder}
|
||||
end
|
||||
|
||||
defp state_after_read(%{capture_prompt: true, output: output} = state, remainder, prompt, count) do
|
||||
output = <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>
|
||||
%{state | input: remainder, output: output}
|
||||
defp state_after_read(%{capture_prompt: true, output: output} = s, remainder, prompt, count) do
|
||||
%{s | input: remainder, output: <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>}
|
||||
end
|
||||
|
||||
defp bytes_until_eol("", _, count), do: {:split, count}
|
||||
@@ -378,7 +369,7 @@ defmodule StringIO do
|
||||
defp bytes_until_eol(<<_::binary>>, _, _), do: :error
|
||||
|
||||
defp io_reply(from, reply_as, reply) do
|
||||
send(from, {:io_reply, reply_as, reply})
|
||||
send from, {:io_reply, reply_as, reply}
|
||||
end
|
||||
|
||||
defp to_reply(list) when is_list(list), do: IO.chardata_to_string(list)
|
||||
|
||||
+288
-334
@@ -7,15 +7,13 @@ defmodule Supervisor do
|
||||
process structure called a *supervision tree*. Supervision trees provide
|
||||
fault-tolerance and encapsulate how our applications start and shutdown.
|
||||
|
||||
A supervisor may be started directly with a list of children via
|
||||
`start_link/2` or you may define a module-based supervisor that implements
|
||||
the required callbacks. The sections below use `start_link/2` to start
|
||||
supervisors in most examples, but it also includes a specific section
|
||||
on module-based ones.
|
||||
A supervisor implemented using this module has a standard set
|
||||
of interface functions and includes functionality for tracing and error
|
||||
reporting.
|
||||
|
||||
## Examples
|
||||
|
||||
In order to start a supervisor, we need to first define a child process
|
||||
In order to define a supervisor, we need to first define a child process
|
||||
that will be supervised. As an example, we will define a GenServer that
|
||||
represents a stack:
|
||||
|
||||
@@ -46,20 +44,16 @@ defmodule Supervisor do
|
||||
and to get the top of the stack by pattern matching.
|
||||
|
||||
We can now start a supervisor that will start and supervise our
|
||||
stack process. The first step is to define a list of **child
|
||||
specifications** that control how each child behaves. Each child
|
||||
specification is a map, as shown below:
|
||||
stack process as follows:
|
||||
|
||||
children = [
|
||||
# The Stack is a child started via Stack.start_link([:hello])
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[:hello]]}
|
||||
}
|
||||
]
|
||||
|
||||
# Now we start the supervisor with the children and a strategy
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
# Start the supervisor with the stack as a single child.
|
||||
#
|
||||
# The first element of the tuple is the module containing
|
||||
# the child implementation, the second is the argument
|
||||
# given to start_link, in this case a stack with `:hello`.
|
||||
{:ok, pid} = Supervisor.start_link([
|
||||
{Stack, [:hello]}
|
||||
], strategy: :one_for_one)
|
||||
|
||||
# After started, we can query the supervisor for information
|
||||
Supervisor.count_children(pid)
|
||||
@@ -88,7 +82,7 @@ defmodule Supervisor do
|
||||
supervisor will automatically start a new one, with the initial stack
|
||||
of `[:hello]`:
|
||||
|
||||
GenServer.call(Stack, :pop)
|
||||
GenServer.call(MyStack, :pop)
|
||||
#=> :hello
|
||||
|
||||
Supervisors support different strategies; in the example above, we
|
||||
@@ -99,53 +93,64 @@ defmodule Supervisor do
|
||||
The rest of this document will cover how child processes are started,
|
||||
how they can be specified, different supervision strategies and more.
|
||||
|
||||
## Start and shutdown
|
||||
## The initialization process
|
||||
|
||||
When the supervisor starts, it traverses all children and retrieves
|
||||
each child specification. It is at this moment `{Stack, [:hello]}`
|
||||
becomes a child specification by calling `Stack.child_spec([:hello])`.
|
||||
In the previous section, we have started a supervisor with one child:
|
||||
|
||||
Then the supervisor starts each child in the order they are defined.
|
||||
This is done by calling the function defined under the `:start` key
|
||||
in the child specification and typically defaults to `start_link/1`.
|
||||
Supervisor.start_link([
|
||||
{Stack, [:hello]}
|
||||
], strategy: :one_for_one)
|
||||
|
||||
The `start_link/1` (or a custom) is then called for each child process.
|
||||
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
|
||||
process identifier of a new process that is linked to the supervisor.
|
||||
The child process usually starts its work by executing the `init/1`
|
||||
callback. Generally speaking, the `init` callback is where we initialize
|
||||
and configure the child process.
|
||||
The first argument given to `start_link` is a list of children.
|
||||
In the example above, we have passed a tuple, where the child is
|
||||
implemented by the `Stack` module and receives an initial argument
|
||||
of `[:hello]` on `Stack.start_link/1`.
|
||||
|
||||
The shutdown process happens in reverse order.
|
||||
Generally speaking, starting the child process happens in three steps:
|
||||
|
||||
When a supervisor shuts down, it terminates all children in the opposite
|
||||
order they are listed. The termination happens by sending a shutdown exit
|
||||
signal, via `Process.exit(child_pid, :shutdown)`, to the child process and
|
||||
then awaiting for a time interval for the child process to terminate. This
|
||||
interval defaults to 5000 milliseconds. If the child process does not
|
||||
terminate in this interval, the supervisor abruptly terminates the child
|
||||
with reason `:brutal_kill`. The shutdown time can be configured in the
|
||||
child specification which is fully detailed in the next section.
|
||||
1. First the supervisor calls `Stack.child_spec([:hello])`. This
|
||||
function must return a **child specification** which describes
|
||||
how the `Stack` process is supervised. When you `use GenServer`,
|
||||
a `child_spec/1` is automatically defined for you but we will see
|
||||
when and how it can be configured. This function is called once
|
||||
when the supervisor starts (or in case of hot code upgrades).
|
||||
|
||||
If the child process is not trapping exits, it will shutdown immediately
|
||||
when it receives the first exit signal. If the child process is trapping
|
||||
exits, then the `terminate` callback is invoked, and the child process
|
||||
must terminate in a reasonable time interval before being abruptly
|
||||
terminated by the supervisor.
|
||||
2. The **child specification** tells the supervisor which function
|
||||
to invoke to start the child process. By default, it is the
|
||||
`start_link/1` function, receiving the same argument and defined
|
||||
in the same module as the `child_spec/1` function. This function
|
||||
is called every time a new child process is necessary. For example,
|
||||
when we crashed a `Stack` in the previous session,
|
||||
`Stack.start_link([:hello])` was called once more to start a new stack.
|
||||
|
||||
In other words, if it is important that a process cleans after itself
|
||||
when your application or the supervision tree is shutting down, then
|
||||
this process must trap exits and its child specification should specify
|
||||
the proper `:shutdown` value, ensuring it terminates within a reasonable
|
||||
interval.
|
||||
3. Finally, `Stack.start_link/1` starts a new process that runs
|
||||
`Stack.init/1`, which is responsible for setting a process that
|
||||
will react to messages.
|
||||
|
||||
Now that we understand the start and shutdown process, let's take a
|
||||
complete look at all of the options provided in the child specification.
|
||||
In summary, when the `Supervisor.start_link(children, opts)` is called,
|
||||
it traverses the list of children and retrieves their `child_spec/1`.
|
||||
Then each child specification describes how each child is started,
|
||||
typically via the `start_link/1` function. The supervisor invokes the
|
||||
`start_link/1` when it initializes and whenever the child process needs
|
||||
to be restarted. The new process started by `start_link/1` often
|
||||
executes the `init` callback as its first step. The `init` callback is
|
||||
where we initialize and configure the child process.
|
||||
|
||||
## Child specification
|
||||
|
||||
The child specification describes how the supervisor start, shutdown and
|
||||
restart child processes.
|
||||
The child specification describes how the supervisor should start and
|
||||
supervise a child process. We have learned that, when we invoked
|
||||
`use GenServer`, a `Stack.child_spec/1` was automatically defined for
|
||||
us. Let's invoke it and see what it returns:
|
||||
|
||||
Stack.child_spec([:hello])
|
||||
#=> %{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[:hello]]},
|
||||
restart: :permanent,
|
||||
shutdown: 5000,
|
||||
type: :worker
|
||||
}
|
||||
|
||||
The child specification contains 5 keys. The first two are required
|
||||
and the remaining ones are optional:
|
||||
@@ -173,28 +178,39 @@ defmodule Supervisor do
|
||||
There is a sixth key, called `:modules`, which is rarely changed and
|
||||
it is set automatically based on the value in `:start`.
|
||||
|
||||
Most times, the behaviour module you are implementing will take care
|
||||
of setting up a proper `child_spec/1` for you. For example, `use Supervisor`
|
||||
will define a `child_spec/1` where the `:type` is set to `:supervisor`
|
||||
and the `:shutdown` is `:infinity`. Still, if you need to customize
|
||||
a certain behaviour, you can do so by defining your own `child_spec/1`
|
||||
function or by passing options on `use`. For example, to specify a
|
||||
`GenServer` with a shutdown limit of 10 seconds (10_000 miliseconds),
|
||||
one might do:
|
||||
|
||||
use GenServer, shutdown: 10000
|
||||
|
||||
Let's understand what the `:shutdown` and `:restart` options control.
|
||||
|
||||
### Shutdown values (:shutdown)
|
||||
|
||||
The following shutdown values are supported in the `:shutdown` option:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally and immediately
|
||||
terminated using `Process.exit(child, :kill)`.
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `Process.exit(child, :kill)`.
|
||||
|
||||
* any integer >= 0 - the amount of time in milliseconds that the
|
||||
* any integer >= 0 - the amount of time in miliseconds that the
|
||||
supervisor will wait for children to terminate after emitting a
|
||||
`Process.exit(child, :shutdown)` signal. If the child process is
|
||||
`Process.exit(child, :shutdown)` signal. If the child process is
|
||||
not trapping exits, the initial `:shutdown` signal will terminate
|
||||
the child process immediately. If the child process is trapping
|
||||
exits, it has the given amount of time in milliseconds to terminate.
|
||||
exits, it has the given amount of time in miliseconds to terminate.
|
||||
If it doesn't terminate within the specified time, the child process
|
||||
is unconditionally terminated by the supervisor via
|
||||
`Process.exit(child, :kill)`.
|
||||
|
||||
* `:infinity` - works as an integer except the supervisor will wait
|
||||
indefinitely for the child to terminate. If the child process is a
|
||||
supervisor, the recommended value is `:infinity` to give the supervisor
|
||||
supervisor, the recommend value is `:infinity` to give the supervisor
|
||||
and its children enough time to shutdown. This option can be used with
|
||||
regular workers but doing so is discouraged and requires extreme care.
|
||||
If not used carefully and the child process does not terminate, it means
|
||||
@@ -219,95 +235,13 @@ defmodule Supervisor do
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
For a more complete understanding of the exit reasons and their
|
||||
impact, see the "Exit reasons and restarts" section.
|
||||
impact, see the "Exit reasons" section next.
|
||||
|
||||
## child_spec/1
|
||||
|
||||
When starting a supervisor, we pass a list of child specifications. Those
|
||||
specifications are maps that tell how the supervisor should start, stop and
|
||||
restart each of its children:
|
||||
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[:hello]]}
|
||||
}
|
||||
|
||||
The map above defines a supervisor with `:id` of `Stack` that is started
|
||||
by calling `Stack.start_link([:hello])`.
|
||||
|
||||
However, specifying the child specification for each child as a map can be
|
||||
quite error prone, as we may change the Stack implementation and forget to
|
||||
update its specification. That's why Elixir allows you to pass a tuple with
|
||||
the module name and the `start_link` argument instead of the specification:
|
||||
|
||||
children = [
|
||||
{Stack, [:hello]}
|
||||
]
|
||||
|
||||
The supervisor will then invoke `Stack.child_spec([:hello])` to retrieve a
|
||||
child specification. Now the `Stack` module is responsible for building its
|
||||
own specification. By default, `use GenServer` defines a `Stack.child_spec/1`
|
||||
function which returns the same child specification we had before:
|
||||
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[:hello]]}
|
||||
}
|
||||
|
||||
It is also possible to simply pass the `Stack` module as a child:
|
||||
|
||||
children = [
|
||||
Stack
|
||||
]
|
||||
|
||||
When only the module name is given, it is equivalent to `{Stack, []}`. In this
|
||||
case, we will end-up with a child specification that looks like this:
|
||||
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[]]}
|
||||
}
|
||||
|
||||
By replacing the map specification by `{Stack, [:hello]}` or `Stack`, we keep
|
||||
the child specification encapsulated in the Stack module, using the default
|
||||
implementation defined by `use GenServer`. We can now share our `Stack` worker
|
||||
with other developers and they can add it directly to their supervision tree
|
||||
without worrying about the low-level details of the worker.
|
||||
|
||||
If you need to access or modify how a worker or a supervisor runs, you can use
|
||||
the `Supervisor.child_spec/2` function. For example, to run the stack with a
|
||||
different `:id` and a `:shutdown` value of 10 seconds (10_000 milliseconds):
|
||||
|
||||
children = [
|
||||
Supervisor.child_spec({Stack, [:hello]}, id: MyStack, shutdown: 10_000)
|
||||
]
|
||||
|
||||
The call to `Supervisor.child_spec/2` above will return the following specification:
|
||||
|
||||
%{
|
||||
id: MyStack,
|
||||
start: {Stack, :start_link, [[:hello]]},
|
||||
shutdown: 10_000
|
||||
}
|
||||
|
||||
You may also configure the child specification in the Stack module itself to
|
||||
use a different `:id` or `:shutdown` value by passing options to `use GenServer`:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer, id: MyStack, shutdown: 10_000
|
||||
|
||||
The options above will customize the `Stack.child_spec/1` function defined
|
||||
by `use GenServer`. It accepts the same options as the `Supervisor.child_spec/2`
|
||||
function.
|
||||
|
||||
You may also completely override the `child_spec/1` function in the Stack module
|
||||
and return your own child specification.
|
||||
|
||||
## Exit reasons and restarts
|
||||
## Exit reasons
|
||||
|
||||
A supervisor restarts a child process depending on its `:restart`
|
||||
configuration. For example, when `:restart` is set to `:transient`, the
|
||||
supervisor does not restart the child in case it exits with reason `:normal`,
|
||||
configuration. For example, when `:restart` is set `:transient`, the
|
||||
supervisr does not restart the child in case it exits with reason `:normal`,
|
||||
`:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when exiting? There are
|
||||
@@ -326,7 +260,7 @@ defmodule Supervisor do
|
||||
|
||||
Notice that supervisor that reached maximum restart intensity will exit with
|
||||
`:shutdown` reason. In this case the supervisor will only be restarted if its
|
||||
child specification was defined with the `:restart` option set to `:permanent`
|
||||
child specification was defined with the `:restart` option is set to `:permanent`
|
||||
(the default).
|
||||
|
||||
## Module-based supervisors
|
||||
@@ -344,11 +278,9 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
def init(_arg) do
|
||||
children = [
|
||||
Supervisor.init([
|
||||
{Stack, [:hello]}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
], strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -366,19 +298,21 @@ defmodule Supervisor do
|
||||
initialization, like setting up an ETS table.
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. The module-based approach allow you to add and remove
|
||||
children on a case-by-case basis.
|
||||
tree. For example, if you add or remove children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps.
|
||||
|
||||
Note `use Supervisor` defines a `child_spec/1` function, allowing
|
||||
the defined module itself to be put under a supervision tree.
|
||||
The generated `child_spec/1` can be customized with the following
|
||||
options:
|
||||
the defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification id, defauts to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the supervisor should be restarted, defaults to `:permanent`
|
||||
|
||||
## `start_link/2`, `init/2`, and strategies
|
||||
## start_link/2, init/2 and strategies
|
||||
|
||||
So far we have started the supervisor passing a single child as a tuple
|
||||
as well as a strategy called `:one_for_one`:
|
||||
@@ -387,36 +321,34 @@ defmodule Supervisor do
|
||||
{Stack, [:hello]}
|
||||
], strategy: :one_for_one)
|
||||
|
||||
or from inside the `c:init/1` callback:
|
||||
Or:
|
||||
|
||||
Supervisor.init([
|
||||
{Stack, [:hello]}
|
||||
], strategy: :one_for_one)
|
||||
|
||||
Although we have mentioned that the supervisor automatically expands
|
||||
`{Stack, [:hello]}` to a child specification by calling
|
||||
`Stack.child_spec([:hello])`, we haven't formally defined all of the
|
||||
arguments accepted by `start_link/2` and `init/2`. Let's rectify that
|
||||
now.
|
||||
However, children can be specified in three different formats and
|
||||
supervisors support different options. Let's formally define those.
|
||||
|
||||
The first argument given to `start_link/2` is a list of children which may
|
||||
be either:
|
||||
|
||||
* a map representing the child specification itself - as outlined in the
|
||||
"Child specification" section
|
||||
* a tuple with a module as first element and the start argument as second -
|
||||
such as `{Stack, [:hello]}`. In this case, `Stack.child_spec([:hello])`
|
||||
is called to retrieve the child specification
|
||||
* a module - such as `Stack`. In this case, `Stack.child_spec([])`
|
||||
is called to retrieve the child specification
|
||||
* a module - such as `Stack`. In this case, it is equivalent to passing
|
||||
`{Stack, []}` (which means `Stack.child_spec/1` is invoked with an empty
|
||||
keywords list)
|
||||
* a tuple with a module as first element and the start argument as second -
|
||||
such as `{Stack, [:hello]}`. When such format is used, the supervisor
|
||||
will retrieve the child specification from the given module.
|
||||
* a map representing the child specification itself - such as the child
|
||||
specification map outlined in the previous section.
|
||||
|
||||
The second argument is a keyword list of options:
|
||||
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one` or `:one_for_all`. See the
|
||||
"Strategies" section.
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
`:simple_one_for_one`. See the "Strategies" section.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to `3`.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
@@ -442,11 +374,59 @@ defmodule Supervisor do
|
||||
one in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted.
|
||||
|
||||
There is also a deprecated strategy called `:simple_one_for_one` which
|
||||
has been replaced by the `DynamicSupervisor`. The `:simple_one_for_one`
|
||||
supervisor was similar to `:one_for_one` but suits better when dynamically
|
||||
attaching children. Many functions in this module behaved slightly
|
||||
differently when this strategy is used.
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one child. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Simple one for one
|
||||
|
||||
The `:simple_one_for_one` supervisor is useful when you want to
|
||||
dynamically start and stop supervised children. As an example,
|
||||
let's start multiple agents dynamically to keep state.
|
||||
|
||||
One important aspect in `:simple_one_for_one` supervisors is
|
||||
that we often want to pass the `:start` arguments later on,
|
||||
when starting the children dynamically, rather than when the
|
||||
child specification is defined. In such cases, we should not do
|
||||
|
||||
Supervisor.start_link [
|
||||
{Agent, fn -> 0 end}
|
||||
]
|
||||
|
||||
as the example above would force all agents to have the same state.
|
||||
In such cases, we can use the `child_spec/2` function to build
|
||||
and override the fields in a child specification:
|
||||
|
||||
# Override the :start field to have no args.
|
||||
# The second argument has no effect thanks to it.
|
||||
agent_spec =
|
||||
Supervisor.child_spec(Agent, start: {Agent, :start_link, []})
|
||||
|
||||
# We start a supervisor with a simple one for one strategy.
|
||||
# The agent won't be started now but later on.
|
||||
{:ok, sup_pid} =
|
||||
Supervisor.start_link([agent_spec], strategy: :simple_one_for_one)
|
||||
|
||||
# No child worker is active until start_child is called
|
||||
Supervisor.count_children(sup_pid)
|
||||
#=> %{active: 0, specs: 1, supervisors: 0, workers: 0}
|
||||
|
||||
The simple one for one strategy can define only one child which works
|
||||
as a template for when we call `start_child/2`.
|
||||
|
||||
With the supervisor started, let's dynamically start agents:
|
||||
|
||||
{:ok, agent1} = Supervisor.start_child(sup_pid, [fn -> 0 end])
|
||||
Agent.update(agent1, & &1 + 1)
|
||||
Agent.get(agent1, & &1) #=> 1
|
||||
|
||||
{:ok, agent2} = Supervisor.start_child(sup_pid, [fn -> %{} end])
|
||||
Agent.get(agent2, & &1) #=> %{}
|
||||
|
||||
Supervisor.count_children(sup_pid)
|
||||
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
|
||||
|
||||
## Name registration
|
||||
|
||||
@@ -456,20 +436,20 @@ defmodule Supervisor do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
import Supervisor.Spec
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour Supervisor
|
||||
@opts unquote(opts)
|
||||
import Supervisor.Spec
|
||||
|
||||
spec = [
|
||||
id: opts[:id] || __MODULE__,
|
||||
start: Macro.escape(opts[:start]) || quote(do: {__MODULE__, :start_link, [arg]}),
|
||||
restart: opts[:restart] || :permanent,
|
||||
type: :supervisor
|
||||
]
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]},
|
||||
type: :supervisor
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
%{unquote_splicing(spec)}
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
@@ -481,25 +461,18 @@ defmodule Supervisor do
|
||||
|
||||
@doc """
|
||||
Callback invoked to start the supervisor and during hot code upgrades.
|
||||
|
||||
Developers typically invoke `Supervisor.init/2` at the end of their
|
||||
init callback to return the proper supervision flags.
|
||||
"""
|
||||
@callback init(args :: term) ::
|
||||
{:ok, {:supervisor.sup_flags(), [:supervisor.child_spec()]}}
|
||||
| :ignore
|
||||
{:ok, {:supervisor.sup_flags, [:supervisor.child_spec]}} |
|
||||
:ignore
|
||||
|
||||
@typedoc "Return values of `start_link` functions"
|
||||
@type on_start ::
|
||||
{:ok, pid}
|
||||
| :ignore
|
||||
| {:error, {:already_started, pid} | {:shutdown, term} | term}
|
||||
@type on_start :: {:ok, pid} | :ignore |
|
||||
{:error, {:already_started, pid} | {:shutdown, term} | term}
|
||||
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child ::
|
||||
{:ok, child}
|
||||
| {:ok, child, info :: term}
|
||||
| {:error, {:already_started, child} | :already_present | term}
|
||||
@type on_start_child :: {:ok, child} | {:ok, child, info :: term} |
|
||||
{:error, {:already_started, child} | :already_present | term}
|
||||
|
||||
@type child :: pid | :undefined
|
||||
|
||||
@@ -507,7 +480,7 @@ defmodule Supervisor do
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Option values used by the `start*` functions"
|
||||
@type option :: {:name, name} | init_option()
|
||||
@type option :: {:name, name} | flag()
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [option, ...]
|
||||
@@ -516,24 +489,15 @@ defmodule Supervisor do
|
||||
@type supervisor :: pid | name | {atom, node}
|
||||
|
||||
@typedoc "Options given to `start_link/2` and `init/2`"
|
||||
@type init_option ::
|
||||
{:strategy, strategy}
|
||||
| {:max_restarts, non_neg_integer}
|
||||
| {:max_seconds, pos_integer}
|
||||
@type flag :: {:strategy, strategy} |
|
||||
{:max_restarts, non_neg_integer} |
|
||||
{:max_seconds, pos_integer}
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :one_for_one | :one_for_all | :rest_for_one
|
||||
@type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one
|
||||
|
||||
# Note we have inlined all types for readability
|
||||
@typedoc "The supervisor specification"
|
||||
@type child_spec :: %{
|
||||
required(:id) => term(),
|
||||
required(:start) => {module(), atom(), [term()]},
|
||||
optional(:restart) => :permanent | :transient | :temporary,
|
||||
optional(:shutdown) => :brutal_kill | non_neg_integer() | :infinity,
|
||||
optional(:type) => :worker | :supervisor,
|
||||
optional(:modules) => [module()] | :dynamic
|
||||
}
|
||||
@type child_spec :: :supervisor.child_spec()
|
||||
|
||||
@doc """
|
||||
Starts a supervisor with the given children.
|
||||
@@ -552,7 +516,7 @@ defmodule Supervisor do
|
||||
`{:ok, child, info}`, or `:ignore`) this function returns
|
||||
`{:ok, pid}`, where `pid` is the PID of the supervisor. If the supervisor
|
||||
is given a name and a process with the specified name already exists,
|
||||
the function returns `{:error, {:already_started, pid}}`, where `pid`
|
||||
the function returns `{:error, {:already_started, pid}}`, where `pid`
|
||||
is the PID of that process.
|
||||
|
||||
If the start function of any of the child processes fails or returns an error
|
||||
@@ -564,10 +528,10 @@ defmodule Supervisor do
|
||||
process and exits not only on crashes but also if the parent process exits
|
||||
with `:normal` reason.
|
||||
"""
|
||||
@spec start_link([:supervisor.child_spec() | {module, term} | module], options) :: on_start
|
||||
@spec start_link([:supervisor.child_spec | {module, term} | module], options) :: on_start
|
||||
def start_link(children, options) when is_list(children) do
|
||||
{sup_opts, start_opts} = Keyword.split(options, [:strategy, :max_seconds, :max_restarts])
|
||||
start_link(Supervisor.Default, init(children, sup_opts), start_opts)
|
||||
start_link(Supervisor.Default, {children, sup_opts}, start_opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -592,10 +556,11 @@ defmodule Supervisor do
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or the deprecated
|
||||
`:simple_one_for_one`.
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to `3`.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
@@ -605,13 +570,11 @@ defmodule Supervisor do
|
||||
is allowed within 5 seconds. Check the `Supervisor` module for a detailed
|
||||
description of the available strategies.
|
||||
"""
|
||||
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.8.
|
||||
@spec init([:supervisor.child_spec() | {module, term} | module], [init_option]) :: {:ok, tuple}
|
||||
@spec init([:supervisor.child_spec | {module, term} | module], flag) :: {:ok, tuple}
|
||||
def init(children, options) when is_list(children) and is_list(options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
intensity = Keyword.get(options, :max_restarts, 3)
|
||||
period = Keyword.get(options, :max_seconds, 5)
|
||||
flags = %{strategy: strategy, intensity: intensity, period: period}
|
||||
@@ -621,30 +584,56 @@ defmodule Supervisor do
|
||||
defp init_child(module) when is_atom(module) do
|
||||
init_child({module, []})
|
||||
end
|
||||
|
||||
defp init_child({module, arg}) when is_atom(module) do
|
||||
try do
|
||||
module.child_spec(arg)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
case System.stacktrace() do
|
||||
case System.stacktrace do
|
||||
[{^module, :child_spec, [^arg], _} | _] ->
|
||||
raise ArgumentError, child_spec_error(module)
|
||||
raise ArgumentError, """
|
||||
The module #{inspect module} was given as a child to a supervisor
|
||||
but it does not implement child_spec/1.
|
||||
|
||||
If you own the given module, please define a child_spec/1 function
|
||||
that receives an argument and returns a child specification as a map.
|
||||
For example:
|
||||
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [opts]},
|
||||
type: :worker,
|
||||
restart: :permanent,
|
||||
shutdown: 500
|
||||
}
|
||||
end
|
||||
|
||||
Note that "use Agent", "use GenServer" and so on automatically define
|
||||
this function for you.
|
||||
|
||||
However, if you don't own the given module and it doesn't implement
|
||||
child_spec/1, instead of passing the module name directly as a supervisor
|
||||
child, you will have to pass a child specification as a map:
|
||||
|
||||
%{
|
||||
id: #{inspect module},
|
||||
start: {#{inspect module}, :start_link, [arg1, arg2]}
|
||||
}
|
||||
|
||||
See the Supervisor documentation for more information.
|
||||
"""
|
||||
stack ->
|
||||
reraise e, stack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp init_child(map) when is_map(map) do
|
||||
map
|
||||
end
|
||||
|
||||
defp init_child({_, _, _, _, _, _} = tuple) do
|
||||
tuple
|
||||
end
|
||||
|
||||
defp init_child(other) do
|
||||
raise ArgumentError, """
|
||||
supervisors expect each child to be one of:
|
||||
@@ -654,49 +643,10 @@ defmodule Supervisor do
|
||||
* a child specification as a map with at least the :id and :start fields
|
||||
* or a tuple with 6 elements generated by Supervisor.Spec (deprecated)
|
||||
|
||||
Got: #{inspect(other)}
|
||||
Got: #{inspect other}
|
||||
"""
|
||||
end
|
||||
|
||||
defp child_spec_error(module) do
|
||||
if Code.ensure_loaded?(module) do
|
||||
"""
|
||||
The module #{inspect(module)} was given as a child to a supervisor
|
||||
but it does not implement child_spec/1.
|
||||
|
||||
If you own the given module, please define a child_spec/1 function
|
||||
that receives an argument and returns a child specification as a map.
|
||||
For example:
|
||||
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [opts]},
|
||||
type: :worker,
|
||||
restart: :permanent,
|
||||
shutdown: 500
|
||||
}
|
||||
end
|
||||
|
||||
Note that "use Agent", "use GenServer" and so on automatically define
|
||||
this function for you.
|
||||
|
||||
However, if you don't own the given module and it doesn't implement
|
||||
child_spec/1, instead of passing the module name directly as a supervisor
|
||||
child, you will have to pass a child specification as a map:
|
||||
|
||||
%{
|
||||
id: #{inspect(module)},
|
||||
start: {#{inspect(module)}, :start_link, [arg1, arg2]}
|
||||
}
|
||||
|
||||
See the Supervisor documentation for more information.
|
||||
"""
|
||||
else
|
||||
"The module #{inspect(module)} was given as a child to a supervisor but it does not exist."
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Builds and overrides a child specification.
|
||||
|
||||
@@ -720,30 +670,36 @@ defmodule Supervisor do
|
||||
|
||||
Supervisor.child_spec({Agent, fn -> :ok end}, id: {Agent, 1})
|
||||
#=> %{id: {Agent, 1},
|
||||
#=> start: {Agent, :start_link, [fn -> :ok end]}}
|
||||
start: {Agent, :start_link, [fn -> :ok end]}}
|
||||
|
||||
It may also be used when there is a need to change the number
|
||||
of arguments when starting a module under a `:simple_one_for_one`
|
||||
strategy, since most args may be given dynamically:
|
||||
|
||||
Supervisor.child_spec(Agent, start: {Agent, :start_link, []})
|
||||
#=> %{id: Agent,
|
||||
start: {Agent, :start_link, []}}
|
||||
|
||||
"""
|
||||
@spec child_spec(child_spec() | {module, arg :: term} | module, keyword) :: child_spec()
|
||||
def child_spec(module_or_map, overrides)
|
||||
|
||||
def child_spec({_, _, _, _, _, _} = tuple, _overrides) do
|
||||
raise ArgumentError,
|
||||
"old tuple-based child specification #{inspect(tuple)} " <>
|
||||
"is not supported in Supervisor.child_spec/2"
|
||||
raise ArgumentError, "old tuple-based child specification #{inspect tuple} " <>
|
||||
"is not supported in Supervisor.child_spec/2"
|
||||
end
|
||||
|
||||
def child_spec(module_or_map, overrides) do
|
||||
Enum.reduce(overrides, init_child(module_or_map), fn
|
||||
Enum.reduce overrides, init_child(module_or_map), fn
|
||||
{key, value}, acc when key in [:id, :start, :restart, :shutdown, :type, :modules] ->
|
||||
Map.put(acc, key, value)
|
||||
|
||||
{key, _value}, _acc ->
|
||||
raise ArgumentError, "unknown key #{inspect(key)} in child specification override"
|
||||
end)
|
||||
raise ArgumentError, "unknown key #{inspect key} in child specification override"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a module-based supervisor process with the given `module` and `arg`.
|
||||
Starts a supervisor process with the given `module` and `arg`.
|
||||
|
||||
To start the supervisor, the `c:init/1` callback will be invoked in the given
|
||||
`module`, with `arg` as its argument. The `c:init/1` callback must return a
|
||||
@@ -760,21 +716,18 @@ defmodule Supervisor do
|
||||
name, the supported values are described in the "Name registration"
|
||||
section in the `GenServer` module docs.
|
||||
"""
|
||||
@spec start_link(module, term, GenServer.options()) :: on_start
|
||||
@spec start_link(module, term) :: on_start
|
||||
@spec start_link(module, term, GenServer.options) :: on_start
|
||||
def start_link(module, arg, options \\ []) when is_list(options) do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:supervisor.start_link(module, arg)
|
||||
|
||||
atom when is_atom(atom) ->
|
||||
:supervisor.start_link({:local, atom}, module, arg)
|
||||
|
||||
{:global, _term} = tuple ->
|
||||
:supervisor.start_link(tuple, module, arg)
|
||||
|
||||
{:via, via_module, _term} = tuple when is_atom(via_module) ->
|
||||
:supervisor.start_link(tuple, module, arg)
|
||||
|
||||
other ->
|
||||
raise ArgumentError, """
|
||||
expected :name option to be one of:
|
||||
@@ -790,11 +743,17 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a child specification to `supervisor` and starts that child.
|
||||
Dynamically adds a child specification to `supervisor` and starts that child.
|
||||
|
||||
`child_spec` should be a valid child specification. The child process will
|
||||
`child_spec` should be a valid child specification (unless the supervisor
|
||||
is a `:simple_one_for_one` supervisor, see below). The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
In the case of `:simple_one_for_one`, the child specification defined in
|
||||
the supervisor is used and instead of a `child_spec`, an arbitrary list
|
||||
of terms is expected. The child process will then be started by appending
|
||||
the given list to the existing function arguments in the child specification.
|
||||
|
||||
If a child specification with the specified id already exists, `child_spec` is
|
||||
discarded and this function returns an error with `:already_started` or
|
||||
`:already_present` if the corresponding child process is running or not,
|
||||
@@ -813,46 +772,36 @@ defmodule Supervisor do
|
||||
returns `{:error, error}` where `error` is a term containing information about
|
||||
the error and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, :supervisor.child_spec() | {module, term} | module | [term]) ::
|
||||
on_start_child
|
||||
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
|
||||
call(supervisor, {:start_child, child_spec})
|
||||
end
|
||||
|
||||
# TODO: Deprecate this on Elixir v1.8. Remove and update typespec on v2.0.
|
||||
def start_child(supervisor, args) when is_list(args) do
|
||||
call(supervisor, {:start_child, args})
|
||||
end
|
||||
|
||||
def start_child(supervisor, child_spec) do
|
||||
call(supervisor, {:start_child, Supervisor.child_spec(child_spec, [])})
|
||||
# TODO: Once we add DynamicSupervisor, we need to enforce receiving
|
||||
# a map here and deprecate the list and tuple formats.
|
||||
@spec start_child(supervisor, :supervisor.child_spec | [term]) :: on_start_child
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child identified by child id.
|
||||
Terminates the given children, identified by PID or child id.
|
||||
|
||||
The process is terminated, if there's one. The child specification is
|
||||
If the supervisor is not a `:simple_one_for_one`, the child id is expected
|
||||
and the process, if there's one, is terminated; the child specification is
|
||||
kept unless the child is temporary.
|
||||
|
||||
A non-temporary child process may later be restarted by the supervisor.
|
||||
The child process can also be restarted explicitly by calling `restart_child/2`.
|
||||
Use `delete_child/2` to remove the child specification.
|
||||
In case of a `:simple_one_for_one` supervisor, a PID is expected. If the child
|
||||
specification identifier is given instead of a `pid`, this function returns
|
||||
`{:error, :simple_one_for_one}`.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no child
|
||||
specification for the given child id, this function returns
|
||||
`{:error, :not_found}`.
|
||||
A non-temporary child process may later be restarted by the supervisor. The child
|
||||
process can also be restarted explicitly by calling `restart_child/2`. Use
|
||||
`delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no child specification
|
||||
for the given child id or there is no process with the given PID, this
|
||||
function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, term()) :: :ok | {:error, error}
|
||||
@spec terminate_child(supervisor, pid | term()) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
# TODO: Deprecate this on Elixir v1.8
|
||||
def terminate_child(supervisor, child_id)
|
||||
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
call(supervisor, {:terminate_child, pid})
|
||||
end
|
||||
|
||||
def terminate_child(supervisor, child_id) do
|
||||
call(supervisor, {:terminate_child, child_id})
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -864,6 +813,8 @@ defmodule Supervisor do
|
||||
If successful, this function returns `:ok`. This function may return an error
|
||||
with an appropriate error tuple if the `child_id` is not found, or if the
|
||||
current process is running or being restarted.
|
||||
|
||||
This operation is not supported by `:simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec delete_child(supervisor, term()) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
@@ -892,8 +843,11 @@ defmodule Supervisor do
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, this function returns `{:error, error}`.
|
||||
|
||||
This operation is not supported by `:simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec restart_child(supervisor, term()) :: {:ok, child} | {:ok, child, term} | {:error, error}
|
||||
@spec restart_child(supervisor, term()) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
@@ -907,7 +861,8 @@ defmodule Supervisor do
|
||||
|
||||
This function returns a list of `{id, child, type, modules}` tuples, where:
|
||||
|
||||
* `id` - as defined in the child specification
|
||||
* `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor
|
||||
|
||||
* `child` - the PID of the corresponding child process, `:restarting` if the
|
||||
process is about to be restarted, or `:undefined` if there is no such
|
||||
@@ -918,9 +873,11 @@ defmodule Supervisor do
|
||||
* `modules` - as specified by the child specification
|
||||
|
||||
"""
|
||||
@spec which_children(supervisor) :: [
|
||||
{term() | :undefined, child | :restarting, :worker | :supervisor, :supervisor.modules()}
|
||||
]
|
||||
@spec which_children(supervisor) ::
|
||||
[{term() | :undefined,
|
||||
child | :restarting,
|
||||
:worker | :supervisor,
|
||||
:supervisor.modules}]
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
@@ -942,14 +899,11 @@ defmodule Supervisor do
|
||||
are still alive
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) :: %{
|
||||
specs: non_neg_integer,
|
||||
active: non_neg_integer,
|
||||
supervisors: non_neg_integer,
|
||||
workers: non_neg_integer
|
||||
}
|
||||
@spec count_children(supervisor) ::
|
||||
%{specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer}
|
||||
def count_children(supervisor) do
|
||||
call(supervisor, :count_children) |> :maps.from_list()
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -964,7 +918,7 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec stop(supervisor, reason :: term, timeout) :: :ok
|
||||
def stop(supervisor, reason \\ :normal, timeout \\ :infinity) do
|
||||
GenServer.stop(supervisor, reason, timeout)
|
||||
:gen.stop(supervisor, reason, timeout)
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Supervisor.Default do
|
||||
@moduledoc false
|
||||
|
||||
def init(args) do
|
||||
args
|
||||
def init({children, opts}) do
|
||||
Supervisor.init(children, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
defmodule Supervisor.Spec do
|
||||
@moduledoc """
|
||||
WARNING: this module is deprecated.
|
||||
|
||||
The functions in this module are deprecated and they do not work
|
||||
with the module-based child specs introduced in Elixir v1.5.
|
||||
NOTE: The functions in this module are deprecated and they do not
|
||||
work with the module-based child specs introduced in Elixir v1.5.
|
||||
Please see the `Supervisor` documentation instead.
|
||||
|
||||
Convenience functions for defining supervisor specifications.
|
||||
@@ -128,8 +126,12 @@ defmodule Supervisor.Spec do
|
||||
@type child_id :: term
|
||||
|
||||
@typedoc "The supervisor specification"
|
||||
@type spec ::
|
||||
{child_id, start_fun :: {module, atom, [term]}, restart, shutdown, worker, modules}
|
||||
@type spec :: {child_id,
|
||||
start_fun :: {module, atom, [term]},
|
||||
restart,
|
||||
shutdown,
|
||||
worker,
|
||||
modules}
|
||||
|
||||
@doc """
|
||||
Receives a list of children (workers or supervisors) to
|
||||
@@ -150,7 +152,7 @@ defmodule Supervisor.Spec do
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum number of restarts allowed in
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to `3`.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
@@ -160,12 +162,9 @@ defmodule Supervisor.Spec do
|
||||
allowed within 5 seconds. Check the `Supervisor` module for a detailed
|
||||
description of the available strategies.
|
||||
"""
|
||||
@spec supervise(
|
||||
[spec],
|
||||
strategy: strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: pos_integer
|
||||
) :: {:ok, tuple}
|
||||
@spec supervise([spec], strategy: strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: pos_integer) :: {:ok, tuple}
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
@@ -181,20 +180,19 @@ defmodule Supervisor.Spec do
|
||||
defp get_id({id, _, _, _, _, _}) do
|
||||
id
|
||||
end
|
||||
|
||||
defp get_id(other) do
|
||||
raise ArgumentError,
|
||||
"invalid tuple specification given to supervise/2. If you are trying to use " <>
|
||||
"the map child specification that is part of the Elixir v1.5, use Supervisor.init/2 " <>
|
||||
"instead of Supervisor.Spec.supervise/2. See the Supervisor module for more information. " <>
|
||||
"Got: #{inspect(other)}"
|
||||
"invalid tuple specification given to supervise/2. If you are trying to use " <>
|
||||
"the map child specification that is part of the Elixir v1.5, use Supervisor.init/2 " <>
|
||||
"instead of Supervisor.Spec.supervise/2. See the Supervisor module for more information. " <>
|
||||
"Got: #{inspect other}"
|
||||
end
|
||||
|
||||
defp assert_unique_ids([id | rest]) do
|
||||
if id in rest do
|
||||
raise ArgumentError,
|
||||
"duplicated id #{inspect(id)} found in the supervisor specification, " <>
|
||||
"please explicitly pass the :id option when defining this worker/supervisor"
|
||||
"duplicated id #{inspect id} found in the supervisor specification, " <>
|
||||
"please explicitly pass the :id option when defining this worker/supervisor"
|
||||
else
|
||||
assert_unique_ids(rest)
|
||||
end
|
||||
@@ -222,15 +220,8 @@ defmodule Supervisor.Spec do
|
||||
Check the documentation for the `Supervisor.Spec` module for more
|
||||
information on the options.
|
||||
"""
|
||||
@spec worker(
|
||||
module,
|
||||
[term],
|
||||
restart: restart,
|
||||
shutdown: shutdown,
|
||||
id: term,
|
||||
function: atom,
|
||||
modules: modules
|
||||
) :: spec
|
||||
@spec worker(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
@@ -253,30 +244,24 @@ defmodule Supervisor.Spec do
|
||||
Check the documentation for the `Supervisor.Spec` module for more
|
||||
information on the options.
|
||||
"""
|
||||
@spec supervisor(
|
||||
module,
|
||||
[term],
|
||||
restart: restart,
|
||||
shutdown: shutdown,
|
||||
id: term,
|
||||
function: atom,
|
||||
modules: modules
|
||||
) :: spec
|
||||
@spec supervisor(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.put_new(options, :shutdown, :infinity)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, modules(module))
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, modules(module))
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args}, restart, shutdown, type, modules}
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
|
||||
defp modules(GenEvent), do: :dynamic
|
||||
defp modules(module), do: [module]
|
||||
defp modules(module), do: [module]
|
||||
end
|
||||
|
||||
+101
-107
@@ -78,19 +78,19 @@ defmodule System do
|
||||
`:micro_seconds` and `:nano_seconds` as time units although Elixir normalizes
|
||||
their spelling to match the SI convention.
|
||||
"""
|
||||
# TODO: Warn all old mappings once Elixir requires Erlang/OTP 19.1+
|
||||
@type time_unit ::
|
||||
:second
|
||||
| :millisecond
|
||||
| :microsecond
|
||||
| :nanosecond
|
||||
| pos_integer
|
||||
| :seconds
|
||||
| :milliseconds
|
||||
| :microseconds
|
||||
| :nanoseconds
|
||||
:second
|
||||
| :millisecond
|
||||
| :microsecond
|
||||
| :nanosecond
|
||||
| pos_integer
|
||||
# TODO: Deprecate these in Elixir 2.0
|
||||
| :seconds
|
||||
| :milliseconds
|
||||
| :microseconds
|
||||
| :nanoseconds
|
||||
|
||||
@base_dir :filename.join(__DIR__, "../../..")
|
||||
@base_dir :filename.join(__DIR__, "../../..")
|
||||
@version_file :filename.join(@base_dir, "VERSION")
|
||||
|
||||
defp strip(iodata) do
|
||||
@@ -101,7 +101,6 @@ defmodule System do
|
||||
case :file.read_file(path) do
|
||||
{:ok, binary} ->
|
||||
strip(binary)
|
||||
|
||||
_ ->
|
||||
""
|
||||
end
|
||||
@@ -110,21 +109,16 @@ defmodule System do
|
||||
# Read and strip the version from the VERSION file.
|
||||
defmacrop get_version do
|
||||
case read_stripped(@version_file) do
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
|
||||
# Returns OTP version that Elixir was compiled with.
|
||||
defmacrop get_otp_release do
|
||||
:erlang.list_to_binary(:erlang.system_info(:otp_release))
|
||||
end
|
||||
|
||||
# Tries to run "git rev-parse --short HEAD". In the case of success returns
|
||||
# the short revision hash. If that fails, returns an empty string.
|
||||
defmacrop get_revision do
|
||||
null =
|
||||
case :os.type() do
|
||||
case :os.type do
|
||||
{:win32, _} -> 'NUL'
|
||||
_ -> '/dev/null'
|
||||
end
|
||||
@@ -139,7 +133,7 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
IO.iodata_to_binary(:httpd_util.rfc1123_date())
|
||||
IO.iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -162,7 +156,7 @@ defmodule System do
|
||||
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
@spec version() :: String.t()
|
||||
@spec version() :: String.t
|
||||
def version, do: get_version()
|
||||
|
||||
@doc """
|
||||
@@ -172,23 +166,22 @@ defmodule System do
|
||||
"""
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
%{
|
||||
build: build(),
|
||||
date: get_date(),
|
||||
%{build: build(),
|
||||
date: get_date(),
|
||||
revision: revision(),
|
||||
version: version(),
|
||||
otp_release: get_otp_release()
|
||||
}
|
||||
version: version()}
|
||||
end
|
||||
|
||||
# Returns a string of the build info
|
||||
defp build do
|
||||
{:ok, v} = Version.parse(version())
|
||||
|
||||
revision_string = if v.pre != [] and revision() != "", do: " (#{revision()})", else: ""
|
||||
otp_version_string = " (compiled with OTP #{get_otp_release()})"
|
||||
|
||||
version() <> revision_string <> otp_version_string
|
||||
cond do
|
||||
([] == v.pre) or ("" == revision()) ->
|
||||
version()
|
||||
true ->
|
||||
"#{version()} (#{revision()})"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -196,7 +189,7 @@ defmodule System do
|
||||
|
||||
Returns the list of command line arguments passed to the program.
|
||||
"""
|
||||
@spec argv() :: [String.t()]
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
:elixir_config.get(:argv)
|
||||
end
|
||||
@@ -207,7 +200,7 @@ defmodule System do
|
||||
Changes the list of command line arguments. Use it with caution,
|
||||
as it destroys any previous argv information.
|
||||
"""
|
||||
@spec argv([String.t()]) :: :ok
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_config.put(:argv, args)
|
||||
end
|
||||
@@ -219,7 +212,7 @@ defmodule System do
|
||||
is not available.
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd() do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
|
||||
_ -> nil
|
||||
end
|
||||
@@ -241,9 +234,7 @@ defmodule System do
|
||||
"""
|
||||
def cwd! do
|
||||
cwd() ||
|
||||
raise RuntimeError,
|
||||
message:
|
||||
"could not get a current working directory, the current location is not accessible"
|
||||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -263,8 +254,7 @@ defmodule System do
|
||||
"""
|
||||
def user_home! do
|
||||
user_home() ||
|
||||
raise RuntimeError,
|
||||
message: "could not find the user home, please set the HOME environment variable"
|
||||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -282,8 +272,11 @@ defmodule System do
|
||||
Returns `nil` if none of the above are writable.
|
||||
"""
|
||||
def tmp_dir do
|
||||
write_env_tmp_dir('TMPDIR') || write_env_tmp_dir('TEMP') || write_env_tmp_dir('TMP') ||
|
||||
write_tmp_dir('/tmp') || ((cwd = cwd()) && write_tmp_dir(cwd))
|
||||
write_env_tmp_dir('TMPDIR') ||
|
||||
write_env_tmp_dir('TEMP') ||
|
||||
write_env_tmp_dir('TMP') ||
|
||||
write_tmp_dir('/tmp') ||
|
||||
((cwd = cwd()) && write_tmp_dir(cwd))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -294,16 +287,14 @@ defmodule System do
|
||||
"""
|
||||
def tmp_dir! do
|
||||
tmp_dir() ||
|
||||
raise RuntimeError,
|
||||
message:
|
||||
"could not get a writable temporary directory, " <>
|
||||
"please set the TMPDIR environment variable"
|
||||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
|
||||
"please set the TMPDIR environment variable"
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
case :os.getenv(env) do
|
||||
false -> nil
|
||||
tmp -> write_tmp_dir(tmp)
|
||||
tmp -> write_tmp_dir(tmp)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -313,13 +304,10 @@ defmodule System do
|
||||
case {stat.type, stat.access} do
|
||||
{:directory, access} when access in [:read_write, :write] ->
|
||||
IO.chardata_to_string(dir)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{:error, _} ->
|
||||
nil
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -337,7 +325,7 @@ defmodule System do
|
||||
The function must receive the exit status code as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_config.update(:at_exit, &[fun | &1])
|
||||
:elixir_config.update :at_exit, &[fun | &1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -365,11 +353,11 @@ defmodule System do
|
||||
The returned value is a map containing name-value pairs.
|
||||
Variable names and their values are strings.
|
||||
"""
|
||||
@spec get_env() :: %{optional(String.t()) => String.t()}
|
||||
@spec get_env() :: %{optional(String.t) => String.t}
|
||||
def get_env do
|
||||
Enum.into(:os.getenv(), %{}, fn var ->
|
||||
var = IO.chardata_to_string(var)
|
||||
[k, v] = String.split(var, "=", parts: 2)
|
||||
Enum.into(:os.getenv, %{}, fn var ->
|
||||
var = IO.chardata_to_string var
|
||||
[k, v] = String.split var, "=", parts: 2
|
||||
{k, v}
|
||||
end)
|
||||
end
|
||||
@@ -381,7 +369,7 @@ defmodule System do
|
||||
`varname` is a string, or `nil` if the environment
|
||||
variable is undefined.
|
||||
"""
|
||||
@spec get_env(String.t()) :: String.t() | nil
|
||||
@spec get_env(String.t) :: String.t | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(String.to_charlist(varname)) do
|
||||
false -> nil
|
||||
@@ -395,10 +383,10 @@ defmodule System do
|
||||
Returns the process identifier of the current Erlang emulator
|
||||
in the format most commonly used by the operating system environment.
|
||||
|
||||
For more information, see `:os.getpid/0`.
|
||||
For more information, see [`:os.getpid/0`](http://www.erlang.org/doc/man/os.html#getpid-0).
|
||||
"""
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: IO.iodata_to_binary(:os.getpid())
|
||||
def get_pid, do: IO.iodata_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Sets an environment variable value.
|
||||
@@ -409,11 +397,9 @@ defmodule System do
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
case :binary.match(varname, "=") do
|
||||
{_, _} ->
|
||||
raise ArgumentError,
|
||||
"cannot execute System.put_env/2 for key with \"=\", got: #{inspect(varname)}"
|
||||
|
||||
raise ArgumentError, "cannot execute System.put_env/2 for key with \"=\", got: #{inspect varname}"
|
||||
:nomatch ->
|
||||
:os.putenv(String.to_charlist(varname), String.to_charlist(value))
|
||||
:os.putenv String.to_charlist(varname), String.to_charlist(value)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -422,11 +408,11 @@ defmodule System do
|
||||
Sets multiple environment variables.
|
||||
|
||||
Sets a new value for each environment variable corresponding
|
||||
to each `{key, value}` pair in `enum`.
|
||||
to each key in `dict`.
|
||||
"""
|
||||
@spec put_env(Enumerable.t()) :: :ok
|
||||
@spec put_env(Enumerable.t) :: :ok
|
||||
def put_env(enum) do
|
||||
Enum.each(enum, fn {key, val} -> put_env(key, val) end)
|
||||
Enum.each enum, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -434,7 +420,7 @@ defmodule System do
|
||||
|
||||
Removes the variable `varname` from the environment.
|
||||
"""
|
||||
@spec delete_env(String.t()) :: :ok
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(String.to_charlist(varname))
|
||||
:ok
|
||||
@@ -450,7 +436,7 @@ defmodule System do
|
||||
Inlined by the compiler into `:erlang.get_stacktrace/0`.
|
||||
"""
|
||||
def stacktrace do
|
||||
:erlang.get_stacktrace()
|
||||
:erlang.get_stacktrace
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -474,7 +460,7 @@ defmodule System do
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For more information, see `:erlang.halt/1`.
|
||||
For more information, see [`:erlang.halt/1`](http://www.erlang.org/doc/man/erlang.html#halt-1).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -506,7 +492,7 @@ defmodule System do
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For more information, see `:init.stop/1`.
|
||||
For more information, see [`:init.stop/1`](http://erlang.org/doc/man/init.html#stop-1).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -603,16 +589,13 @@ defmodule System do
|
||||
## Shell commands
|
||||
|
||||
If you desire to execute a trusted command inside a shell, with pipes,
|
||||
redirecting and so on, please check `:os.cmd/1`.
|
||||
redirecting and so on, please check
|
||||
[`:os.cmd/1`](http://www.erlang.org/doc/man/os.html#cmd-1).
|
||||
"""
|
||||
@spec cmd(binary, [binary], keyword) :: {Collectable.t(), exit_status :: non_neg_integer}
|
||||
@spec cmd(binary, [binary], keyword) ::
|
||||
{Collectable.t, exit_status :: non_neg_integer}
|
||||
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||
assert_no_null_byte!(command, "System.cmd/3")
|
||||
|
||||
unless Enum.all?(args, &is_binary/1) do
|
||||
raise ArgumentError, "all arguments for System.cmd/3 must be binaries"
|
||||
end
|
||||
|
||||
cmd = String.to_charlist(command)
|
||||
|
||||
cmd =
|
||||
@@ -624,12 +607,11 @@ defmodule System do
|
||||
|
||||
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
|
||||
{initial, fun} = Collectable.into(into)
|
||||
|
||||
try do
|
||||
do_cmd(Port.open({:spawn_executable, cmd}, opts), initial, fun)
|
||||
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace()
|
||||
stacktrace = System.stacktrace
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
@@ -641,13 +623,13 @@ defmodule System do
|
||||
receive do
|
||||
{^port, {:data, data}} ->
|
||||
do_cmd(port, fun.(acc, {:cont, data}), fun)
|
||||
|
||||
{^port, {:exit_status, status}} ->
|
||||
{acc, status}
|
||||
end
|
||||
end
|
||||
|
||||
defp cmd_opts([{:into, any} | t], opts, _into), do: cmd_opts(t, opts, any)
|
||||
defp cmd_opts([{:into, any} | t], opts, _into),
|
||||
do: cmd_opts(t, opts, any)
|
||||
|
||||
defp cmd_opts([{:cd, bin} | t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:cd, bin} | opts], into)
|
||||
@@ -658,7 +640,8 @@ defmodule System do
|
||||
defp cmd_opts([{:stderr_to_stdout, true} | t], opts, into),
|
||||
do: cmd_opts(t, [:stderr_to_stdout | opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, false} | t], opts, into), do: cmd_opts(t, opts, into)
|
||||
defp cmd_opts([{:stderr_to_stdout, false} | t], opts, into),
|
||||
do: cmd_opts(t, opts, into)
|
||||
|
||||
defp cmd_opts([{:parallelism, bool} | t], opts, into) when is_boolean(bool),
|
||||
do: cmd_opts(t, [{:parallelism, bool} | opts], into)
|
||||
@@ -667,21 +650,20 @@ defmodule System do
|
||||
do: cmd_opts(t, [{:env, validate_env(enum)} | opts], into)
|
||||
|
||||
defp cmd_opts([{key, val} | _], _opts, _into),
|
||||
do: raise(ArgumentError, "invalid option #{inspect(key)} with value #{inspect(val)}")
|
||||
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
|
||||
|
||||
defp cmd_opts([], opts, into), do: {into, opts}
|
||||
defp cmd_opts([], opts, into),
|
||||
do: {into, opts}
|
||||
|
||||
defp validate_env(enum) do
|
||||
Enum.map(enum, fn
|
||||
Enum.map enum, fn
|
||||
{k, nil} ->
|
||||
{String.to_charlist(k), false}
|
||||
|
||||
{k, v} ->
|
||||
{String.to_charlist(k), String.to_charlist(v)}
|
||||
|
||||
other ->
|
||||
raise ArgumentError, "invalid environment key-value #{inspect(other)}"
|
||||
end)
|
||||
raise ArgumentError, "invalid environment key-value #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -810,9 +792,9 @@ defmodule System do
|
||||
@doc """
|
||||
Returns the OTP release number.
|
||||
"""
|
||||
@spec otp_release :: String.t()
|
||||
@spec otp_release :: String.t
|
||||
def otp_release do
|
||||
:erlang.list_to_binary(:erlang.system_info(:otp_release))
|
||||
:erlang.list_to_binary :erlang.system_info(:otp_release)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -862,36 +844,48 @@ defmodule System do
|
||||
defp assert_no_null_byte!(binary, operation) do
|
||||
case :binary.match(binary, "\0") do
|
||||
{_, _} ->
|
||||
raise ArgumentError,
|
||||
"cannot execute #{operation} for program with null byte, got: #{inspect(binary)}"
|
||||
|
||||
raise ArgumentError, "cannot execute #{operation} for program with null byte, got: #{inspect binary}"
|
||||
:nomatch ->
|
||||
binary
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_time_unit(:native), do: :native
|
||||
defp normalize_time_unit(:native),
|
||||
do: :native
|
||||
|
||||
defp normalize_time_unit(:second), do: :seconds
|
||||
defp normalize_time_unit(:millisecond), do: :milli_seconds
|
||||
defp normalize_time_unit(:microsecond), do: :micro_seconds
|
||||
defp normalize_time_unit(:nanosecond), do: :nano_seconds
|
||||
# TODO: Remove these mappings once Elixir requires Erlang/OTP 19.1
|
||||
defp normalize_time_unit(:second),
|
||||
do: :seconds
|
||||
defp normalize_time_unit(:millisecond),
|
||||
do: :milli_seconds
|
||||
defp normalize_time_unit(:microsecond),
|
||||
do: :micro_seconds
|
||||
defp normalize_time_unit(:nanosecond),
|
||||
do: :nano_seconds
|
||||
|
||||
defp normalize_time_unit(:seconds), do: :seconds
|
||||
defp normalize_time_unit(:milliseconds), do: :milli_seconds
|
||||
defp normalize_time_unit(:microseconds), do: :micro_seconds
|
||||
defp normalize_time_unit(:nanoseconds), do: :nano_seconds
|
||||
# TODO: Warn on Elixir 1.5
|
||||
defp normalize_time_unit(:seconds),
|
||||
do: :seconds
|
||||
defp normalize_time_unit(:milliseconds),
|
||||
do: :milli_seconds
|
||||
defp normalize_time_unit(:microseconds),
|
||||
do: :micro_seconds
|
||||
defp normalize_time_unit(:nanoseconds),
|
||||
do: :nano_seconds
|
||||
|
||||
defp normalize_time_unit(unit) when is_integer(unit) and unit > 0, do: unit
|
||||
defp normalize_time_unit(unit) when is_integer(unit) and unit > 0,
|
||||
do: unit
|
||||
|
||||
# TODO: Warn on Elixir 1.5
|
||||
defp normalize_time_unit(erlang_unit)
|
||||
when erlang_unit in [:milli_seconds, :micro_seconds, :nano_seconds] do
|
||||
when erlang_unit in [:milli_seconds, :micro_seconds, :nano_seconds] do
|
||||
erlang_unit
|
||||
end
|
||||
|
||||
defp normalize_time_unit(other) do
|
||||
raise ArgumentError,
|
||||
"unsupported time unit. Expected :second, :millisecond, " <>
|
||||
":microsecond, :nanosecond, or a positive integer, " <> "got #{inspect(other)}"
|
||||
"unsupported time unit. Expected :second, :millisecond, " <>
|
||||
":microsecond, :nanosecond, or a positive integer, " <>
|
||||
"got #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
+33
-49
@@ -84,7 +84,7 @@ defmodule Task do
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification id, defaults to the current module
|
||||
* `:id` - the child specification id, defauts to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:temporary`
|
||||
* `:shutdown` - how to shut down the child
|
||||
@@ -92,7 +92,7 @@ defmodule Task do
|
||||
Opposite to `GenServer`, `Agent` and `Supervisor`, a Task has
|
||||
a default `:restart` of `:temporary`. This means the task will
|
||||
not be restarted even if it crashes. If you desire the task to
|
||||
be restarted for non-successful exits, do:
|
||||
be restarted for non-successful exists, do:
|
||||
|
||||
use Task, restart: :transient
|
||||
|
||||
@@ -184,18 +184,18 @@ defmodule Task do
|
||||
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep do
|
||||
@opts unquote(opts)
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
spec = [
|
||||
id: opts[:id] || __MODULE__,
|
||||
start: Macro.escape(opts[:start]) || quote(do: {__MODULE__, :start_link, [arg]}),
|
||||
restart: opts[:restart] || :temporary,
|
||||
shutdown: opts[:shutdown] || 5000,
|
||||
type: :worker
|
||||
]
|
||||
|
||||
@doc false
|
||||
def child_spec(arg) do
|
||||
default = %{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [arg]},
|
||||
restart: :temporary
|
||||
}
|
||||
|
||||
Supervisor.child_spec(default, @opts)
|
||||
%{unquote_splicing(spec)}
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
@@ -389,7 +389,7 @@ defmodule Task do
|
||||
Enum.to_list(stream)
|
||||
|
||||
"""
|
||||
@spec async_stream(Enumerable.t(), module, atom, [term], keyword) :: Enumerable.t()
|
||||
@spec async_stream(Enumerable.t, module, atom, [term], keyword) :: Enumerable.t
|
||||
def async_stream(enumerable, module, function, args, options \\ [])
|
||||
when is_atom(module) and is_atom(function) and is_list(args) do
|
||||
build_stream(enumerable, {module, function, args}, options)
|
||||
@@ -414,7 +414,7 @@ defmodule Task do
|
||||
|
||||
See `async_stream/5` for discussion, options, and more examples.
|
||||
"""
|
||||
@spec async_stream(Enumerable.t(), (term -> term), keyword) :: Enumerable.t()
|
||||
@spec async_stream(Enumerable.t, (term -> term), keyword) :: Enumerable.t
|
||||
def async_stream(enumerable, fun, options \\ []) when is_function(fun, 1) do
|
||||
build_stream(enumerable, fun, options)
|
||||
end
|
||||
@@ -441,14 +441,13 @@ defmodule Task do
|
||||
@doc """
|
||||
Awaits a task reply and returns it.
|
||||
|
||||
In case the task process dies, the current process will exit with the same
|
||||
reason as the task.
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value of `5000`. If the
|
||||
timeout is exceeded, then the current process will exit. If the task process
|
||||
is linked to the current process which is the case when a task is started with
|
||||
`async`, then the task process will also exit. If the task process is trapping
|
||||
exits or not linked to the current process, then it will continue to run.
|
||||
If the timeout is exceeded, `await` will exit; however,
|
||||
the task will continue to run. When the calling process exits, its
|
||||
exit signal will terminate the task if it is not trapping exits.
|
||||
|
||||
This function assumes the task's monitor is still active or the monitor's
|
||||
`:DOWN` message is in the message queue. If it has been demonitored, or the
|
||||
@@ -484,7 +483,6 @@ defmodule Task do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
reply
|
||||
|
||||
{:DOWN, ^ref, _, proc, reason} ->
|
||||
exit({reason(reason, proc), {__MODULE__, :await, [task, timeout]}})
|
||||
after
|
||||
@@ -498,19 +496,17 @@ defmodule Task do
|
||||
# TODO: Remove on 2.0
|
||||
# (hard-deprecated in elixir_dispatch)
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value(tasks, fn
|
||||
Enum.find_value tasks, fn
|
||||
%Task{ref: ^ref} = task ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{reply, task}
|
||||
|
||||
%Task{} ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def find(tasks, {:DOWN, ref, _, proc, reason} = msg) when is_reference(ref) do
|
||||
find = fn %Task{ref: task_ref} -> task_ref == ref end
|
||||
|
||||
if Enum.find(tasks, find) do
|
||||
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
|
||||
end
|
||||
@@ -560,7 +556,7 @@ defmodule Task do
|
||||
handle this case and return the result.
|
||||
"""
|
||||
@spec yield(t, timeout) :: {:ok, term} | {:exit, term} | nil
|
||||
def yield(task, timeout \\ 5000)
|
||||
def yield(task, timeout \\ 5_000)
|
||||
|
||||
def yield(%Task{owner: owner} = task, _) when owner != self() do
|
||||
raise ArgumentError, invalid_owner_error(task)
|
||||
@@ -571,10 +567,8 @@ defmodule Task do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{:ok, reply}
|
||||
|
||||
{:DOWN, ^ref, _, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :yield, [task, timeout]}})
|
||||
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
{:exit, reason}
|
||||
after
|
||||
@@ -631,7 +625,7 @@ defmodule Task do
|
||||
end
|
||||
|
||||
In the example above, we create tasks that sleep from 1
|
||||
up to 10 seconds and return the number of seconds they slept.
|
||||
up to 10 seconds and return the amount of seconds they slept.
|
||||
If you execute the code all at once, you should see 1 up to 5
|
||||
printed, as those were the tasks that have replied in the
|
||||
given time. All other tasks will have been shut down using
|
||||
@@ -641,7 +635,6 @@ defmodule Task do
|
||||
def yield_many(tasks, timeout \\ 5000) do
|
||||
timeout_ref = make_ref()
|
||||
timer_ref = Process.send_after(self(), timeout_ref, timeout)
|
||||
|
||||
try do
|
||||
yield_many(tasks, timeout_ref, :infinity)
|
||||
catch
|
||||
@@ -671,9 +664,11 @@ defmodule Task do
|
||||
|
||||
^timeout_ref ->
|
||||
[{task, nil} | yield_many(rest, timeout_ref, 0)]
|
||||
|
||||
after
|
||||
timeout ->
|
||||
[{task, nil} | yield_many(rest, timeout_ref, 0)]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -687,7 +682,7 @@ defmodule Task do
|
||||
Returns `{:ok, reply}` if the reply is received while shutting down the task,
|
||||
`{:exit, reason}` if the task died, otherwise `nil`.
|
||||
|
||||
The second argument is either a timeout or `:brutal_kill`. In case
|
||||
The shutdown method is either a timeout or `:brutal_kill`. In case
|
||||
of a `timeout`, a `:shutdown` exit signal is sent to the task process
|
||||
and if it does not exit within the timeout, it is killed. With `:brutal_kill`
|
||||
the task is killed straight away. In case the task terminates abnormally
|
||||
@@ -705,10 +700,10 @@ defmodule Task do
|
||||
`{:exit, :noproc}` as the result or exit reason can not be determined.
|
||||
"""
|
||||
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, term} | {:exit, term} | nil
|
||||
def shutdown(task, shutdown \\ 5000)
|
||||
def shutdown(task, shutdown \\ 5_000)
|
||||
|
||||
def shutdown(%Task{pid: nil} = task, _) do
|
||||
raise ArgumentError, "task #{inspect(task)} does not have an associated task process"
|
||||
raise ArgumentError, "task #{inspect task} does not have an associated task process"
|
||||
end
|
||||
|
||||
def shutdown(%Task{owner: owner} = task, _) when owner != self() do
|
||||
@@ -722,10 +717,8 @@ defmodule Task do
|
||||
case shutdown_receive(task, mon, :brutal_kill, :infinity) do
|
||||
{:down, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, :brutal_kill]}})
|
||||
|
||||
{:down, _, reason} ->
|
||||
{:exit, reason}
|
||||
|
||||
result ->
|
||||
result
|
||||
end
|
||||
@@ -734,14 +727,11 @@ defmodule Task do
|
||||
def shutdown(%Task{pid: pid} = task, timeout) do
|
||||
mon = Process.monitor(pid)
|
||||
exit(pid, :shutdown)
|
||||
|
||||
case shutdown_receive(task, mon, :shutdown, timeout) do
|
||||
{:down, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, timeout]}})
|
||||
|
||||
{:down, _, reason} ->
|
||||
{:exit, reason}
|
||||
|
||||
result ->
|
||||
result
|
||||
end
|
||||
@@ -750,17 +740,17 @@ defmodule Task do
|
||||
## Helpers
|
||||
|
||||
defp reason(:noconnection, proc), do: {:nodedown, monitor_node(proc)}
|
||||
defp reason(reason, _), do: reason
|
||||
defp reason(reason, _), do: reason
|
||||
|
||||
defp monitor_node(pid) when is_pid(pid), do: node(pid)
|
||||
defp monitor_node({_, node}), do: node
|
||||
defp monitor_node({_, node}), do: node
|
||||
|
||||
# spawn a process to ensure task gets exit signal if process dies from exit signal
|
||||
# between unlink and exit.
|
||||
defp exit(task, reason) do
|
||||
caller = self()
|
||||
ref = make_ref()
|
||||
enforcer = spawn(fn -> enforce_exit(task, reason, caller, ref) end)
|
||||
enforcer = spawn(fn() -> enforce_exit(task, reason, caller, ref) end)
|
||||
Process.unlink(task)
|
||||
Process.exit(task, reason)
|
||||
send(enforcer, {:done, ref})
|
||||
@@ -769,9 +759,8 @@ defmodule Task do
|
||||
|
||||
defp enforce_exit(pid, reason, caller, ref) do
|
||||
mon = Process.monitor(caller)
|
||||
|
||||
receive do
|
||||
{:done, ^ref} -> :ok
|
||||
{:done, ^ref} -> :ok
|
||||
{:DOWN, ^mon, _, _, _} -> Process.exit(pid, reason)
|
||||
end
|
||||
end
|
||||
@@ -781,15 +770,12 @@ defmodule Task do
|
||||
{:DOWN, ^mon, _, _, :shutdown} when type in [:shutdown, :timeout_kill] ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
flush_reply(ref)
|
||||
|
||||
{:DOWN, ^mon, _, _, :killed} when type == :brutal_kill ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
flush_reply(ref)
|
||||
|
||||
{:DOWN, ^mon, _, proc, :noproc} ->
|
||||
reason = flush_noproc(ref, proc, type)
|
||||
flush_reply(ref) || reason
|
||||
|
||||
{:DOWN, ^mon, _, proc, reason} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
flush_reply(ref) || {:down, proc, reason}
|
||||
@@ -812,10 +798,8 @@ defmodule Task do
|
||||
receive do
|
||||
{:DOWN, ^ref, _, _, :shutdown} when type in [:shutdown, :timeout_kill] ->
|
||||
nil
|
||||
|
||||
{:DOWN, ^ref, _, _, :killed} when type == :brutal_kill ->
|
||||
nil
|
||||
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
{:down, proc, reason}
|
||||
after
|
||||
@@ -826,6 +810,6 @@ defmodule Task do
|
||||
end
|
||||
|
||||
defp invalid_owner_error(task) do
|
||||
"task #{inspect(task)} must be queried from the owner but was queried from #{inspect(self())}"
|
||||
"task #{inspect task} must be queried from the owner but was queried from #{inspect self()}"
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user