Compare commits

...
966 Commits
Author SHA1 Message Date
José Valim 34df817638 Release v1.0.2 2014-10-21 15:19:58 -02:00
José Valim 311a5790d6 Update release instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	Makefile
2014-10-21 15:02:37 -02:00
José Valim b4c75f97b4 Fail to compile try without else/catch/after/rescue, closes #2797
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:55 -02:00
José Valim 70692e6618 Improve token errors for aliases, closes #2818
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:47 -02:00
José Valim 529d584262 Unify error reporting from EEx, closes #2833
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/eex/test/eex_test.exs
2014-10-21 14:57:36 -02:00
José Valim c0fb275d5b Properly handle eval_failure warnings, closes #2835
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:56:55 -02:00
José Valim 24f46be144 Use unamed tables for module compilation
* We have created a main named table that keeps all module information

* We have reduced the amount of tables by storing internal, docs and
  attributes altogether and using table looks for retriving the relevant
  information

* At this moment, creating a module in Elixir defines three tables,
  one for data, another for functions and another for clauses

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-17 13:53:04 +02:00
José Valim 0fe3da9dea Update CHANGELOG 2014-10-15 20:54:31 +02:00
José Valim 03d0ad3f03 No longer inline binary expressions in EEx
String.Chars is now always inlined by the compiler.

Closes #2815

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:48 +02:00
José Valim a87f43701f Merge pull request #2819 from benjamintanweihao/patch-3
Fix missing concatenation operator.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:34 +02:00
k1complete dce3a3a2a5 set null prompt function in dumbterm mode
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:19 +02:00
Eduardo Gurgel 7b20084b28 Add OTP 17.3 to run on Travis CI
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:57 +02:00
José Valim 542808102f Build .beam location from scratch on mix compile.elixir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:41 +02:00
Eric Meadows-Jönsson fd18b6a41f Fix String.replace/4 typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 19:41:53 +02:00
Eric Meadows-Jönsson 4c9c95f80f Add task for pushing standalone mix
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 15:23:47 +02:00
José Valim 89460154bb Release v1.0.1 2014-10-07 13:33:07 +02:00
José Valim b026e8877a --elixirc-paths should be a subset of project paths
Since the manifest is shared, --elixirc-paths must
be a subset as we need to know what to change and what
to not change in the manifest file.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-07 13:25:42 +02:00
José Valim 597381b2e0 Write to manifest when file is removed
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 22:10:01 +02:00
José Valim df70729b14 Test that we do purge and delete artifacts
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:26:26 +02:00
José Valim 61b116c1a5 Also purge and delete during tests
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:21:31 +02:00
José Valim d37a10bbd0 Purge and delete modules before compiling them
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:21:25 +02:00
José Valim 5d75c5c861 Refactor and add new rewrite rules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 16:46:01 +02:00
Eric Meadows-Jönsson db4089cc71 Don't crash on malformed proxy env var
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-06 11:35:24 +02:00
José Valim f9f4f14edf Update CHANGELOG 2014-10-02 21:10:39 +02:00
Eric Meadows-Jönsson 58946ce879 Give higher prio to powershell on windows
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-01 18:32:23 +02:00
Eric Meadows-Jönsson de1d5ff793 Send elixir version as parameter when installing hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:22:07 +02:00
Eric Meadows-Jönsson a4be8fb260 Fix :stderr_to_stdout option
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:21:58 +02:00
Eric Meadows-Jönsson 031a27f5fa Fix spec for surround_many
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:21:50 +02:00
José Valim f3c1931cac Update CHANGELOG 2014-09-27 11:34:48 +02:00
José Valim 2e48d81c20 Move printing out of copy_path, always create dir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:33:03 +02:00
José Valim e7ef3fc95d Merge pull request #2787 from elixir-lang/emj-hex-file-error
Report correct location if local.hex failed

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:32:52 +02:00
José Valim 2d01aa1c6a Do not pass false into port command
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:08:04 +02:00
José Valim ae07124550 Merge pull request #2789 from alco/system-cmd-path-resolution
System.cmd path resolution

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:07:28 +02:00
José Valim 1f744f4dac Also remove elixir_exp from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-25 10:37:10 +02:00
José Valim ad88ac642f Remove :elixir module from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-25 10:30:19 +02:00
Eric Meadows-Jönsson f7956e9d80 Bump Hex requirement
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-23 21:44:24 +02:00
Eric Meadows-Jönsson da6892aa96 Add MIX_ENV output to archive.build
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-22 23:32:07 +02:00
José Valim 7a3bd20804 Update CHANGELOG 2014-09-20 13:43:52 +02:00
José Valim f7015166dc Filter out :elixir_lexical from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-20 13:41:47 +02:00
José Valim c6f346a355 Change version requirement to be MAJOR.MINOR
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-20 13:41:41 +02:00
José Valim 911a3ba97f Update release instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-19 18:26:40 +02:00
José Valim a1668a6b28 Ensure Mix.Config is deep merged
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-16 19:53:47 +02:00
José Valim a4cb74a83f Include stracktrace on ExUnit timeouts
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-16 19:53:43 +02:00
José Valim bbf9552186 Add tests for add_process_handler/2
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-15 19:33:17 +02:00
Eric Meadows-Jönsson e621b5828e Load dependencies before deps.check compiles
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-15 10:40:35 +02:00
José Valim 68e282c50a Do not require {ref, :done}, instead allow sync removal
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-14 23:15:57 +02:00
José Valim 403a50fbcd Ensure chunk/4 is haltable
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-13 16:14:23 +02:00
José Valim 8e98cd4f41 Start v1.0.1-dev 2014-09-12 12:34:52 +02:00
budstein 499c385000 Fix a typo in Code.eval_file's doc
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:37 +02:00
Alexei Sholik aecafc099d Fix a typo in IO.inspect’s doc
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:31 +02:00
Haoming Zhu 785c7d700a fix previous commit typo bug
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:15 +02:00
Bill Gathen cecc8a0f95 Fix my own typo in helpers.ex
Brand-new language, and I put a ding in it. Sorry about that. :-(

Congratulations on 1.0!

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:32:53 +02:00
José Valim 52ff7e9686 Release v1.0.0 2014-09-10 14:11:13 +02:00
José Valim e2eb2cfdb1 Remove broken List.unzip 2014-09-10 01:24:34 +02:00
José Valim a5b4c1c04d Document and test Logger functions return :ok 2014-09-09 16:58:48 +02:00
José Valim e38f960d7e Update CHANGELOG 2014-09-09 16:55:47 +02:00
José Valim 9e74ee686e Add Logger.enable/1 and Logger.disable/1 2014-09-09 16:54:02 +02:00
José Valim e068625d7d Add integer guards around Enum.fetch/2 2014-09-09 00:09:18 +02:00
José Valim 00e8446a58 Remove support for inner swap_handler 2014-09-08 19:07:13 +02:00
José Valim 2aecfede5b Merge pull request #2759 from mikepack/transform-doc-fix
Fix typos in Stream.transform/3 docs
2014-09-07 22:29:51 +02:00
Mike Pack 9eebefb976 Fix typos in Stream.transform/3 docs 2014-09-07 14:24:28 -06:00
José Valim b709cba371 Properly document Task.Supervisor options 2014-09-07 16:40:01 +02:00
José Valim 1d919f59e3 Remove deprecated code 2014-09-07 15:36:07 +02:00
José Valim 0e8d3f9208 Start v1.0.0 2014-09-07 13:20:02 +02:00
José Valim 8931f34582 Release v1.0.0-rc2 2014-09-07 12:49:56 +02:00
José Valim 843706e36e Add free operators
<<~, ~>>, <~, ~>, <~>, <|>
2014-09-07 12:42:10 +02:00
José Valim 172bfdb128 Revert "Merge pull request #2702 from alexrp/master"
<~ is already a valid expression in Elixir.

This reverts commit 2c741ec6ae, reversing
changes made to 12a457829f.
2014-09-07 12:12:35 +02:00
José Valim 2c741ec6ae Merge pull request #2702 from alexrp/master
Introduce new operators intended for use by parser combinator libraries.
2014-09-07 12:06:29 +02:00
Eric Meadows-Jönsson 12a457829f Return the proper exit code from powershell 2014-09-06 21:10:48 +02:00
José Valim 0ff6850237 Raise if status code != 0 and use non-verbose modes 2014-09-06 20:33:02 +02:00
Eric Meadows-Jönsson 63f6088476 Bump Hex requirement 2014-09-06 20:13:37 +02:00
Eric Meadows-Jönsson 206f6413f8 Merge pull request #2756 from elixir-lang/emj-archive-shell
Fetch Hex with wget or curl
2014-09-06 20:12:49 +02:00
Eric Meadows-Jönsson a2c0424b37 Add file exists check on windows when installing archive 2014-09-06 19:14:36 +02:00
Eric Meadows-Jönsson 6ea7d3cef6 Fetch Hex with wget or curl
This way we can use https and do peer verification without bundling
certificates with elixir.
2014-09-06 16:37:57 +02:00
José Valim 2cafd8c060 Ensure we don't look up inexisting variables for default args 2014-09-05 17:59:45 +02:00
José Valim 7271dfebfb Update CHANGELOG 2014-09-05 16:52:26 +02:00
José Valim 2eff3590c1 Lowercase drive letters from cwd, closes #2747 2014-09-05 16:43:55 +02:00
José Valim b578f45060 Fix IEx test 2014-09-05 16:35:30 +02:00
José Valim a34ccfe129 Make the regex type non-opaque 2014-09-05 16:27:20 +02:00
José Valim 8f4442f2df Test :sys functions with GenEvent 2014-09-05 16:15:28 +02:00
José Valim 598e42e00b Tidy up GenEvent docs and test suite 2014-09-05 13:34:26 +02:00
José Valim b93912b9dc Deprecate monitor option in GenEvent 2014-09-05 13:20:40 +02:00
José Valim 71eca8a5d6 Move from already_added to already_present 2014-09-05 12:50:42 +02:00
José Valim f0bb0e1f7f Do not wrap process handlers in a tuple 2014-09-05 12:26:59 +02:00
José Valim 2cb0639208 Move from module_not_found to the more generic handler_not_found 2014-09-05 11:59:53 +02:00
José Valim 081e3b36ec Allow only one process handler per process 2014-09-05 11:51:33 +02:00
José Valim d521d7927f Leave clause in case the handler can fail 2014-09-04 19:21:37 +02:00
José Valim 7313d26720 Remove deprecated branches 2014-09-04 13:58:58 +02:00
José Valim 577a07d47f Do not consider handler swap to be failures 2014-09-04 13:24:17 +02:00
José Valim f6093d40d7 Tag all doctest tests 2014-09-04 12:10:35 +02:00
José Valim 1dd034d4aa Merge pull request #2752 from fishcakez/dialyzer_warns
Fix some dialyzer warnings
2014-09-03 21:05:29 +02:00
James Fish 2417b196a4 Fix elixir dialyzer warnings 2014-09-03 19:29:01 +01:00
James Fish 50bb712e22 Fix iex dialyzer warnings 2014-09-03 19:24:32 +01:00
James Fish 7b0747c907 Fix :sys.replace_state/2,3 for GenEvent 2014-09-03 19:24:01 +01:00
James Fish b878e414de Fix mix dialyzer warnings and improve task specs 2014-09-03 19:23:29 +01:00
James Fish c447868e0f Fix Mix lock files to use maps 2014-09-03 19:22:42 +01:00
James Fish 681d5aa9c4 Fix logger dialyzer warnings 2014-09-03 19:20:09 +01:00
James Fish 0263dcc621 Don't check for race conditions with dialyzer
For more information:
http://erlang.org/pipermail/erlang-bugs/2014-July/004518.html
2014-09-03 19:13:39 +01:00
José Valim 1ae24c5c5d Update CHANGELOG 2014-09-03 14:38:28 +02:00
José Valim 6c02ebe4af Merge pull request #2720 from edgurgel/print-mailbox-on-failing-assert-receive
Print process mailbox on failing assert_receive
2014-09-03 14:24:16 +02:00
José Valim a08e44c5a9 Default max_restarts to 3 (closer to Erlang) 2014-09-03 14:23:03 +02:00
José Valim 08f30098db Improve iex error message 2014-09-03 14:22:16 +02:00
José Valim 5dbd91e4fa Merge pull request #2746 from styx/master
Fixes #2744
2014-09-03 13:46:23 +02:00
Mikhail S. Pobolovets 5b2eb54794 #2744: Ensure Path.relative_to is case insensitive on the drive letter
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-03 14:40:03 +03:00
José Valim 2c17007693 Merge pull request #2749 from alco/macro-env-doc
Document how to obtain a custom instance of Macro.Env
2014-09-03 13:13:15 +02:00
Alexei Sholik 2770c32f3d Document how to obtain a custom instance of Macro.Env 2014-09-03 11:10:07 +03:00
José Valim 8c8f6d8346 Merge pull request #2743 from fishcakez/loggertest_exit_signal_rc
Fix exit signal race condition in logger tests
2014-09-02 12:22:22 +02:00
James Fish 3950130e73 Fix exit signal race condition in logger tests
Test process must wait for exit signal before untrapping exits,
otherwise exit signal may cause it to exit.
2014-09-02 11:07:18 +01:00
Alex Rønne Petersen c8bcd89c7c Introduce new operators intended for use by parser combinator libraries.
The expected definitions (which are by no means required) in
parser combinator speak are:

* `p  ~>> f`   =  `bind(p, f)`
* `p1 ~>  p2`  =  `pair_right(p1, p2)`
* `p1 <~  p2`  =  `pair_left(p1, p2)`
* `p1 <~> p2`  =  `pair_both(p1, p2)`
* `p  |~> f`   =  `map(p, f)`
* `1p <|> p2`  =  `either(p1, p2)`
2014-09-02 01:57:50 +02:00
José Valim 7f7aaa115f Merge pull request #2736 from fishcakez/logger_stopped_exit
Fix Logger.log/2 not to raise on Logger exit
2014-09-01 19:33:11 -04:00
José Valim 53aeb26407 Merge pull request #2741 from styx/master
Fixes #2737
2014-09-01 19:31:55 -04:00
Mikhail S. Pobolovets bba0d12510 #2737: deps.unlock should warn when given missing dependency
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-01 17:53:03 +03:00
Eric Meadows-Jönsson 035d8227a5 Merge pull request #2740 from styx/patch-1
Typo fix
2014-09-01 13:36:43 +02:00
Eric Meadows-Jönsson 003a4da87e Merge pull request #2738 from mgwidmann/master
Correct documentation for proper grammer.
2014-09-01 13:36:17 +02:00
Mikhail S. Pobolovets 1977631b7f Typo fix 2014-09-01 11:58:09 +03:00
matt c9a0ec000a Correct documentation for proper grammer. 2014-08-31 23:55:41 -04:00
Eric Meadows-Jönsson ba842ba113 No commas in deps.unlock 2014-08-31 19:49:50 +02:00
James Fish 6304919d0d Update Logger mode after configuration
Compute mode after configure as possible mode should change.
2014-08-31 17:04:23 +01:00
James Fish d2d27e96e5 Fix Logger.log/2 not to raise on Logger exit 2014-08-31 16:40:57 +01:00
Eric Meadows-Jönsson fc6f7c532c Fix unused warnings 2014-08-31 09:01:00 +02:00
José Valim 1bee85e518 Merge pull request #2734 from gjaldon/clearer-unquote-splicing-error
Friendlier unquote_splicing error with hints
2014-08-30 14:58:30 -04:00
Gabriel Jaldon 66b211987d Friendlier unquote_splicing error with hints 2014-08-31 02:01:17 +08:00
José Valim 15fdae4de6 Start v1.0.0-rc2 2014-08-30 13:38:53 -04:00
José Valim 5b4497fa6b Release v1.0.0-rc1 2014-08-30 13:33:02 -04:00
José Valim 612e124ec9 Add tests for sync_threshold 2014-08-29 08:13:53 -04:00
José Valim abd2b35738 Get rid of Logger timeout config, closes #2724 2014-08-29 08:01:03 -04:00
José Valim 2afb875099 Merge pull request #2725 from MSch/document-compile-attr-accumulation
Document that @compile accumulates.
2014-08-28 14:10:41 -04:00
Martin Schürrer 88507f65f8 Document that @compile accumulates. 2014-08-28 20:05:56 +02:00
José Valim 275d1f5494 Straight up and unify impls of clean, deps.clean and deps.unlock 2014-08-28 13:00:36 -04:00
José Valim e6420c8d13 Merge pull request #2715 from nurugger07/remove_unused_deps
Add an unused flag to deps.clean & deps.unlock
2014-08-28 11:38:46 -04:00
Eduardo Gurgel 60bcf1cadb Print process mailbox on failing assert_receive 2014-08-29 00:33:48 +12:00
Eduardo Gurgel d86ca5826c Format multiline AssertionError message with correct padding 2014-08-29 00:33:25 +12:00
José Valim 2af00f4c33 Merge pull request #2723 from edgurgel/fix-private-function-on-algebra
Make do_surround_many private on Inspect.Algebra
2014-08-27 16:08:55 -04:00
Eduardo Gurgel 0fe86e77c1 Make do_surround_many private on Inspect.Algebra 2014-08-28 08:01:32 +12:00
José Valim 1ca451f779 Normalize is_guard checks 2014-08-27 15:28:31 -04:00
José Valim ebe0c8cf8e Update CHANGELOG 2014-08-27 12:21:12 -04:00
José Valim de9feb26d5 Deprecate x.Alias syntax 2014-08-27 12:17:15 -04:00
José Valim b8528f6c8e Remove warnings from the test suite 2014-08-25 14:42:24 -04:00
Alexei Sholik c9fbbac391 Add version checks and proper error formatting to escripts. Closes #2709
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-25 14:26:54 -04:00
José Valim 98e1b4ccf8 Ensure we tag the destination when compiling through mix
Closes #2533
2014-08-25 14:26:10 -04:00
Alexei Sholik ae19491620 Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2014-08-23 23:24:48 +03:00
Alexei Sholik 64f3760b19 Add missing fixtures for escript test 2014-08-23 23:16:29 +03:00
José Valim 0b38bc050f Demonitor and flush when appropriate 2014-08-23 15:56:38 -04:00
José Valim 9431338654 Add timeouts to ExUnit, closes #2660 2014-08-23 15:55:33 -04:00
José Valim 88be750a32 import_config/1 now accepts wildcards 2014-08-23 15:14:12 -04:00
José Valim 032c8b8291 Update CHANGELOG 2014-08-23 14:42:52 -04:00
José Valim 2d90d8ac60 Remove code duplication on watcher 2014-08-23 14:38:28 -04:00
José Valim a8b2cb99ee Use to_string when possible on Logger.log 2014-08-23 14:34:51 -04:00
José Valim 68274311fb Merge pull request #2712 from fishcakez/logger_rc
Fix logger watcher race condition
2014-08-23 14:34:05 -04:00
José Valim 88f84f80b5 Merge pull request #2713 from fishcakez/logger_eh_racecon
Fix race condition in Logger.ErrorHandlerTest
2014-08-23 14:32:48 -04:00
James Fish 2bf898e8fe Fix race condition in Logger.ErrorHandlerTest
When crashing the error logger handler, the handler's watcher could log
its message before Logger was flushed, causing the watcher's log
message to appear in the first, rather than second, capture_log.
2014-08-23 19:20:06 +01:00
José Valim d45f451943 Allow configurable timeout and catch exits on logger 2014-08-23 14:09:11 -04:00
James Fish c0a075724f Fix logger watcher race condition
Previously a backend could be removed from logger and the watcher would
not have exited before the remove_backend/1 call returned. This meant
that a new watcher could not always be started because the child
(watcher) might still exist but be about to die.
2014-08-23 18:59:13 +01:00
José Valim f1a39375c9 Do not crash the caller when Logger is not available
Instead, return :ok | {:error, :not_available} and leave it up
to the caller to decide.
2014-08-23 13:43:58 -04:00
Alexei Sholik b9c1b22163 Add protocol consolidation support to escript.build. Closes #2699 2014-08-23 20:40:56 +03:00
José Valim be75e3be17 Purge in memory in modules before consolidating, closes #2700 2014-08-23 13:32:19 -04:00
José Valim 9793caa1ef Ensure we automatically load deps on compile 2014-08-23 12:22:38 -04:00
José Valim 0792fe2c52 Merge pull request #2708 from alco/typo
Fix a typo in Mix.Project.compile_path doc
2014-08-22 12:02:31 -04:00
Alexei Sholik 4408aa3450 Fix a typo in Mix.Project.compile_path doc 2014-08-22 18:33:24 +03:00
Eric Meadows-Jönsson e4ba8d5d21 Mix should not depend directly on Logger 2014-08-22 15:56:25 +02:00
Eric Meadows-Jönsson 689f14a867 Always compile project if task cannot be found
Closes #2704.
2014-08-21 15:26:58 +02:00
Eric Meadows-Jönsson aa5b414e8e Merge pull request #2703 from Kosmas/master
Update CONTRIBUTING.md
2014-08-21 12:19:11 +02:00
Kosmas Chatzimichalis 07467a733b Update CONTRIBUTING.md
Corrected 'should be send' to 'should be sent'
2014-08-21 11:34:34 +02:00
Alexei Sholik 74391b793c Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2014-08-20 20:56:20 +03:00
Alexei Sholik f0b8bc1756 Use common code for Elixir and Erlang escripts 2014-08-20 20:38:24 +03:00
José Valim 3e54b95bf2 Fix the build 2014-08-20 11:30:30 -04:00
José Valim 313d26cb3b Use task start to get better reports 2014-08-20 10:28:11 -04:00
José Valim 44b85be014 Add Task.start/1 and Task.start/3 2014-08-20 10:27:23 -04:00
José Valim e344b1f9d8 Add a small note to UndefinedFunctionError when the module is not available 2014-08-20 10:22:36 -04:00
Eric Meadows-Jönsson f5f4205969 Merge pull request #2698 from Kosmas/master
Update CONTRIBUTING.md
2014-08-20 15:08:20 +02:00
Kosmas Chatzimichalis 538c717ded Update CONTRIBUTING.md
renamed 'issues tracker' to 'issue tracker' for consistency
2014-08-20 14:54:50 +02:00
Eric Meadows-Jönsson 8dd81e661a Merge pull request #2697 from hamiltop/patch-1
Fix docs (missing indentation in examples)
2014-08-20 11:24:32 +02:00
hamiltop 26b61b98f9 Fix docs (missing indentation in examples) 2014-08-19 20:28:24 -07:00
Eric Meadows-Jönsson ab9450d92f Don't print the underlying type for @opaque
Closes #2612
2014-08-19 17:14:30 +02:00
Eric Meadows-Jönsson a4a23b9a93 Fix Enum.traverse deprecated warning doctest 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 2f83ea5a7f Wrap erlang macros in () 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 7940d223ec Move elixir.hrl to src/ 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson c6fa951520 Better error message on nbsp 2014-08-19 17:00:58 +02:00
José Valim e5c8194a70 Rename :add_stream_handler to :add_process_handler 2014-08-19 09:55:26 -04:00
Eric Meadows-Jönsson 4a7e3dd11f Document alias arguments 2014-08-19 14:16:19 +02:00
José Valim 638fccb79d Remove duration from GenEvent.Stream 2014-08-19 07:34:49 -04:00
José Valim e9d84ec43b Allow gen_event_EXIT notifications to be disabled for streams 2014-08-19 07:07:23 -04:00
Eric Meadows-Jönsson bb35b9afb1 Include makefile in precompiled zip 2014-08-18 23:11:27 +02:00
Eric Meadows-Jönsson 1496c04592 Improve error message for invalid typespec 2014-08-18 23:08:26 +02:00
José Valim 3e9725a5a8 Make tests that remove the logging backend sync 2014-08-18 16:51:38 -04:00
José Valim 1565ec7658 Update CHANGELOG 2014-08-18 16:43:17 -04:00
José Valim f1aa2f01c7 Merge pull request #2695 from elixir-lang/jv-ge
GenEvent++
2014-08-18 16:33:11 -04:00
José Valim 7284542116 Split GenEvent.Stream into its own file 2014-08-18 16:28:25 -04:00
José Valim 4f17e8fd1c Also tag notify messages 2014-08-18 16:20:25 -04:00
José Valim f536507b9d Make the mode a property of the sender 2014-08-18 15:38:55 -04:00
José Valim 592300a0f0 Ensure Logger watcher monitors event manager 2014-08-18 09:18:28 -04:00
Alexei Sholik 28b1bd16de Document the :language setting 2014-08-18 16:12:15 +03:00
José Valim 19fdd84f80 Update docs 2014-08-17 19:48:50 -04:00
José Valim 631e2a051a Document lack of guaranteed on monitored handlers 2014-08-17 18:44:06 -04:00
José Valim df0f7b5f95 Fix failures in logger 2014-08-17 18:36:59 -04:00
José Valim a0dfc4160e Ensure proper whereis expansion 2014-08-17 18:28:14 -04:00
José Valim ee8331023e Do not add duplicated handlers 2014-08-17 16:55:11 -04:00
José Valim a2ee71f5ab Implement some of fishcakez feedback 2014-08-17 13:18:55 -04:00
José Valim 60163194e2 Improve coverage for GenEvent 2014-08-17 09:20:02 -04:00
José Valim ba37682300 Merge pull request #2691 from herenowcoder/patch-1
fix doc typo in Macro.expand/2
2014-08-17 08:57:53 -04:00
Wojciech Kaczmarek 15265b9352 fix doc typo in Macro.expand/2 2014-08-17 14:55:25 +02:00
Alexei Sholik 0a929310de Support building Erlang-based escripts in mix escript.build 2014-08-17 15:19:42 +03:00
José Valim a8670c1cfa Do not pre-compile a project if an alias was found
Closes #2689
2014-08-17 06:10:20 -04:00
Alexei Sholik ebd18d45fa Only add :elixir application for Elixir projects in compile.app task 2014-08-17 01:22:33 +03:00
José Valim ccbfec611a Merge pull request #2684 from alco/mix-compile-erlang
Properly handle compilation errors in Erlang compiler
2014-08-16 17:05:00 -04:00
Alexei Sholik 98a0999c2e Properly handle compilation errors in Erlang compiler 2014-08-16 23:45:44 +03:00
José Valim e504142d3a Merge pull request #2686 from billgathen/patch-1
Docs: Using autocomplete for function discovery in IEx
2014-08-16 15:58:01 -04:00
José Valim c04e13169f Deprecate Collectable.empty and Enum.traverse
The current implementation has three issues:

1. It is slow as we can't reuse the data structure but rather
   build a new one from scratch

2. Due to 1., trees can't be traversed

3. empty/1 is unable to deal with resources
2014-08-16 15:46:58 +02:00
José Valim 96a4bff67b Use the new resource acc on GenEvent.stream/2 2014-08-16 15:41:30 +02:00
José Valim 46de5ea462 Make Stream.resource/3 more like Stream.transform/2
With this change, we allow the accumulator to be passed
to after_fun once iteration is done.

This, however, introduces a deprecation on the old
format used by Stream.resource/3.
2014-08-16 15:35:56 +02:00
Bill Gathen 22257b8342 Using autocomplete for function discovery
I had to go to the mailing list to discover this trick. Knowing it's autocomplete supplying the information makes it obvious in hindsight, but I had it in my head that it would be part of the h() command. It might also be worth adding a mention of autocomplete to the h(IEx) page, as well, but since this is likely first help page a new user would come across, this may be enough.
2014-08-16 08:59:18 -04:00
José Valim a0be9fe272 Improve Logger formatter defaults
* Make colors in the docs consistent with the ones in the code
* Add new lines before and after each message
2014-08-15 18:30:30 +02:00
José Valim 1fd5a2a38e Use monitored handlers instead of linked ones 2014-08-15 18:02:47 +02:00
José Valim 13aec18bd5 Update docs 2014-08-15 13:57:22 +02:00
José Valim a85938d092 Fix the build 2014-08-15 13:54:08 +02:00
José Valim 0d4af57abe Port GenEvent to Elixir for upcoming extensions 2014-08-15 13:16:14 +02:00
José Valim e00db53112 Talk about correctness first, performance later 2014-08-14 22:09:00 +02:00
José Valim b941618a10 Port the :gen_event client API to Elixir
The only difference is that notify is made similar to
GenServer's cast where an unknown atom name does not
trigger an error.
2014-08-13 18:20:10 +02:00
Eric Meadows-Jönsson d895c762a1 Add --force option to 'mix local.rebar' 2014-08-13 17:11:28 +02:00
Eric Meadows-Jönsson 427c4b01c9 Ensure load_tasks return no duplicates 2014-08-13 17:10:40 +02:00
Eric Meadows-Jönsson 9d5a728cb8 Output mix version on 'mix -v' 2014-08-13 17:10:40 +02:00
José Valim 1ed99d4ac7 Improve error message for undefined struct 2014-08-13 09:19:26 +02:00
José Valim ceb17fac7d Use GenServer when possible 2014-08-12 23:22:51 +02:00
José Valim c638f45542 Merge pull request #2674 from koudelka/unquote-doc-fix
Minor unquote/1 doc fix.
2014-08-12 21:40:02 +02:00
Michael Shapiro c09905d537 Minor unquote/1 doc fix. 2014-08-12 12:23:08 -07:00
José Valim 117a428030 Merge pull request #2668 from reset/fix-nocolor
fix --no-color / --color option for `mix test`
2014-08-12 19:32:33 +02:00
Jamie Winsor 2ada7c40d7 fix --no-color / --color option for mix test 2014-08-12 10:32:10 -07:00
José Valim cd162bf3d6 Do not inherit bugs on GenServer calls and casts
Prior to this change, we were inheriting OTP bug where
bad arguments in call were treated as exits.

We also fixed the bug in cast where messages could be
delivered out of order. It is preferrable to wait on
connection.
2014-08-12 17:30:25 +02:00
José Valim e6e71675ac Make a distinction between eol and ; 2014-08-12 16:27:45 +02:00
José Valim c54e0caf6f Merge pull request #2670 from chyndman/cygpath-quotes
Use quotes to avoid space-in-path issues with cygpath
2014-08-12 09:34:41 +02:00
José Valim b64552780e Merge pull request #2671 from lexmag/no-purge
No need in some purges in `Kernel.WarningTest`
2014-08-12 09:34:25 +02:00
Eric Meadows-Jönsson add8c66e82 Merge pull request #2673 from alexrp/master
Clarify match?/2 docs.
2014-08-12 02:02:35 +02:00
Alex Rønne Petersen aecbd2c16a Clarify match?/2 docs. 2014-08-12 01:54:08 +02:00
Aleksei Magusev 32a649c1cd No need for Stream. prefix 2014-08-12 03:31:03 +04:00
Aleksei Magusev a1e657cb75 No need in some purges in Kernel.WarningTest 2014-08-12 03:06:48 +04:00
Chris Hyndman 6bb4c4de03 Use quotes to avoid space-in-path issues with cygpath 2014-08-11 15:13:50 -07:00
José Valim ea5b56d434 Merge pull request #2667 from MSch/fix-new-config-exs
Fix example in config/config.exs that's created for new projects.
2014-08-11 20:51:03 +02:00
Martin Schürrer a4082748cf Fix example in config/config.exs that's created for new projects. 2014-08-11 20:43:36 +02:00
José Valim 4d763846c3 Ensure code escape \ works in IEx 2014-08-11 10:05:24 +02:00
José Valim d56cc88f5f Start v0.15.2-dev 2014-08-11 10:05:24 +02:00
José Valim 5e27b7b72b Merge pull request #2666 from hamiltop/stream_cycle_performance
Add special case for lists in Stream.cycle
2014-08-11 09:53:38 +02:00
Peter Hamilton 260d6d16ab Add special case for lists in Stream.cycle 2014-08-11 00:38:53 -07:00
José Valim 1a82f126a4 Merge pull request #2664 from hamiltop/add_timer_and_interval_streams
Add Stream.interval/1 and Stream.timer/1
2014-08-11 08:16:51 +02:00
Peter Hamilton 3cf3eee2ea Clean up interval/1 and timer/1 docs 2014-08-10 16:03:43 -07:00
Peter Hamilton 9e8a41b2d3 Add Stream.interval/1 and Stream.timer/1 2014-08-10 11:25:53 -07:00
José Valim a54d9212b8 Release v0.15.1 2014-08-10 19:06:49 +02:00
José Valim ee33608f75 Update CHANGELOG 2014-08-10 19:05:45 +02:00
José Valim 6e91275782 Merge pull request #2663 from lexmag/fix-pipe_op
Bind value to proper name in `elixir_tokenizer`
2014-08-10 17:56:59 +02:00
Aleksei Magusev c6f27541c1 Bind value to proper name in elixir_tokenizer 2014-08-10 19:41:13 +04:00
José Valim a2662a4b51 Ensure elixir and iex works on MinGW 2014-08-10 15:11:18 +02:00
José Valim 1d29262949 Check for terminal on both stdout and stderr 2014-08-10 14:40:46 +02:00
José Valim 1d655fa8f2 Check for terminal support from Elixir command line 2014-08-10 13:57:41 +02:00
José Valim 4bec785f4c Also support :sync mode in GenEvent, make :ack one the default 2014-08-10 12:00:13 +02:00
José Valim e3273f731f Fix build 2014-08-09 21:50:24 +02:00
José Valim a8564a7045 Deprecate functions as collectable 2014-08-09 21:32:32 +02:00
José Valim e2bac56556 Reply to GenEvent manager from stream right away
This way the GenEvent can start processing the other
handlers, reducing the processing time for each event
from sum(handlers) to max(handlers).
2014-08-09 21:12:51 +02:00
José Valim a38e6b7a0d Ensure aliases are invoked for recursive tasks
Closes #2650
2014-08-09 13:59:32 +02:00
José Valim 72199ca71d Fix build 2014-08-09 13:47:48 +02:00
José Valim 9420945f59 Leave it to the application to start Logger after compilation
Closes #2649
2014-08-09 13:38:27 +02:00
Eric Meadows-Jönsson 4ca1736f86 Accept more forms of git versions
Fixes the parsing of “git version 1.9.3 (Apple Git-50)”.
Closes #2653.
2014-08-09 11:16:25 +02:00
José Valim fd85ca443e Merge pull request #2652 from chyndman/elixir-bat-iex-startcli
Suppress "-s elixir start_cli" for IEx
2014-08-09 10:36:32 +02:00
Chris Hyndman 187e86749e Suppress "-s elixir start_cli" for IEx 2014-08-08 18:13:42 -07:00
José Valim e04a7aef6a Merge pull request #2651 from fishcakez/translator_format
Improve translator formatting
2014-08-08 22:19:23 +02:00
José Valim 0b1c8c92c0 Set -noshell on bin/iex 2014-08-08 22:11:06 +02:00
Chris Hyndman bf270d144f Prepend -noshell and friends always within Cygwin
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-08 22:11:06 +02:00
James Fish 2c009d1ec3 Improve translator formatting
* Improve application stop reasons
* Include stacktrace in crash reports
2014-08-08 21:08:36 +01:00
José Valim 08555f545c Merge pull request #2648 from sunaku/makefile-message-clarity
Makefile: shorten tip about Unicode embedding time
2014-08-08 20:41:26 +02:00
Suraj N. Kurapati 65d3af694b Makefile: shorten tip about Unicode embedding time
The estimate about this step taking "up to a minute" really depends on
the machine we're compiling on.  For example, on an old or underpowered
machine, it may take _longer_ than a minute.  So this patch changes the
wording accordingly and puts the reason for the delay at the beginning.
2014-08-08 09:07:26 -07:00
José Valim 8aa43eaedd Merge pull request #2647 from jwarwick/task_docs
Fixed doc typos in Task and URI
2014-08-08 16:30:12 +02:00
John Warwick 334c732691 Fixed doc typos in Task and URI 2014-08-08 10:23:36 -04:00
José Valim 24587110ad Merge pull request #2646 from jwarwick/logger_type
Fixed Logger.configure_backend spec
2014-08-08 16:18:46 +02:00
John Warwick b98421fe51 Fixed Logger.configure_backend spec 2014-08-08 10:11:39 -04:00
José Valim 752e3f8bc3 Update CHANGELOG 2014-08-08 13:31:40 +02:00
José Valim 6c8e70b1f8 Load handlers inside supervisor as they are now dynamic 2014-08-08 13:21:55 +02:00
José Valim c6fbfd2a67 Copy and check elixir version in archives
Closes #2396
2014-08-08 12:53:50 +02:00
José Valim 56ea6e52e4 Update configure_backend docs 2014-08-08 11:39:10 +02:00
José Valim 4726e0af91 Persist calls to add_backend and remove_backend 2014-08-08 11:38:18 +02:00
José Valim ee82679908 Merge pull request #2645 from tomkadwill/doc_false_handle_info
Added @doc false to handle_info function
2014-08-08 11:25:56 +02:00
Tom Kadwill aedd5a938f Added @doc false to handle_info function 2014-08-08 10:21:26 +01:00
José Valim 2d9c3fb6f0 Print warnings when used ? with characters with escape codes
Closes #2640
2014-08-08 11:13:13 +02:00
José Valim 6f07f4af22 Create the config directory before writing to config file 2014-08-08 10:23:54 +02:00
José Valim 656bd640e6 Load config/config.exs into escript
Closes #2643
2014-08-08 10:13:17 +02:00
José Valim bc2cb15a96 Add Logger.flush/0 2014-08-07 20:14:04 +02:00
José Valim dbf205b311 Deprecate Module.function/3 2014-08-07 20:00:57 +02:00
José Valim 0e0bb1c2a7 Do not raise conflicts on imports used from quotes, closes #2589 2014-08-07 18:09:19 +02:00
José Valim 736406a9b3 Do not allow invalid quoted expression to leak
Closes #2499
2014-08-07 17:42:05 +02:00
José Valim 886917753e Ensure we can escape bitstrings, add Macro.validate/1 2014-08-07 17:42:05 +02:00
José Valim f29889da99 Make environment explicit on escript build 2014-08-07 17:42:05 +02:00
José Valim aee33eac6d Merge pull request #2638 from jwarwick/logger_docs
Fixing typos in Logger module docs
2014-08-07 16:16:30 +02:00
John Warwick d8428ba1e6 Fixing typos in Logger module docs 2014-08-07 10:11:36 -04:00
José Valim 1f725b0d2c Merge pull request #2637 from fishcakez/improve_sasl_reports
Improve Logger's SASL report translation
2014-08-06 22:37:22 +02:00
James Fish 38f895cc13 Improve Logger's SASL report translation
* Pattern match keyword lists for faster parsing
* Support :supervisor_bridge reports
* Support missing :initial_call in :crash_report
* Improve formatting of registered name in :crash_report output
* More tests
2014-08-06 21:21:36 +01:00
José Valim 3fc07eff8e Merge pull request #2636 from voltone/uri-recomposition
Improved RFC compliance for URI to_string
2014-08-06 21:20:48 +02:00
Bram Verburg aa244c30fc Improved RFC compliance for URI to_string 2014-08-06 21:55:48 +03:00
José Valim f8647dae3e Ensure backends receive the id when added, closes #2635 2014-08-06 19:56:22 +02:00
José Valim 39fa2878ce Fix list rendering on ANSI docs 2014-08-06 19:42:26 +02:00
José Valim 5b5b95eb11 Update CHANGELOG 2014-08-06 13:27:54 +02:00
José Valim 67caa3ebb3 Fix padding in Logger 2014-08-06 13:20:01 +02:00
José Valim d09cd173bf Merge pull request #2634 from fishcakez/sasl_reports
Add SASL log forwarding option to Logger
2014-08-06 12:05:59 +02:00
José Valim 79fb40ab6a Correctly follow code specs when loading binaries
If we are compiling the module, we will load it with the
output of its beam file. If the module exists only in
memory, :code.which/1 will return `:in_memory`.

Closes #2533
2014-08-06 12:05:55 +02:00
José Valim ae2cb6bc43 Add the Windows wiki link to the README 2014-08-06 12:05:55 +02:00
James Fish 413b815ecc Add tests for SASL report logging 2014-08-06 09:47:17 +01:00
James Fish df89b5022e Add SASL log forwarding option to Logger 2014-08-05 21:22:50 +01:00
José Valim 90729655ee Merge pull request #2633 from lexmag/not-match
Use `not` instead of `!` in `IO.ANSI.enabled?/0`
2014-08-05 18:12:12 +02:00
Aleksei Magusev 171e9b3659 Use not instead of ! in IO.ANSI.enabled?/0 2014-08-05 19:29:39 +04:00
José Valim 3e256fcc1e Merge pull request #2631 from chyndman/contributing-windows
Remove Windows-related CONTRIBUTING.md section (now in Wiki)
2014-08-05 09:18:27 +02:00
Chris Hyndman 29ebbeda93 Remove Windows-related CONTRIBUTING.md section (now in Wiki) 2014-08-04 15:14:02 -07:00
José Valim 214d056911 Support on log messages 2014-08-04 20:38:13 +02:00
pragdave 9f065a77ca align the messages between log messages with different length level text. Added an extra format $levelpad, which is a space if the level is "warn" or "info"
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-04 20:32:43 +02:00
José Valim e70ac9768e Fix iex test 2014-08-04 19:41:37 +02:00
José Valim ec6c98ef47 Merge Logger.metadata 2014-08-04 19:03:24 +02:00
José Valim 7d0f92e12a Fix docs and specs introspection in iex 2014-08-04 19:03:24 +02:00
José Valim 8bf6193ccc Merge pull request #2630 from scrogson/patch-2
Doc correction for GenEvent
2014-08-04 18:51:15 +02:00
Sonny Scroggin 50f5dc76fb Doc correction for GenEvent 2014-08-04 11:45:22 -05:00
José Valim b9000999c9 Use the Logger PID to avoid race conditions on shutdown, closes #2628 2014-08-04 15:21:06 +02:00
José Valim 52be06c314 Do not expand args for unknown functions, closes #2387 2014-08-04 13:46:49 +02:00
José Valim 3da560f10c Update Mix.Dep.Lock documentation 2014-08-04 13:18:58 +02:00
José Valim f0f25300a0 Load mix deps only when there is a need to use them
This helps speed up peformance to many tasks. Tasks like
clean, help and compile --list must now explicitly call
loadpaths with checks disabled so they can still show
data coming from dependencies but without causing crashes.
2014-08-04 13:04:56 +02:00
José Valim c8a3a619af Update CHANGELOG 2014-08-04 10:45:59 +02:00
José Valim 25f64051ba Remove documentation from defimpls 2014-08-04 10:45:00 +02:00
José Valim 5efa6bb1c4 Since IO.ANSI.enabled? is a fast operation, no need to prefetch it on IEx 2014-08-04 10:43:02 +02:00
José Valim cb48f90f28 Merge pull request #2627 from smpallen99/inspect_base
Add integer base support to inspect
2014-08-04 10:42:34 +02:00
Stephen M. Pallen 3ba93b1186 Add integer base support to inspect
Option :base for inspect/2 supports :binary, :octal, :decimal, :hex.
Includes tests and documentation.
2014-08-03 20:17:58 -04:00
José Valim 7babcd0ed2 Merge pull request #2625 from niku/doc-about-.iex.exs
Improve IEx docs about .iex.exs
2014-08-03 20:04:12 +02:00
José Valim b6c1f2b2e1 Merge pull request #2624 from lexmag/pathtype
Reuse pathtype detection in `Path` module
2014-08-03 20:03:25 +02:00
Johnny Winn 5f853f8e71 Implement flag to clean unused dependencies
This addresses issues #2569
https://github.com/elixir-lang/elixir/issues/2569
2014-08-03 13:42:51 -04:00
Aleksei Magusev 6cccf3ac22 Reuse pathtype detection in Path module 2014-08-03 21:20:11 +04:00
niku 5b9b186d4b Improve IEx docs about .iex.exs 2014-08-03 23:02:03 +09:00
José Valim 57898bee03 Do not normalize paths on join, closes #2500 2014-08-03 14:59:38 +02:00
José Valim 6a6ba9f50d Remove deprecated code 2014-08-02 22:35:57 +02:00
José Valim 1e3135514f Start v0.15.1-dev 2014-08-02 21:57:43 +02:00
José Valim 2f2a52a648 Release v0.15.0 2014-08-02 21:49:21 +02:00
José Valim 89fd8f305f Print a nice prelude if we fail to load a project, closes #2430 2014-08-02 15:19:53 +02:00
José Valim 353c043d8c Use Mix.Shell.printable_app_name 2014-08-02 14:50:55 +02:00
José Valim 54a40efeec Push the given app name as default on Mix.Project 2014-08-02 14:29:12 +02:00
José Valim f781795032 Bump hex requirement 2014-08-02 14:04:07 +02:00
José Valim 54ffb5768d Add a test case for printing samples on IO shell 2014-08-02 12:13:36 +02:00
José Valim 8fc068821f Print app name on Mix.shell.cmd/1/2 2014-08-02 12:08:42 +02:00
Eric Meadows-Jönsson 1f91fd351e Merge pull request #2619 from knewter/feature/2616_print_app_name_before_compiling_makefile_dependencies
Print app name before compiling Makefile deps
2014-08-02 03:00:03 +02:00
Josh Adams ab4399323f Print app name before compiling Makefile deps
This should close out #2616.
2014-08-01 19:40:11 -05:00
José Valim 3b34a57bd2 Avoid race conditions in the struct detection 2014-08-01 23:58:02 +02:00
José Valim 62a469dc23 Improve mix new error messages 2014-08-01 21:36:28 +02:00
José Valim 8b0c4127ef Merge pull request #2618 from patrickgombert/mix-new
Validate app and module on mix new
2014-08-01 21:22:56 +02:00
Patrick Gombert dc4b7c89ea Validate app and module on mix new 2014-08-01 14:15:15 -05:00
José Valim b5febd991c Allow elixirc_paths to also be given through the command line
Once given, the command line overrides the project
configuration in `mix compile.elixir`.

Closes #2557
2014-08-01 19:52:32 +02:00
José Valim f1323ade7c Accept upcoming hex formats 2014-08-01 18:28:56 +02:00
José Valim 89561c321d Ensure defoverridable is not order dependant, closes #2615 2014-08-01 16:49:57 +02:00
José Valim d1b8657933 Do not load mix.exs for mix deps coming from hex
This is an optimization that allows us to shave off some
seconds from booting applications.
2014-08-01 14:53:41 +02:00
José Valim 89d9cc89ca Ensure IO.ANSI.Docs can correctly fill missing columns 2014-08-01 14:10:42 +02:00
José Valim 7b0cbb1632 Speed up and calculate effective length after rendering 2014-08-01 13:34:11 +02:00
pragdave 3dc0e87c33 Add basic support for markdown tables to built-in formatter
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-01 13:14:12 +02:00
José Valim a19613cf19 Quote bash comparison arguments
/cc @chyndman
2014-08-01 12:50:47 +02:00
José Valim a8e4fcdb6c Reduce limit as we traverse collections on inspect 2014-08-01 12:50:36 +02:00
José Valim a70ca83ba2 Add logger to the list of elixir apps for escript 2014-08-01 11:15:27 +02:00
José Valim 11c857a9a2 Merge pull request #2613 from chyndman/stdlib-test-win-cmd
Use Command Prompt for stdlib test on Windows
2014-08-01 10:12:05 +02:00
Chris Hyndman 84b1f6546b Use Command Prompt for stdlib test on Windows 2014-07-31 15:06:11 -07:00
José Valim c8c1757fdd Merge pull request #2611 from lexmag/tilde-in-path
Handle tilde in `Path.expand/1,2` properly
2014-07-31 19:05:34 +02:00
José Valim 9a162b5076 Inspect.Algebra.surround_many/6 now expects Inspect.Opts 2014-07-31 18:33:34 +02:00
Aleksei Magusev 92d2f25048 Handle tilde in Path.expand/1,2 properly 2014-07-31 20:09:58 +04:00
José Valim a2ddb6abbb Add Agent MFAs 2014-07-31 17:55:48 +02:00
José Valim ac629c875b Move children decision down the tree so we don't ignore umbrella children 2014-07-31 16:49:59 +02:00
José Valim ab8ba93d1d Only print req mismatch for dependencies on compile 2014-07-31 16:06:03 +02:00
José Valim 9d62e674d7 Fix test suite 2014-07-31 14:01:18 +02:00
José Valim 58ca3401e4 Deprecate IO.ANSI.terminal? and tidy up colors 2014-07-31 13:54:08 +02:00
José Valim a5926616a6 Ensure log configuration triggers a restart 2014-07-31 12:59:34 +02:00
José Valim 59910ae4bf Add flush to Logger.add_backend/2 2014-07-31 12:43:11 +02:00
José Valim 69b47483c1 Merge pull request #2609 from fishcakez/logger_ansi_console
Add ANSI support to console logger
2014-07-31 12:43:06 +02:00
José Valim 284e75d031 Remove warnings from the test suite 2014-07-31 10:46:29 +02:00
José Valim c1d63e4639 Optimize IO.ANSI.format/2 by avoiding remote calls 2014-07-31 01:50:10 +02:00
James Fish 212b204af8 Add ANSI support to console logger 2014-07-31 00:06:31 +01:00
José Valim 90f83e1b3d Improve docs for Logger 2014-07-30 21:22:40 +02:00
José Valim 562af52d07 Fix logger docs 2014-07-30 20:16:53 +02:00
José Valim ed79d60cef Update CHANGELOG 2014-07-30 20:11:29 +02:00
José Valim 42d378142b No more binding with a list 2014-07-30 20:00:12 +02:00
José Valim aeefd59b15 Add Logger 2014-07-30 19:46:26 +02:00
José Valim 6db5091378 Provide :on option for Regex.split/3 2014-07-30 15:45:48 +02:00
José Valim 4e7ba9d598 Deprecate IO.ANSI.escape/2 and IO.ANSI.escape_fragment/2 2014-07-30 13:14:53 +02:00
José Valim 60b66ac669 Merge pull request #2562 from thmzlt/ansi
Implement IO.ANSI format/2 and format_fragment/2
2014-07-30 13:00:20 +02:00
José Valim 1fda82d9c2 Merge pull request #2606 from edgurgel/patch-1
Add OTP 17.1 to run on Travis CI
2014-07-30 12:50:12 +02:00
José Valim 55dbd617a6 Ensure no lock for rebar dependencies, closes #2561 2014-07-30 12:48:16 +02:00
Eduardo Gurgel 841a4e0195 Add OTP 17.1 to run on Travis CI 2014-07-30 22:47:01 +12:00
José Valim 5d60728d4b Always touch all marked dependencies from deps fetcher 2014-07-30 12:42:43 +02:00
José Valim 9717a63604 Do not consider subpatterns on Regex.split/3 2014-07-30 11:13:18 +02:00
José Valim fae20670ed Update CHANGELOG 2014-07-29 20:40:54 +02:00
José Valim 6874eb7ce8 Deprecate octal and upcase char syntaxes 2014-07-29 20:33:42 +02:00
Joe-noh 404e95b7ac Fix indent
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-29 18:02:15 +02:00
José Valim 30a5914a92 Merge pull request #2600 from niku/remove-duplicate-test
Remove duplicate test
2014-07-29 16:53:41 +02:00
José Valim f97a36ff98 Merge pull request #2599 from rclements/patch-1
Update gen_server.ex
2014-07-29 16:53:27 +02:00
niku e68f617e2a Remove duplicate test 2014-07-29 23:51:24 +09:00
Robby Clements 3056e95ded Update gen_server.ex 2014-07-29 09:49:47 -05:00
José Valim 9ba77e1ce6 List crypto, compiler and syntax tools as dependencies, closes #2593 2014-07-29 11:42:12 +02:00
José Valim 84010d4467 Ensure private functions are not exported, closes #2587 2014-07-29 11:22:19 +02:00
José Valim a91aa70103 Do not expose protocol convention on assert_impl!
Closes #2594
2014-07-29 10:59:25 +02:00
José Valim 301c7fd122 Update CHANGELOG 2014-07-29 10:45:12 +02:00
José Valim 8809ab2f5e Do not leak Stream representation 2014-07-29 10:41:02 +02:00
José Valim 71e441dcc2 Remove IEx warnings 2014-07-29 10:40:40 +02:00
José Valim f11197fd04 Remove escript from elixir app 2014-07-29 10:38:20 +02:00
José Valim 7ac771da67 Do two separate :io.setopts calls 2014-07-29 10:38:20 +02:00
José Valim a47751f4de Merge pull request #2592 from tomkadwill/doc-false
Added @doc false to terminate and code_change
2014-07-28 22:09:33 +02:00
José Valim c91217860c Merge pull request #2573 from chyndman/iex-werl-option
Windows IEx enhancements
2014-07-28 22:09:00 +02:00
Chris Hyndman f69e9df983 Add new --werl option 2014-07-28 07:49:02 -10:00
Chris Hyndman 2ee1d5daef Suppress "dumb terminal" warning 2014-07-28 07:48:21 -10:00
Chris Hyndman d3dba63659 Use start to spawn werl without lingering cmd window 2014-07-28 07:42:28 -10:00
Chris Hyndman c016d74379 Use setlocal/endlocal to prevent polluting user's environment 2014-07-28 07:42:27 -10:00
Tom Kadwill a6d887ee09 Added @doc false to terminate and code_change 2014-07-28 12:49:33 +01:00
José Valim aa017a4dcb Merge pull request #2585 from herenowcoder/enum-docs
use os.timestamp for setting random seed in doc examples
2014-07-28 09:34:59 +02:00
José Valim b7c7d7e72c Merge pull request #2586 from jquadrin/patch-1
small IEx evaluator refactoring
2014-07-28 09:34:23 +02:00
José Valim d164a9b93f Merge pull request #2590 from MSch/fix-mix-escript
Fix mix escript
2014-07-28 09:30:49 +02:00
Martin Schürrer 0105cf538e Update Elixir's mix.exs to fix escript.build 2014-07-27 22:47:21 -05:00
Martin Schürrer 957e389fe7 Update mix's mix.exs to fix escript.build 2014-07-27 22:45:40 -05:00
Joseph Quadrino 88d4d38afa small evaluator refactoring 2014-07-27 16:52:16 -04:00
Wojciech Kaczmarek 8b4b985894 use os.timestamp for setting random seed in doc examples 2014-07-27 17:59:22 +02:00
José Valim 986eb8bdb1 Merge pull request #2556 from patrickgombert/master
Allow --app flag to be passed on mix new
2014-07-27 08:33:58 -05:00
José Valim 052eac333c Merge pull request #2580 from Joe-noh/lowercase-http-s-proxy
[MIX] allow lowercase http(s)_proxy env var
2014-07-27 08:31:43 -05:00
José Valim acae645db1 Merge pull request #2581 from mururu/parse-space
Parse unary/binary operators regardless of number of spaces
2014-07-27 08:27:29 -05:00
José Valim 7cb5f6ff3c Merge pull request #2582 from tomkadwill/added-doc-false
Added @doc false to handle_call functions
2014-07-27 07:58:05 -05:00
Tom Kadwill 0fe940a621 Added @doc false to handle call 2014-07-27 11:52:15 +01:00
Yuki Ito 81ed03ffb2 Parse unary/binary operators regardless of number of spaces
closes #2534.
2014-07-26 20:03:38 +09:00
Joe-noh 3947f7455a allow lowercase http(s)_proxy env var 2014-07-26 17:03:25 +09:00
José Valim 406a2e6755 Merge pull request #2576 from Gazler/docs/dict
Docs: change Dict.merge description to read more naturally
2014-07-25 23:30:30 -05:00
Gary Rennie 0b3fcb254a Docs: change Dict.merge description to read more naturally
Previously there was an additional "dict" - this is as a result of it
previously saying "dict `a`"
2014-07-25 23:10:50 +01:00
Eric Meadows-Jönsson 054843505a Merge pull request #2575 from Gazler/docs/dict
Docs: ensure docs for Dict use full variable names
2014-07-25 15:42:48 -05:00
Gary Rennie 92016cd80e Docs: ensure docs for Dict use full variable names
The following variable switches have been made:

 * `d` has been changed to `dict`
 * `a` has been changed to `dict1`
 * `b` has been changed to `dict2`
 * `d1` has been changed to `dict1`
 * `d2` has been changed to `dict2`

 This fixes #2574
2014-07-25 21:07:53 +01:00
José Valim 0c06e72f0c Merge pull request #2570 from chyndman/iex-expand-fun-fallback
Fallback to no tab expansion if not supported
2014-07-23 22:12:38 -07:00
Chris Hyndman a722a4b33c Fallback to no tab expansion if not supported 2014-07-23 08:53:27 -10:00
José Valim 4afb8a516c Remove bug where access/update was incorrectly filling values with maps 2014-07-22 12:44:05 -07:00
José Valim e9fc50bc6d Exit on GenEvent call, move stream to its own module 2014-07-22 09:49:11 -07:00
José Valim 0a5a980ab7 Merge pull request #2566 from jwarwick/mix_clean_typo
Fixed typos in Mix.Tasks.Clean docs
2014-07-21 16:42:52 -05:00
John Warwick 30505b7758 Fixed typos in Mix.Tasks.Clean docs 2014-07-21 15:35:05 -04:00
Thomaz Leite 9f867f2ec8 Fix IO.ANSI doctests 2014-07-21 12:22:13 -03:00
Thomaz Leite 5e2020804a Reimplement IO.ANSI do_format
This avoids copying partial lists, and handles nested improper lists.
2014-07-21 12:07:39 -03:00
Thomaz Leite b5cea26e57 Add test case for nested improper lists 2014-07-21 11:33:37 -03:00
Thomaz Leite 03e6d3a5a2 Add docs to format/2 and format_fragment/2 2014-07-20 13:36:34 -03:00
Thomaz Leite 89513f9d15 Put IO.ANSI.reset inside a list before concat'ing 2014-07-20 13:15:06 -03:00
Thomaz Leite f20ef46e90 Implement IO.ANSI format/2 and format_fragment/2 2014-07-20 12:11:54 -03:00
José Valim d7ebc46a2d Improve config.exs docs 2014-07-20 11:07:16 +02:00
José Valim bd6d4bcd6f Merge pull request #2563 from alco/typespec-typo
Fix a typo in typespec doc
2014-07-20 11:05:43 +02:00
Alexei Sholik 4f8862cedd Fix a typo in typespec doc 2014-07-20 11:57:22 +03:00
Eric Meadows-Jönsson 1afc5ec71b Trigger rebuild in Makefile for io/ansi/docs.ex
Closes #2558.
2014-07-19 23:26:29 +02:00
José Valim 925ff6050a Merge pull request #2554 from chyndman/mix-ps1-revised
mix.ps1 Revision
2014-07-19 20:52:29 +02:00
Thomaz Leite 6ce97090f3 Change IO.ANSI tests to assume chardata is returned 2014-07-19 14:36:32 -03:00
Thomaz Leite 6538f33825 Bring back original tests for IO.ANSI
To avoid conflict and preserve compatibilty, the new tests use the
functions `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`.
2014-07-19 13:26:53 -03:00
José Valim a1e4a2741f Avoid unecessary wrap inside do keyword 2014-07-19 18:13:38 +02:00
Thomaz Leite fe57a821d8 Rewrite IO.ANSI tests to match new behavior 2014-07-19 12:44:59 -03:00
José Valim c42fe88dcc Provide Inspect.Algebra.format/2 that returns iodata 2014-07-19 13:32:45 +02:00
José Valim 28d7fc35a6 Merge pull request #2559 from jwarwick/keyword_docs
Fixing doc typos in Keyword
2014-07-19 10:58:16 +02:00
John Warwick 9fa279897b Fixing doc typos in Keyword 2014-07-18 16:26:46 -04:00
Patrick Gombert 6465a3a4af Allow --app flag to be passed on mix new
Allows path to not follow app name conventions
2014-07-17 20:48:04 -05:00
Chris Hyndman 1582df8d70 Fixed to use flattened array $newArgs 2014-07-17 16:19:26 -07:00
Chris Hyndman 8d9aec5a55 Revise mix.ps1 to use FileInfo object to represent path, modify $args directly 2014-07-17 15:09:38 -07:00
José Valim 7f4df95922 Merge pull request #2553 from chyndman/mix-test-archive-win-refute
Skip additional refutation due to code server file handle on Windows
2014-07-17 21:02:06 +02:00
Chris Hyndman 98a5affa4a Skip additional refutation due to code server file handle on Windows 2014-07-17 11:33:07 -07:00
José Valim b720559d56 Merge pull request #2552 from alco/application-doc-typo
Fix a typo on Application.format_error doc
2014-07-17 16:22:22 +02:00
Alexei Sholik cd5cc91cd4 Fix a typo on Application.format_error doc 2014-07-17 17:12:51 +03:00
José Valim 93d92d1f36 Optimize rstrip for ascii chars 2014-07-16 23:59:09 +02:00
José Valim ce3674dd07 Add OptionParser.to_argv/1 2014-07-16 22:22:02 +02:00
José Valim e4cfb62a74 Merge pull request #2549 from jwarwick/exception_typo
Fixed typos in Exception docs
2014-07-16 22:08:03 +02:00
John Warwick bb51701614 Fixed typos in Exception docs 2014-07-16 16:05:48 -04:00
José Valim 6ffb248f03 No longer allow empty access as in [][] 2014-07-16 17:54:26 +02:00
José Valim 50af8058ef Use john and meg in code samples 2014-07-16 17:41:28 +02:00
José Valim b1d9e147b3 Update CHANGELOG 2014-07-16 15:06:35 +02:00
José Valim 7ea91d2d68 Clean up String.slice/2/3 impls 2014-07-16 14:03:52 +02:00
Alexei Sholik c7640d3cf4 Do not traverse the string twice in String.slice(<str>, <range>)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 13:05:54 +02:00
José Valim 399240295b Remove invalid return values from GenEvent.handle_info/2 docs 2014-07-16 12:58:03 +02:00
José Valim b965d8eb5d Also allow structs atoms on Map.from_struct/1 2014-07-16 12:57:11 +02:00
Josh Nussbaum 380bf1e279 Added Map.from_struct/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 12:50:14 +02:00
José Valim 6272b60d36 Ensure all elixir dependencies are started before hand, closes #2542 2014-07-16 12:48:46 +02:00
José Valim 0c7699f21c Merge pull request #2541 from chyndman/mix-bat-exec-directly
Call Mix.start, Mix.CLI.main directly in bin\mix.bat
2014-07-16 12:07:04 +02:00
José Valim 1086a8455f Merge pull request #2544 from eproxus/patch-1
Remove non-existing from argument from handle_cast
2014-07-16 11:44:47 +02:00
Adam Lindberg d8e7578e41 Remove non-existing from argument from handle_cast
`handle_cast` does not support a `from` argument and this example will crash if it has one.
2014-07-16 10:54:02 +02:00
Chris Hyndman 2d0fbcc23c Call Mix.start, Mix.CLI.main directly in bin\mix.bat 2014-07-15 13:42:58 -07:00
José Valim 21d5d43ac5 Merge pull request #2539 from hamiltop/update_genevent_stream_doc
Update doc for GenEvent.stream/2
2014-07-15 20:56:59 +02:00
Peter Hamilton 027f90380b Update doc for GenEvent.stream/2
- GenEvent streams are not two way, so remove language suggesting that
you can notify via the streams.
- Clarify that consumption doesn't start until we enumerate.
2014-07-15 11:54:04 -07:00
José Valim 1a68f63525 Improve description of operations that takes linear time 2014-07-15 20:14:44 +02:00
José Valim 798c53a4cb Add linearity notes to Enum.fetch and friends 2014-07-15 16:33:25 +02:00
José Valim e3a89050f2 Merge pull request #2536 from alco/string-slice-speedup
Optimize String.slice by using binary_part
2014-07-15 10:56:42 +02:00
Alexei Sholik f176cb55c1 Optimize String.slice by using binary_part 2014-07-15 02:40:03 +03:00
José Valim 97f2e8744b Insert new line after IEx clear message 2014-07-14 19:48:24 +02:00
José Valim c513459c10 Merge pull request #2531 from jwarwick/system_cmd_type
Updating System.cmd return types
2014-07-14 16:11:04 +02:00
John Warwick fd6f1d47be Updating System.cmd return types 2014-07-14 10:06:23 -04:00
José Valim 6b8b5bfd01 Revert "Merge pull request #2530 from qingliangcn/master"
This reverts commit 42875c0b72, reversing
changes made to be320880b2.
2014-07-14 14:02:13 +02:00
Eric Meadows-Jönsson e6e96165aa Ensure type_to_signature works for all AST variables 2014-07-14 12:05:40 +02:00
José Valim 42875c0b72 Merge pull request #2530 from qingliangcn/master
use pure erlang function to judge the erlang version, no shell.
2014-07-14 11:29:57 +02:00
qingliangcn b35e2cf82a use pure erlang function to judge the erlang version, no shell. 2014-07-14 14:49:06 +08:00
José Valim be320880b2 Ensure functions in Kernel.SpecialForms and IEx.Helpers are also auto-completed
Closes #2527
2014-07-13 09:50:55 +02:00
José Valim fe1712d642 Merge pull request #2528 from alco/base-warnings
Update bitstring syntax in base.ex
2014-07-13 09:22:59 +02:00
Alexei Sholik 63d815912e Update bitstring syntax in base.ex 2014-07-13 06:03:22 +03:00
José Valim cd4dc75c93 Merge pull request #2526 from alco/at-exit-doc
Clarify the consequences of using System.at_exit
2014-07-12 16:31:42 +02:00
José Valim 058b157b36 Remove deprecated features and deprecate soft ones 2014-07-12 16:23:17 +02:00
Alexei Sholik 19c9941e7f Clarify the consequences of using System.at_exit 2014-07-12 17:11:57 +03:00
Eric Meadows-Jönsson 7895ca0821 Clarify release instructions 2014-07-12 15:21:29 +02:00
Eric Meadows-Jönsson 2b20bb424f Start v0.14.4-dev 2014-07-12 15:06:10 +02:00
Eric Meadows-Jönsson 82535feb90 Release v0.14.3 2014-07-12 14:49:29 +02:00
José Valim 81f007bdd7 Update CHANGELOG 2014-07-12 14:11:14 +02:00
José Valim 0b35996d5e Optimize and improve docs for Float operations 2014-07-12 14:05:18 +02:00
José Valim fc5cf506ae Merge pull request #2520 from nurugger07/float_modification_merges
Add precision to Float.ceil/1 & Float.floor/1
2014-07-12 12:38:35 +02:00
José Valim c4582e0041 Clarify attributes and exit signals in the language 2014-07-11 22:19:14 +02:00
Eric Meadows-Jönsson c80d215674 Fix stacktrace in Module.get_attribute example
Closes #2522.
2014-07-11 21:56:48 +02:00
José Valim 3ee9d5d0df Merge pull request #2521 from ericmj/record-typespecs
Add typespec syntax for records
2014-07-11 21:08:16 +02:00
Eric Meadows-Jönsson d86ba0e1c7 Add typespec syntax for records 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson c295ee50de Allow expansion of local macros via Macro.expand/2 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson 54c25a55fe Add compile time optimisation to record fields macro 2014-07-11 20:35:35 +02:00
Johnny Winn 583a4ec5f6 Add precision to Float.ceil/1 & Float.floor/1 2014-07-11 12:34:24 -04:00
José Valim 69b78dad51 Update docs and CHANGELOG 2014-07-11 17:49:34 +02:00
José Valim 0685e0315a Document and list aliases on help --names 2014-07-11 17:32:41 +02:00
José Valim 5e429001d9 Support aliases in Mix.Task.run/2 2014-07-11 17:32:41 +02:00
José Valim 707b810f1b Merge pull request #2517 from ericmj/record-fields
Add field retrieval of records via `record(record())`
2014-07-11 17:31:21 +02:00
José Valim e51cfc142d Merge pull request #2516 from alco/sigil-docs
Make sigil docs more readable in the source
2014-07-11 17:29:02 +02:00
Eric Meadows-Jönsson b259e793bb Fix mix archive in changelog 2014-07-11 17:12:21 +02:00
Eric Meadows-Jönsson 32738a97d9 Add field retrieval of records via record(record()) 2014-07-11 17:09:16 +02:00
Alexei Sholik 45c6fbaced Make sigil doctests more readable in the source 2014-07-11 17:17:42 +03:00
José Valim bba4f1bd1b Optimize and add specs to Mix.Task module 2014-07-10 21:58:35 +02:00
Eric Meadows-Jönsson ade2596e92 Fix bug where umbrella tasks were not reenabled
Closes #2058.
2014-07-10 21:21:23 +02:00
Eric Meadows-Jönsson 0e804a2b5c Merge pull request #2513 from ericmj/mix-mtimes
Warn and reset mime on future files
2014-07-10 20:44:28 +02:00
Eric Meadows-Jönsson 33ab1679f3 Warn and reset mime on future files 2014-07-10 20:08:06 +02:00
Eric Meadows-Jönsson f2b12633f3 Only read mtime once per source file 2014-07-10 19:26:25 +02:00
José Valim 5a4b2769ee Clarify elixirc_options are overridden via CLI 2014-07-10 18:04:23 +02:00
José Valim 2439bae409 Use Keyword syntax instead of list check 2014-07-10 17:59:56 +02:00
José Valim 6ed7e40d03 Add OptionParser.split/1 2014-07-10 17:58:19 +02:00
José Valim 2fc76fe1b0 Don't keep all in the option parser config 2014-07-10 16:46:29 +02:00
Eric Meadows-Jönsson e541a56130 Update changelog 2014-07-10 15:56:37 +02:00
José Valim 6378750b12 Only accept --no-SWITCH for booleans
The previous behaviour was confusing and a special case.
This enables negated switches only for booleans, otherwise
it is considered as a regular switch (as the combination
of --no-SWITCH for any other type is an error anyway).
2014-07-10 14:05:49 +02:00
José Valim fc25f60718 Do not remove interpolation escape on uppercase sigils, closes #2512 2014-07-10 12:54:16 +02:00
José Valim 3b351e7650 ErlangError -> CompileError 2014-07-10 11:49:56 +02:00
José Valim 999a535603 Manually call check_command before eval, closes #2449 2014-07-10 11:44:03 +02:00
José Valim a726dd812b Improve cannot pipe error 2014-07-10 11:30:00 +02:00
Eric Meadows-Jönsson 58d54afac6 Merge pull request #2511 from ericmj/macro-tostring-sigils
Use sigil syntax in Macro.to_string
2014-07-10 00:32:58 +02:00
Eric Meadows-Jönsson 001d776176 Use sigil syntax in Macro.to_string 2014-07-09 19:34:07 +02:00
José Valim 4bcd25fb4d Require Record before in defrecord samples 2014-07-09 19:22:23 +02:00
José Valim f0fa9e66a6 Add performance note to String.slice/1 2014-07-09 18:47:25 +02:00
José Valim 12f9bf09b0 Mention app: false on no app error 2014-07-09 18:47:25 +02:00
José Valim bf8f5a995f Merge pull request #2508 from chyndman/mix-bat-short-names
Use short filenames to handle spaces in path to bin\mix
2014-07-09 11:06:37 +02:00
Chris Hyndman d3a3feace4 Use short filenames to handle spaces in path to bin\mix 2014-07-08 17:09:23 -07:00
Eric Meadows-Jönsson 30331b2bea Do not allow deps names on mix deps.get
Closes #2507.
2014-07-09 00:24:13 +02:00
José Valim f5bf9d9659 Do not call handle_file_warning directly 2014-07-08 19:03:22 +02:00
José Valim 2865bd8653 Make elixir_errors API more strict and consider :file in meta 2014-07-08 18:54:40 +02:00
José Valim 4513b6965f Emit :file attribute on all nodes instead of a select few 2014-07-08 15:53:32 +02:00
José Valim b47e8c61d7 Use line from :keep when there is a :file meta in definitions 2014-07-08 13:35:17 +02:00
José Valim 40bf079566 Update CHANGELOG 2014-07-08 12:06:43 +02:00
José Valim 2eaf854e79 Slightly improve IO docs 2014-07-08 11:41:25 +02:00
Joe-noh 7c12cf029c implement IO.(bin)read which accept :all
IO.(bin)read(dev, `:all`) reads all contents from device

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-08 11:39:18 +02:00
José Valim 597d90b326 Add comments to code:ensure_loaded/1 snippets 2014-07-08 11:28:24 +02:00
José Valim 5dcad26b79 Ensure we don't loop when printing inspect failures, closes #2503 2014-07-08 11:25:49 +02:00
José Valim f8c2d5cb4f Merge pull request #2502 from alco/escript-embed-elixir
Fix the usage of `embed_elixir` in escript.build
2014-07-08 10:46:31 +02:00
Alexei Sholik b0f432b2f8 Fix the usage of embed_elixir in escript.build 2014-07-08 01:27:43 +03:00
José Valim 651a4bad67 Clarify behaviour of split on Windows 2014-07-07 20:31:27 +02:00
José Valim f52e350adc Ensure deps are not recompiled every second time, closes #2471 2014-07-07 17:38:51 +02:00
José Valim 35e8019447 Improve mix docs 2014-07-07 17:29:02 +02:00
José Valim a6fab4a111 Use a native splitting on String.split/1 instead of relying on regexes
The new implementation is 20x faster for small strings and about
100x times faster for large ones.
2014-07-06 21:20:52 +02:00
José Valim 64d62d2472 Emit warnings for underscored variables appearing twice in a pattern, closes #2480 2014-07-06 20:18:00 +02:00
Eric Meadows-Jönsson 10b322aa48 Rename Mix.Shell.info option :escape => :ansi 2014-07-06 16:28:38 +02:00
Eric Meadows-Jönsson 981955301d Shell info messages can still be escaped 2014-07-06 02:39:16 +02:00
Eric Meadows-Jönsson ac425f04ef Ensure the message is not ANSI escaped
Closes #2496.
2014-07-06 02:20:40 +02:00
José Valim 1dda1563da Ensure docs with no function raise warnings 2014-07-05 22:25:15 +02:00
José Valim 0e71b1e711 Merge pull request #2495 from thmzlt/patch-1
Fix typo in GenEvent moduledoc
2014-07-05 22:11:31 +02:00
thmzlt 841f6f2387 Fix typo in GenEvent moduledoc 2014-07-05 17:08:49 -03:00
José Valim 2eb7fb97de Check if ANSI escape codes are enabled on IEx's clear, closes #2441 2014-07-05 15:16:00 +02:00
José Valim e781983e6b Fix regex compilation and set ucp flags 2014-07-05 15:01:25 +02:00
José Valim 7d17a12783 Add test cases for ExUnit.DocTest failures 2014-07-05 14:53:24 +02:00
jw2013 d888057689 throws error for missing docs in DocTest
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-05 14:49:10 +02:00
José Valim 76d6e05399 Ensure case wrapper in cond gets the proper line 2014-07-05 14:45:07 +02:00
José Valim 1bd0ca6dea Return explicit :ok from more functions 2014-07-05 13:43:52 +02:00
José Valim 8e1f7fddca Always return ok from tracking functions 2014-07-05 13:21:59 +02:00
José Valim abda47903b Merge pull request #2494 from jw2013/typo
fix typo in range moduledoc
2014-07-05 10:39:49 +02:00
jw2013 030e72c9c6 fix range typo 2014-07-04 22:07:08 -07:00
Eric Meadows-Jönsson 88dbad9650 Fix some sentence tenses in changelog 2014-07-05 03:19:47 +02:00
Eric Meadows-Jönsson 07263b8533 Merge pull request #2493 from jwarwick/changelog_typos
Fixed Changelog typos
2014-07-05 03:13:44 +02:00
John Warwick f67741b6dc Fixed Changelog typos 2014-07-04 20:51:13 -04:00
José Valim 4724ff12e2 Fix bug when flat_map is used inside another flat_map with enumerable, closes #2483 2014-07-04 21:51:57 +02:00
José Valim f7c7ff46b4 Store stacktrace before invoking callback function 2014-07-04 19:27:05 +02:00
José Valim c4868f8549 s/file/function in IO docs 2014-07-04 19:26:31 +02:00
José Valim 85951052ca Add a note for binary_part/3 on String.slice/3 2014-07-04 18:37:02 +02:00
José Valim 28bd15b76c Consider multiple @external_resource entries in mix 2014-07-04 18:30:55 +02:00
José Valim 1fdb715967 Add a note about IO.bin* 2014-07-04 18:30:54 +02:00
Eric Meadows-Jönsson 0279547232 Re-add deprecated local tasks 2014-07-04 15:46:23 +02:00
José Valim 7fabf32d9e Do not crash if applications are already started 2014-07-04 12:13:53 +02:00
José Valim 4cbf0eb2ac Merge pull request #2484 from fishcakez/dialyzer_fixes
Fix unmatched returns dialyzer warnings
2014-07-04 11:54:55 +02:00
José Valim 071b0830f8 Ensure line numbers are properly preserved in EEx, closes #2485 2014-07-04 11:54:07 +02:00
James Fish 39cd4a6594 Fix unmatched returns dialyzer warnings 2014-07-04 02:45:16 +01:00
José Valim 0d2332fe5e Update CHANGELOG 2014-07-03 21:03:17 +02:00
José Valim c807fa6ec5 Add Mix.compilers 2014-07-03 21:01:13 +02:00
José Valim 4a10806d9a Pass a list to :error_logger 2014-07-03 20:51:41 +02:00
José Valim 57031f8cb0 Improve typespecs for protocols once more 2014-07-03 20:04:06 +02:00
José Valim b292486ac9 Improve more typespecs 2014-07-03 19:14:06 +02:00
José Valim 595f9bb0b1 Improve poorly specified callbacks 2014-07-03 16:50:37 +02:00
José Valim ebe3397ad9 Do not include unreachable locals 2014-07-03 16:26:44 +02:00
José Valim bc12b76bcc Merge pull request #2481 from fishcakez/remote_call_ast
Fix remote call AST
2014-07-03 14:45:17 +02:00
José Valim d8df85e99c Update plt in .gitignore 2014-07-03 14:15:58 +02:00
José Valim 80b9879d0a Refactor how definitions are analyzed 2014-07-03 14:15:24 +02:00
James Fish d54f9ec931 Fix remote call AST 2014-07-03 11:59:25 +01:00
José Valim d2258acbb3 Ensure yanked locals are added back with all nodes, closes #2188 2014-07-03 11:43:42 +02:00
José Valim 6c3a4bd019 Fix the guard for this clause evaluates to false warning
Since the guard can be hidden inside if/unless, it is better
to have a more generic error, stating we will always get the
same result from an check/guard.
2014-07-03 00:58:02 +02:00
José Valim 7903211546 Merge pull request #2479 from jwarwick/process_typo
Fixed Process doc typos
2014-07-02 22:35:49 +02:00
John Warwick 6b87fcf903 Fixed Process doc typos 2014-07-02 16:33:57 -04:00
José Valim 07d0d1b275 Improve some typespecs 2014-07-02 22:32:16 +02:00
José Valim 95361172a0 Update CHANGELOG 2014-07-02 22:05:41 +02:00
José Valim a0f3086167 Convert existing bitstring examples to new syntax 2014-07-02 21:32:00 +02:00
José Valim 65a59b41e5 New bitstring syntax, closes #1950 2014-07-02 21:19:01 +02:00
José Valim 78dc02fb81 Merge pull request #2477 from fishcakez/dialyze
Fix dialyzer makefile commands
2014-07-02 17:37:17 +02:00
José Valim dc577b8bfa Ensure mix preserves -pa and -pz given to the command line 2014-07-02 17:30:20 +02:00
José Valim ed9862ea91 First deprecate types in struct fields 2014-07-02 17:30:20 +02:00
James Fish f710c61179 Fix dialyzer makefile commands 2014-07-02 15:55:02 +01:00
Eric Meadows-Jönsson d86cf14f0d Add shorthand syntax for nonempty_list() 2014-07-02 15:48:03 +02:00
Eric Meadows-Jönsson bdfd5817dc Fix typespec_to_ast for lists 2014-07-02 02:44:47 +02:00
José Valim 3b2ece4ddc Merge pull request #2474 from chyndman/elixir-bat-short-circuit
Fix for batch file misbehaving with parenthetical arguments
2014-07-02 00:12:56 +02:00
José Valim 3452f364aa Improve typespecs and docs 2014-07-02 00:12:06 +02:00
José Valim dad77a4d4b Mention MIX_ENV on compile.protocols 2014-07-02 00:12:06 +02:00
Eric Meadows-Jönsson c8b925c581 Merge pull request #2475 from alco/version-doc-fix
Fix weird examples in Version docs
2014-07-02 00:02:43 +02:00
Alexei Sholik 24297c87e6 Fix weird examples in Version docs 2014-07-02 00:26:54 +03:00
Chris Hyndman 0c589eff04 Umanbiguous check for parameters 2014-07-01 13:51:25 -07:00
Chris Hyndman 3a3684ca78 Process Erlang commands last 2014-07-01 13:44:23 -07:00
José Valim 6e39366c40 Merge pull request #2473 from jwarwick/changelog_typo
Fixed CHANGELOG typo
2014-07-01 20:45:46 +02:00
John Warwick fd0ccb7741 Fixed CHANGELOG typo 2014-07-01 14:09:09 -04:00
José Valim 81f151245e Merge pull request #2472 from michaelkschmidt/master
Use absolute symbolic links on windows
2014-07-01 19:06:30 +02:00
Michael Schmidt acfdd5b59e Use absolute symbolic links on windows 2014-07-01 11:17:30 -05:00
Eric Meadows-Jönsson 588800c495 Merge pull request #2467 from ericmj/multiclause-defaule
Raise on multiple clauses with default arg
2014-07-01 16:51:12 +02:00
Eric Meadows-Jönsson cfd157ab7d Merge pull request #2466 from ericmj/typespec-struct
Remove default generation of `@type t` for structs
2014-07-01 16:51:01 +02:00
Eric Meadows-Jönsson 377eb6126d Raise on multiple clauses with default arg 2014-07-01 16:41:19 +02:00
Eric Meadows-Jönsson 2cad4c96bd Remove default generation of @type t for structs
`Struct{}` in typespecs will generate default types for all missing
fields.
2014-07-01 16:23:30 +02:00
José Valim 2453501c83 Rename escriptize to escript.build 2014-07-01 15:23:24 +02:00
José Valim dcc3c6290e Deprecate embed_extra_apps in escriptize 2014-07-01 15:17:59 +02:00
José Valim 54240e1b4f Inline functions in Port module 2014-07-01 14:54:56 +02:00
José Valim 6639f91ba2 No longer fill in missing intermediate values with empty maps in Access 2014-07-01 13:46:49 +02:00
José Valim 15bf309760 Improve error message for struct expansion in invalid contexts 2014-07-01 12:41:27 +02:00
José Valim 1de0302ecb Fix failing test and improve zip/unzip docs 2014-07-01 12:39:06 +02:00
José Valim 871cbda3f8 Allow functions in get_in/2 and get_and_update_in/3 (#2470) 2014-07-01 12:25:07 +02:00
José Valim c4ea85d7e5 Bug fixes on windows for buggy 17.1 2014-07-01 10:16:39 +02:00
José Valim a8f3125774 Deprecate lists in find_executable/1 2014-06-30 21:43:26 +02:00
José Valim c564af180b Update CHANGELOG 2014-06-30 21:35:54 +02:00
José Valim 3ebe1ed728 Use binaries so we can use Elixir functions 2014-06-30 21:30:01 +02:00
José Valim 6b030b2978 Use the new System.cmd/3 in stdlib and apps 2014-06-30 21:15:00 +02:00
José Valim 9f2fa0f7af Add System.cmd/3, closes #2251 2014-06-30 20:50:44 +02:00
José Valim da06e35443 Remove wrong usage of exit(0|1) 2014-06-30 20:35:06 +02:00
José Valim da4d047a37 Use iolists in collectable for binaries 2014-06-30 20:33:38 +02:00
José Valim 782314abff Also use exit({:shutdown, 1}) on mix test 2014-06-30 16:59:11 +02:00
José Valim 94d53f052b Use exit({:shutdown, int}) to signal shell status 2014-06-30 16:38:26 +02:00
José Valim 9ceebf96db Move local.install and local.uninstall to archive.* 2014-06-30 12:28:51 +02:00
José Valim 8611bb7322 Run command line and escripts in a new process without trap exit
Closes #1985
2014-06-30 11:48:31 +02:00
José Valim ba2e1c95ef Remove duplicated specs and old PR example 2014-06-30 10:17:31 +02:00
José Valim 5979b5ca75 Merge pull request #2464 from KronicDeth/feature/2460/enum-sort-by
Enum.sort_by/3
2014-06-30 10:09:45 +02:00
Luke Imhoff f144cae300 Fix phrasing of Enum.sort_by/3 @doc 2014-06-29 16:09:52 -05:00
Luke Imhoff 8bcf473b68 Pull reused test data into variable 2014-06-29 16:03:42 -05:00
Luke Imhoff b10fdfc7b8 Fix spacing 2014-06-29 16:03:09 -05:00
Luke Imhoff 95eb1c0978 Fix @spec sort_by
Use format provided by José Valim that uses `when` to eliminate the need
for `@type mapped_element`.
2014-06-29 15:30:34 -05:00
José Valim 28264c6792 Improvements to the operator table
* |>, <<<, >>> and ^^^ were made left associative

  * <, >, <=, >= were given higher precedence than
    comparison ones (==, !=, etc)

  * xor is deprecated
2014-06-29 16:18:33 +02:00
José Valim 18f5063814 Update (soft) deprecations 2014-06-29 15:13:02 +02:00
José Valim 42d5c544cd Improve <<>> docs 2014-06-29 14:28:51 +02:00
José Valim 713a706958 Match test lines by proximity, closes #2450 2014-06-29 13:56:00 +02:00
José Valim ef23a3fe39 Start v0.14.3-dev 2014-06-29 11:51:53 +02:00
José Valim 44b0412042 Release v0.14.2 2014-06-29 11:39:33 +02:00
José Valim d984a82587 Merge pull request #2463 from hamiltop/master
Update documentation on Kernel.var!/2
2014-06-29 11:13:40 +02:00
hamiltop e59d50a891 Update documentation on Kernel.var!/2
The documentation is no longer accurate since passing atoms to `Kernel.var!/2` has been deprecated and removed.
2014-06-28 22:00:29 -07:00
Luke Imhoff 7a5cb8d374 Add @spec for Enum.sort_by/3 2014-06-28 20:38:01 -05:00
Luke Imhoff f23b133d29 Add @doc for Enum.sort_by/3 2014-06-28 20:23:32 -05:00
Luke Imhoff 0bdee49f7f Enum.sort_by/3
Enum.sort_by/3 takes a collection, a mapper function, an an optional
sorter function.  The function sorts by the mapped value using the sorter.
The optional sorter default to `<=/2`.
2014-06-28 17:52:04 -05:00
Eric Meadows-Jönsson 7bf568d0b3 Bump Hex version requirement 2014-06-28 16:47:30 +02:00
José Valim f95cafe5ca Fix regression when case is used inside cond head 2014-06-28 16:31:03 +02:00
José Valim f0e1443f21 Use iodata_to_binary in join/2 and map_join/3
Everything is previously converted to a string using
the String.Chars protocol, so we can just treat it
as iodata.
2014-06-28 14:33:20 +02:00
José Valim cf619585a6 No need to reverse a list when building an iolist 2014-06-28 12:21:17 +02:00
José Valim c639b01146 Update CHANGELOG 2014-06-28 12:02:58 +02:00
José Valim b4415f2a14 Change precedence of & to not special case / 2014-06-28 11:16:51 +02:00
José Valim 538b51c20a Remove usage of :filelib functions
This is to work around a regression in 17.1 where many
:filelib functions were broken. More information read:

http://erlang.org/pipermail/erlang-bugs/2014-June/004465.html
2014-06-27 23:00:18 +02:00
José Valim c8a0158791 Add @external_resource attribute, closes #2455 2014-06-27 22:16:34 +02:00
José Valim b088d4b90e Also filter out .ps1 files 2014-06-26 20:10:47 +02:00
José Valim 02a415facd Add MIX_ARCHIVES env variable 2014-06-26 19:48:32 +02:00
José Valim 5cee19067d Enum.join/2 and Enum.map_join/3 now uses iolists 2014-06-26 18:41:12 +02:00
José Valim 3496681236 Merge pull request #2457 from mururu/args-for-bodyless
Ensure we can only use variables and `\\` as arguments of bodyless clause
2014-06-26 17:07:00 +02:00
Yuki Ito a7645a2c8b Ensure we can only use variables and \\ as arguments of bodyless clause, closes #2374 2014-06-26 23:06:49 +09:00
José Valim d9423627f3 Document the role of MIX_HOME more explicitly 2014-06-26 14:48:44 +02:00
José Valim 5a8520fb0f Ensure we show a consistent error when we cannot evaluate .iex.exs 2014-06-26 14:41:23 +02:00
José Valim 4458ee1232 Remove --gen-debug option 2014-06-26 13:35:54 +02:00
José Valim a6b1cca91f Soft deprecate Mix.Generator.from_file/1 2014-06-26 13:35:54 +02:00
José Valim e2535731c8 Merge pull request #2453 from jwarwick/specialform_typo
Fixed typos in Kernel.SpecialForms docs
2014-06-25 22:01:35 +02:00
John Warwick 393ff69b1e Fixed typos in Kernel.SpecialForms docs 2014-06-25 15:49:16 -04:00
José Valim 033747ad63 Fix changelog message typo 2014-06-25 13:15:01 +02:00
José Valim 730d043631 Update CHANGELOG 2014-06-25 12:38:35 +02:00
José Valim c819bdbf74 Raise CondClauseError for cond clause mismatch 2014-06-25 01:46:56 +02:00
José Valim 64f8541ee6 Make cond a special form so it does not leak vars on head 2014-06-25 01:46:56 +02:00
Eric Meadows-Jönsson d1f4e2d49b Simplify .git directory check 2014-06-25 01:16:53 +02:00
Eric Meadows-Jönsson 5a13aecbae Error if git executable is not available 2014-06-25 00:15:11 +02:00
Eric Meadows-Jönsson 61904ee24c Remove git rev-parse check for valid .git directory
Instead pass `—git-dir=.git` to all git commands to ensure that the
command will only affect the local directory.
2014-06-25 00:14:35 +02:00
José Valim 5c7fdcc244 Soft deprecate EEx.TransformerEngine and EEx.AssignsEngine 2014-06-24 13:51:17 +02:00
José Valim f772877039 Update CHANGELOG 2014-06-24 13:28:41 +02:00
José Valim e571218917 Sigils no longer balance start and end tokens, closes #2284 2014-06-24 13:17:43 +02:00
José Valim aa01dd1fc1 Ensure derived protocols are defined with a file 2014-06-24 11:28:27 +02:00
José Valim 30971c9cb3 Merge pull request #2447 from jwarwick/task_docs
Fixing doc typos in Task
2014-06-23 20:16:09 +02:00
John Warwick abfc69a6e2 Fixing doc typos in Task 2014-06-23 14:02:31 -04:00
José Valim ac1fc484e4 Registered name returns an empty list, as in Erlang, to avoid ambiguity 2014-06-23 17:20:28 +02:00
José Valim 889bfebe4e Convert build_info to a map 2014-06-22 23:38:20 +02:00
José Valim e9899a5462 Fix local.install description 2014-06-22 18:00:47 +02:00
José Valim ec92bb118d Merge pull request #2444 from alco/kernel-cli-paths
Support paths inside archives in -pa and -pz options. Closes #2442
2014-06-22 14:38:55 +02:00
Alexei Sholik fd9ca435b7 Support paths inside archives in -pa and -pz options. Closes #2442 2014-06-22 15:26:36 +03:00
José Valim 3ec0d20621 Only set encoding when there is noshell, closes #2432 2014-06-22 14:16:40 +02:00
José Valim 16f3b928a1 Do not set -noshell if we are going to override it afterwards 2014-06-22 14:16:40 +02:00
José Valim fb20b6ddf6 Merge pull request #2443 from alco/doc-typo
Remove a stray XML tag from the path docs
2014-06-22 14:15:53 +02:00
Alexei Sholik d77a4d6c3f Remove a stray XML tag from the path docs 2014-06-22 14:44:58 +03:00
José Valim d7900239c2 Improve docs and defaults for File 2014-06-22 12:13:21 +02:00
José Valim babd98be3f Use mfa when starting remote nodes so it works with any elixir version 2014-06-21 20:43:16 +02:00
José Valim 9a85b1556b Merge pull request #2440 from tallakt/stream_unfold_typo
Fix unclear doc in Stream.unfold
2014-06-21 16:15:06 +02:00
Tallak Tveide d1da200af5 Fix unclear doc in Stream.unfold 2014-06-21 15:05:20 +02:00
José Valim e260b92f89 Merge pull request #2431 from alco/escript-name
Mention the fact that :escript_name is also used as a module name
2014-06-21 11:12:55 +02:00
José Valim e09e5c3f41 Merge pull request #2438 from jwarwick/helper_warning
Cleaning up IEx.Helpers.Flush warning
2014-06-21 11:12:01 +02:00
John Warwick a76a281309 Cleaning up IEx.Helpers.Flush warning 2014-06-20 22:09:28 -04:00
Alexei Sholik be42d00067 Update escript options: :name, :comment, :emu_args 2014-06-21 01:07:34 +03:00
José Valim f21a998f81 Merge pull request #2437 from lexmag/uri-parse
Let `URI.parse/1` work with `%URI{}`
2014-06-20 23:01:41 +02:00
Aleksei Magusev 89e0018d58 Let URI.parse/1 work with %URI{} 2014-06-21 00:37:16 +04:00
José Valim 21b691cd4e Do not expect options to have a certain format in the environment 2014-06-20 21:14:02 +02:00
José Valim a8a91c2282 Merge pull request #2435 from chyndman/makefile-win-epmd
workaround 'epmd -daemon' not working on Windows
2014-06-20 20:10:55 +02:00
José Valim 5275646966 Merge pull request #2433 from chyndman/contrib-win-update
Add extra Path directory, remove PowerShell warning
2014-06-20 20:10:34 +02:00
Chris Hyndman e60cd6a484 workaround 'epmd -daemon' not working on Windows 2014-06-20 10:53:23 -07:00
José Valim 552006bea3 Merge pull request #2434 from jwarwick/agent_doc
Fixed doc typos in Agent and File.Stream
2014-06-20 19:51:30 +02:00
John Warwick 2077b584ad Fixed doc typos in Agent and File.Stream 2014-06-20 13:22:25 -04:00
Chris Hyndman 29f05fda35 Add extra Path directory, remove PowerShell warning 2014-06-20 10:09:55 -07:00
Eric Meadows-Jönsson cbb7c545e0 Fix size/1 warnings 2014-06-20 14:24:36 +02:00
José Valim c909267077 Merge pull request #2429 from jwarwick/application_doc
Fixing typos in Application doc
2014-06-19 22:01:56 +02:00
John Warwick a9bbabfabc Fixing typos in Application doc 2014-06-19 15:49:31 -04:00
José Valim c4d666a4ba Fix typo on Access docs 2014-06-19 16:24:13 +02:00
José Valim 8e824d3da1 Merge pull request #2427 from fishcakez/task_init_call
Set :proc_lib initial call on task
2014-06-19 11:09:39 +02:00
Eric Meadows-Jönsson 41caa0d635 Merge pull request #2428 from knewter/feature/deriving_typofix
Fix typo in deriving docs
2014-06-19 01:39:28 +02:00
Josh Adams 3a0108edf8 Fix typo in deriving docs 2014-06-18 18:22:05 -05:00
James Fish 7123fb9d09 Set :proc_lib initial call on task
Sets the :proc_lib initial call to the {mod, fun, arity} of the task.

This allows :proc_lib.translate_initial_call(task.pid) to show the mfa
of a task process when debugging. Tools such as :observer and the erlang
shell make use of this function when getting information on a process.

This gives a useful initial call in the crash report sent to the error
logger when a task raises.
2014-06-18 22:58:20 +01:00
José Valim 6d8e118192 Check if Makefile.win is available before running nmake 2014-06-18 23:17:56 +02:00
José Valim 846703b31c Merge pull request #2426 from chyndman/mix-make-manager-win-nmake
make manager calls nmake /F Makefile.win on Windows
2014-06-18 23:07:52 +02:00
Chris Hyndman 46905b39a7 make manager calls nmake /F Makefile.win on Windows 2014-06-18 12:20:11 -07:00
Eric Meadows-Jönsson 0f43aca5bc Merge pull request #2424 from ericmj/delay-typespecs
Delay typespec compilation to after expansion
2014-06-18 20:01:17 +02:00
Eric Meadows-Jönsson 587c83cbff Delay typespec compilation to after expansion 2014-06-18 19:49:57 +02:00
José Valim e8194b1764 Fix wrong changelog entry 2014-06-18 16:57:20 +02:00
José Valim feb9cb645e Remove previously deprecated behaviour 2014-06-18 16:56:59 +02:00
José Valim 29f9a4fd67 Start v0.14.2-dev 2014-06-18 16:16:18 +02:00
José Valim 22f3a50eba Release v0.14.1 2014-06-18 16:09:05 +02:00
José Valim 470e5424bb Update CHANGELOG 2014-06-18 13:42:51 +02:00
José Valim 8f272c4cc8 Start epmd, clean up warnings 2014-06-18 10:50:48 +02:00
José Valim 46c23b1547 Merge pull request #2423 from fishcakez/genevent_stream_stop
Improve GenEvent.stream/2 cleanup
2014-06-18 10:26:31 +02:00
James Fish 4d607ace08 Improve GenEvent.stream/2 cleanup
Makes the cleanup of the Stream resource more efficient by reusing the
monitored process and its link to ensure the handler has been removed.

The flushing of messages, after removing the handler, is also only done
when needed.
2014-06-18 02:05:10 +01:00
José Valim 78adb5b200 Ensure handler does not get stuck and properly clean up mailbox 2014-06-17 23:12:02 +02:00
José Valim 311efb66a9 Add GenEvent.stream modes and make it sync by default 2014-06-17 20:20:50 +02:00
José Valim 272be2c088 Work around and document for mix test on windows, closes #2375 2014-06-17 19:12:55 +02:00
José Valim 911acc3173 Attempt to start epmd before running tests 2014-06-17 17:48:59 +02:00
José Valim 182cbfe541 Update to Unicode 7.0.0 2014-06-17 17:17:32 +02:00
José Valim c43bc6a06d Attempt to fix race condition on test by depending on last compiled file 2014-06-17 17:00:46 +02:00
José Valim 38aa5c57e5 Add Node.start/3 and Node.stop/0, closes #2406 2014-06-17 16:56:50 +02:00
José Valim 4f93dae442 Merge pull request #2415 from alco/list-format
Reformat unordered lists in all docstrings
2014-06-17 16:39:43 +02:00
Alexei Sholik 41c74e9682 Fix ordered list formatting
The rules are similar to those for the unordered lists (see previous
commits). Subsequent lines and paragraphs should be aligned to the
beginning of the sentence on the first line, like this:

  1. This is a list item,
     it has a second line.

     Also paragraph.

  2. Second list item.

         code fragment

Short, one-sentence items that can be read as part of the surrounding
paragraph can start with a non-capital letter
2014-06-17 16:34:20 +03:00
Alexei Sholik e0f5dd506a Apply uniform capitalization and punctuation rules to all lists
* Never use capital letters after `-`. Sentences start right after
    the bullet and continue to the first period.

  * When list elements are one or two sentences, use non-capitalized
    format without terminators.

    The two sentences are converted into one, separated by a semicolon.
    This mainly applies to the cases with short sentences or when only
    one of many list elements needs to have two sentences.

    Example:

        * `:opt_a` - basic description, no terminator
        * `:opt_b` - also short description; this used to be second
          sentence

  * When at least one list element needs to have real sentences, 
    the whole list is formatted like that, with a period at the end
    of each element:

        * `:opt_a` - this is still short.

        * `:opt_b` or `:opt_c` - but this is longer. May have multiple 
          sentences.

          Or even paragraphs.
2014-06-17 16:25:16 +03:00
José Valim f90eeaa024 Fix map printing to indent 2 levels, closes #2165 2014-06-17 13:57:21 +02:00
José Valim 9451e69996 Improve task log based on @fishcakez feedback 2014-06-17 13:26:22 +02:00
José Valim 924350868a Deprecate previously soft deprecated conversion functions 2014-06-17 13:21:34 +02:00
José Valim ee91be519a Update CHANGELOG 2014-06-17 13:03:38 +02:00
José Valim 90951be944 Remove deprecated on_exit callbacks 2014-06-17 12:37:08 +02:00
José Valim 65cf31d32d Do not match files starting with . in Path.wildcard/2 by default 2014-06-17 11:33:25 +02:00
José Valim e14c9ea6ce Merge pull request #2419 from lexmag/uri-binaries
Make `URI.encode*` to work only with binaries
2014-06-17 10:43:15 +02:00
Aleksei Magusev 8635fcc869 Make URI.encode* to work only with binaries 2014-06-17 12:41:01 +04:00
José Valim 65809a5644 Merge pull request #2416 from chyndman/win-volume-path-normalize
Add normalize dispatch for Windows volume letters
2014-06-17 10:35:09 +02:00
José Valim d53a315069 Merge pull request #2414 from christhekeele/empty-range-slice
Ensures Enum.slice always returns a list.
2014-06-17 10:31:27 +02:00
José Valim 8d2199c3f3 Merge pull request #2418 from jw2013/master
fix typo in docstring of String.at and String.slice
2014-06-17 10:29:52 +02:00
jw2013 54e69c023a fix typo in docstring of Enum.at and Enum.slice 2014-06-16 21:48:39 -04:00
Alexei Sholik 55503239a4 Add missing backticks 2014-06-17 03:00:00 +03:00
Chris Hyndman 19d8446ac0 Improved match and guard 2014-06-16 16:50:11 -07:00
Chris Hyndman 5a94906f1d Add normalize dispatch for Windows volume letters 2014-06-16 16:19:57 -07:00
Alexei Sholik 915ba1bb29 Reformat unordered lists in all docstrings
List items are marked with a star (*). Bullets marking list elements
must have a 2-space indent. The continuation lines should be indented
by 2 more spaces.

The second and subsequent paragraphs need to be indented 4 spaces
from the beginning of the line to be included in the list item.

The code fragments have to be indented 8 spaces from the beginning
of the line (or 4 spaces from the beginning of the preceding paragraph).

Sublists are marked with a hyphen (-). They follow the rules above
with the difference that their initial indent will be greater
(8 spaces from the beginning of the line).
2014-06-17 02:01:27 +03:00
Chris Keele d35d03a8ee Ensures Enum.slice always returns a list.
Both Enum.slice/3 and Enum.slice/2 now will return empty lists for
impossible or out of bound ranges, rather than `nil`.

Fixes #2410.
2014-06-16 17:22:01 -05:00
José Valim b959049c77 Properly indent tables 2014-06-16 23:57:14 +02:00
José Valim 24c4bc3009 Merge pull request #2412 from christhekeele/empty-string-slice
Ensures String.slice always returns a string
2014-06-16 23:09:36 +02:00
Chris Keele 000a174353 Ensures String.slice returns a string.
Both String.slice/3 and String.slice/2 now will return empty strings for
impossible or out of bound ranges.
2014-06-16 15:45:28 -05:00
José Valim b126e3b089 Merge pull request #2408 from ericmj/base-case
Add options for which case to use in Base functions
2014-06-16 22:04:15 +02:00
José Valim 319957f5df Merge pull request #2409 from alco/escriptize-config
Put escript options under :escript in the project config
2014-06-16 22:03:34 +02:00
Alexei Sholik cb2eb7d376 Add deprecation warnings for old escript options 2014-06-16 22:52:34 +03:00
Alexei Sholik 1857267872 Put escript options under :escript in the project config 2014-06-16 22:52:27 +03:00
Eric Meadows-Jönsson 91faec5f65 Add options for which case to use in Base functions 2014-06-16 21:36:26 +02:00
Eric Meadows-Jönsson 0f0a91faef Bump hex version requirement to 0.2.5 2014-06-16 21:22:42 +02:00
Eric Meadows-Jönsson baec57637e Fix consolidate task example 2014-06-16 17:21:00 +02:00
José Valim 0c8c6f03e0 Clarify quote docs 2014-06-16 16:42:05 +02:00
José Valim 7bdbefb277 Improve compile.protocols docs and make it public 2014-06-16 16:07:31 +02:00
José Valim ec0e7d8056 Remove get_in/1 2014-06-16 15:49:19 +02:00
José Valim c2e912bdfd --quiet is no longer supported 2014-06-15 17:45:04 +02:00
José Valim 14e1d410f1 Do not tag mix exceptions if in root project 2014-06-15 17:28:23 +02:00
José Valim 15925573e8 Ensure we can measure a timestamp difference on tests 2014-06-15 17:12:04 +02:00
José Valim f32ede4e3f Ensure umbrella is recompiled when a child dependency changes 2014-06-15 16:35:25 +02:00
José Valim 1dc9193e69 Rely on print_app for mix deps.compile and ensure path deps are compiled 2014-06-14 20:57:07 +02:00
Eric Meadows-Jönsson dacb30ad05 Fix path normalization of "/.."
Closes #2404.
2014-06-14 00:51:59 +02:00
José Valim 362df17316 Update CHANGELOG 2014-06-13 17:11:48 +02:00
José Valim d54d0f6a6b Log when tasks go down 2014-06-13 17:11:47 +02:00
José Valim 72d2f38cb5 Merge pull request #2402 from lexmag/encode-www-form
Introduce `URI.encode(decode)_www_form/2` functions
2014-06-13 14:51:43 +02:00
Aleksei Magusev b3cc527126 Introduce URI.decode_www_form/1 function 2014-06-13 16:31:26 +04:00
Aleksei Magusev 6b45855f0e Fix edge cases for URI.decode/1 2014-06-13 16:30:16 +04:00
Aleksei Magusev 40835556da Introduce URI.encode_www_form/1 function 2014-06-13 16:30:16 +04:00
José Valim dc9a47d259 Do not warn on case template teardown/teardown_all definition 2014-06-13 09:20:08 +02:00
José Valim 2d1cf2ebc1 Merge pull request #2400 from alco/patch-3
Fix a typo in Kernel docs
2014-06-13 09:07:00 +02:00
Alexei Sholik 9a56e5ef5a Fix a typo in Kernel docs 2014-06-13 01:08:00 +03:00
José Valim ab148551ba Merge pull request #2399 from chyndman/win-disambiguate-exe
Disambiguate (w)erl to (w)erl.exe
2014-06-12 21:30:26 +02:00
chyndman 4d7c75f2ca Disambiguate (w)erl to (w)erl.exe 2014-06-12 12:23:31 -07:00
José Valim e7f3db9fd4 Merge pull request #2397 from alco/option-parser-fix
Do not allow underscores in option names. Closes #2391
2014-06-12 19:52:11 +02:00
José Valim 4682511923 Make test and test cases exit with reason :shutdown
This guarantees OTP processes are properly shutdown when
the test finishes.
2014-06-12 19:21:37 +02:00
Alexei Sholik e942c07f34 Do not allow underscores in option names. Closes #2391 2014-06-12 19:26:47 +03:00
José Valim dc403e4311 Merge pull request #2395 from pwoolcoc/mix-help-new-typo
fix typo in `mix help new`
2014-06-11 22:19:15 +02:00
Paul b3c6bc2bc5 fix typo in mix help new 2014-06-11 15:42:30 -04:00
José Valim c377483715 Ensure mix test print app name for umbrella 2014-06-11 13:27:30 +02:00
José Valim 3ded858534 Remember users umbrella deps are not shared 2014-06-11 12:41:45 +02:00
José Valim aa09b1e2d2 Improve docs for URI 2014-06-11 11:26:24 +02:00
José Valim 8cb4c719f9 Merge pull request #2392 from lexmag/improve-uri
Make `URI.encode/1` to conform to RFC 3986. Introduce `URI.encode/2` function.
2014-06-11 11:15:14 +02:00
Aleksei Magusev ffa2abd8c6 Introduce URI.encode/2 function
`URI.encode/1` escapes character if it
is not satisfied `URI.char_unescaped?/1`
2014-06-10 19:56:23 +04:00
Aleksei Magusev 14722ba102 Make URI.encode/1 to conform to RFC 3986 2014-06-10 16:20:44 +04:00
Aleksei Magusev 7f4437ece5 Improve function naming in URI 2014-06-10 16:20:43 +04:00
José Valim afc7644fe0 Guarantee we get a keyword on mix deps specs 2014-06-10 12:41:19 +02:00
José Valim 7de880495b Remove previously deprecated code, enable new deprecations 2014-06-10 12:35:07 +02:00
José Valim 29addf9e93 Update Access docs, closes #2388 2014-06-10 12:26:12 +02:00
José Valim 6e9f9f254f Kill test process only after on_exit runs
We do this to avoid links created during the test from
crashing when the test exits which happened before the on_exit
callbacks run.

This was particularly troubling with GenServer, GenEvent
and Supervisor, in which the first does not crash when
a linked process exits with normal reason, but the other
two do. Due to this behaviour, before this change there was
a possibility of race conditions causing tests to crash on
on_exit.

In case the test process exits with a non-normal reason,
all links will be broken, so the problem doesn't manifest
in such cases.
2014-06-10 12:10:31 +02:00
José Valim db25e24eb8 Merge pull request #2390 from benjamintanweihao/patch-3
Update IEx.pry documentation
2014-06-10 12:10:25 +02:00
Benjamin Tan Wei Hao 8a184a6b3d Update IEx.pry documentation
Modified example that works with the current version of Elixir. Fix some typos in the pry console.
2014-06-10 17:54:44 +08:00
José Valim fcd6213c4c Merge pull request #2386 from alco/patch-2
Fix a typo
2014-06-09 17:34:22 +02:00
Alexei Sholik 5409b7e707 Fix a typo 2014-06-09 18:28:45 +03:00
José Valim 815772811f Start v0.14.1-dev 2014-06-08 21:14:54 +02:00
José Valim 9cff61493e Release v0.14.0 2014-06-08 21:09:14 +02:00
José Valim 65a3434bd4 Do not support Macro.var/1 2014-06-08 12:17:23 +02:00
José Valim 4ff9f9aa29 Update CHANGELOG 2014-06-08 11:15:27 +02:00
José Valim 3d982fd1a9 Deprecate var!(:atom), add Macro.var/1 2014-06-08 11:05:39 +02:00
José Valim 396b1e68a8 Make the exit conditions for GenServer/GenEvent/Supervisor clear 2014-06-07 18:32:31 +02:00
José Valim 9c549b3465 Fix help for elixirc executables, closes #2380 2014-06-06 20:59:43 +02:00
José Valim 04194a189d Support --config on mix run 2014-06-06 17:25:36 +02:00
José Valim 35e04d0ea5 Allow mix loadconfig to be called multiple times 2014-06-06 16:40:29 +02:00
José Valim 1038018e84 Migrate from size to byte_size and tuple_size 2014-06-06 13:33:02 +02:00
José Valim 2b3e53670e Provide Protocol.derive/3 2014-06-05 22:54:10 +02:00
José Valim dff93a44c2 Soft deprecate size/1
Closes #2356
2014-06-05 21:02:10 +02:00
José Valim f76c8fec7b Support E in scientific notation for floats 2014-06-05 13:18:25 +02:00
José Valim 8b19b0b3f4 Merge pull request #2379 from jw2013/master
Fix broken promise in Kernel.SpecialForms
2014-06-05 13:09:25 +02:00
jw2013 77fad29a1b Fix broken promise in Kernel.SpecialForms 2014-06-05 06:53:15 -04:00
José Valim 5bab9cae49 Avoid race conditions on ExUnit test suite 2014-06-05 12:16:37 +02:00
José Valim 05507d1f3a Merge pull request #2378 from scrogson/patch-1
Grammar fix.
2014-06-05 10:29:21 +02:00
Sonny Scroggin e369900b06 Grammar fix. 2014-06-04 22:56:02 -05:00
José Valim a629cfce86 Merge pull request #2376 from chyndman/initial-mix-powershell-script
Initial PowerShell script addressing mix commas
2014-06-04 18:33:44 +02:00
chyndman 1a93d35d9c Initial PowerShell script addressing mix commas 2014-06-03 16:45:24 -07:00
José Valim 06cc1c183f Get rid of Mix.Sup 2014-06-03 20:31:47 +02:00
José Valim cb5806bbf3 Deprecate teardown and teardown_all 2014-06-03 18:56:47 +02:00
José Valim db70fd5a5f Also support on_exit on setup_all 2014-06-03 18:28:04 +02:00
José Valim 513ed0bbae Move on handler functionality to its own module 2014-06-03 18:23:42 +02:00
José Valim 3bbae0a368 Merge pull request #2365 from MSch/exunit-on-exit
Support on_exit for registering callbacks in ExUnit.
2014-06-03 17:26:23 +02:00
José Valim 2134db3a0d Merge pull request #2373 from jwarwick/cli
Fixed reading .mix/config.exs
2014-06-03 10:25:50 +02:00
John Warwick 80f35eab7d Fixed reading .mix/config.exs 2014-06-02 22:58:37 -07:00
Eric Meadows-Jönsson ab8a2d195f Only import select functions from Mix.Config 2014-06-02 23:34:57 +02:00
José Valim a7096b5061 Merge pull request #2372 from chyndman/eacces-posix-typo
:eaccess becomes :eacces
2014-06-03 01:19:20 +09:00
chyndman 8552886a8a :eaccess becomes :eacces 2014-06-02 09:16:26 -07:00
José Valim 789a657490 Merge pull request #2371 from zambal/master
Fix operator precedence bug in Mix.Compilers.Elixir.clean/1
2014-06-02 23:33:14 +09:00
zambal 2ee44eba29 Fix operator precedence bug in Mix.Compilers.Elixir.clean/1 2014-06-02 16:18:19 +02:00
José Valim e4a9fae0fd Remove dependency on manifest files 2014-06-02 14:13:00 +02:00
José Valim 0030b282c1 Clean up Path and Task code 2014-06-01 18:52:39 +02:00
José Valim 9c90955863 Merge pull request #2370 from keithseahus/master
Fix Path.absname("/") and Path.expand("/") to return the absolute path "/".
2014-06-01 17:12:36 +02:00
Keisuke Takahashi 61e626bdb8 Add tests for Path.expand("/") to be "/" (the issue is already fixed). 2014-06-01 21:37:12 +09:00
Keisuke Takahashi 3c40de4c0c Fix Path.absname("/") to return the absolute path "/", not "". 2014-06-01 21:23:25 +09:00
José Valim 86a8a6ee88 Tidy up previous commits 2014-06-01 11:32:33 +02:00
José Valim 4d5696e074 Merge pull request #2367 from chyndman/file-rm-windows-read-write
File.rm makes files writeable on Windows before deletion
2014-06-01 09:39:57 +02:00
José Valim 892f211550 Merge pull request #2368 from bkochendorfer/add_proxy_support
Add http and https proxy options
2014-06-01 09:37:23 +02:00
Brett K 2caa16ac22 Add http and https proxy options 2014-06-01 00:10:02 -05:00
chyndman 32c5694551 Corrected octal/decimal handling 2014-05-31 10:43:04 -07:00
Eric Meadows-Jönsson 1aeb7ae7d0 Fix :io.columns calls 2014-05-31 18:40:28 +02:00
chyndman fe8f3464b6 Added rm test for read-only files 2014-05-31 08:44:13 -07:00
chyndman 8941d2821d chmod does not require conversion of bases 2014-05-31 08:36:34 -07:00
José Valim 7ba3c3cfb7 Revert "EEx tests aware of CR+LF newline on Windows"
This reverts commit f60d3668ed.
2014-05-31 09:58:29 +02:00
chyndman 5cc2724b40 File.rm makes files writeable on Windows before deletion 2014-05-30 17:15:23 -07:00
Martin Schürrer 40dff7ebe8 Support on_exit for registering callbacks in ExUnit. 2014-05-30 17:55:52 +02:00
José Valim 5158a19fbd Add a CHANGELOG entry to task change 2014-05-30 16:16:49 +02:00
José Valim fd936bb326 Merge pull request #2350 from ericmj/keyword-error
Raise if giving non-keywords to Keyword
2014-05-30 16:03:43 +02:00
José Valim 3e54d33481 Do not catch failures on tasks
Closes #2345
2014-05-30 16:01:08 +02:00
José Valim 0fe1e686b3 Revert "Set up the monitor only on await for tasks"
This reverts commit 85a04fe919.
2014-05-30 15:59:17 +02:00
José Valim 9e80f3b82b Add guard to impl of Enumerable for Function
This improves the stacktrace in case of wrong dispatch.
2014-05-30 15:59:17 +02:00
José Valim 01bda975c3 Add missing |> Stream.run/1 to its documentation 2014-05-30 15:59:17 +02:00
José Valim 05a8ce56a3 Merge pull request #2364 from pwoolcoc/minor-mix-help-fix
Document the additional `--names` argument that `mix help` can take
2014-05-30 15:34:54 +02:00
Paul d498a89267 Document the additional --names argument that mix help can take 2014-05-30 07:59:00 -04:00
José Valim 46db0d4304 Fix typo on CLI --app parsing 2014-05-29 23:06:36 +02:00
José Valim f2ec77886a Merge pull request #2361 from pwoolcoc/mix-help-names
Mix help names
2014-05-29 20:27:46 +02:00
Paul 4e8ae3abdb add test for mix help --names 2014-05-29 14:07:28 -04:00
Paul dc7ffae763 combine loops 2014-05-29 14:07:19 -04:00
Paul b7897b224d enable passing --names to mix help to only display command names 2014-05-29 14:07:12 -04:00
José Valim c80a65b657 Improve formatting 2014-05-29 19:43:18 +02:00
José Valim f02cd8437b Use new Mix.Config in generated files 2014-05-29 19:37:35 +02:00
José Valim 7d87e84803 Define Mix.Config 2014-05-29 19:11:03 +02:00
José Valim b4a0b4cc19 Provide Mix.raise/1 that attaches mix info to Mix exceptions
Closes #1744
2014-05-29 13:35:26 +02:00
José Valim 1940f490b2 Merge pull request #2358 from chyndman/win-contributing-edit
qualify that PowerShell doesn't work yet
2014-05-28 20:46:46 +02:00
chyndman 69098968be qualify that PowerShell doesn't work yet 2014-05-28 11:40:09 -07:00
José Valim 85a04fe919 Set up the monitor only on await for tasks
Closes #2345
2014-05-28 20:10:43 +02:00
José Valim 54db13ae56 Merge pull request #2354 from chyndman/eex-test-crlf-newlines
EEx tests aware of CR+LF newline on Windows
2014-05-28 19:30:53 +02:00
chyndman f60d3668ed EEx tests aware of CR+LF newline on Windows 2014-05-28 10:25:16 -07:00
Eric Meadows-Jönsson 3471898364 Raise if giving non-keywords to Keyword 2014-05-28 18:28:57 +02:00
José Valim bf7d172f11 Make available_compiler_options public and improve error message 2014-05-28 14:12:29 +02:00
José Valim 28457563ce Merge pull request #2351 from ericmj/compiler_opts
Do not allow custom compiler options
2014-05-28 14:08:38 +02:00
José Valim 280a5ceedc Fix get_and_update_in/2 docs 2014-05-28 13:54:59 +02:00
José Valim a696cf41dc Improve protocol docs 2014-05-28 13:48:35 +02:00
José Valim f3f0ec09b7 Add get_and_update_in/* ops and update CHANGELOG 2014-05-28 13:38:51 +02:00
José Valim e040688bd9 Require get_and_update/3 instead of update/3 in Access 2014-05-28 11:05:58 +02:00
José Valim 9d84e1c007 Merge pull request #2353 from scrogson/patch-1
Supervisor documentation updates
2014-05-28 10:20:50 +02:00
José Valim 2e6a83dff9 Merge pull request #2352 from chyndman/mix-shell-tests-win
Mix tests aware of Windows' esoteric newline behavior
2014-05-28 10:17:31 +02:00
Sonny Scroggin d866a26ac1 supervise vs. supervisor 2014-05-28 02:18:14 -05:00
Sonny Scroggin 3453c30f4c import happens already with use Supervisor 2014-05-28 02:17:35 -05:00
Sonny Scroggin 50a7b0e1bc Small typo 2014-05-28 02:01:30 -05:00
chyndman 7ce790035d Mix.CmdTest, Mix.ShellTest and Windows newlines (take 2) 2014-05-27 14:55:02 -07:00
chyndman 606cd2ff69 Defined os_newline in test_helper for Mix 2014-05-27 14:36:22 -07:00
José Valim 9c9d2e1ea0 Fix Kernel bootstrap 2014-05-27 23:18:06 +02:00
José Valim 6e7baa42ac Test and assert valid paths 2014-05-27 23:12:57 +02:00
chyndman 633f92edbe Mix.CmdTest, Mix.ShellTest and Windows newlines (take 2) 2014-05-27 14:06:59 -07:00
chyndman b23f2fc360 Mix.ShellTest aware of Windows newline behavior 2014-05-27 12:39:48 -07:00
chyndman 5b31ec421d Mix.CmdTest aware of Windows newline behavior 2014-05-27 12:31:55 -07:00
José Valim 2d38f20b43 Add get_in/1, put_in/2 and update_in/2 2014-05-27 13:56:18 +02:00
Eric Meadows-Jönsson 9f6f92b0f2 Do not allow custom compiler options 2014-05-27 12:46:09 +02:00
José Valim 4a183b462c Fix nested updates bootstrapping 2014-05-27 10:47:58 +02:00
José Valim 4d53d8c710 Add get_in/2, put_in/3 and update_in/3 2014-05-27 10:14:19 +02:00
José Valim a09a0c2a1a Add Access.update/3 2014-05-27 10:14:19 +02:00
José Valim 6a42ab0797 Merge pull request #2348 from chyndman/mix-build-project-and-tests-win
Mix.Project.build_structure tested for copying fallback
2014-05-27 08:54:58 +02:00
chyndman 183ed3ffdd Mix.Project.build_structure test asserts according to symlink_or_copy 2014-05-26 14:43:23 -07:00
José Valim 8a8779fcbc Merge pull request #2349 from chyndman/mix-cli-test-win-stderr
Mix.CLITest redirects stderr to stdout for testing on Windows
2014-05-26 23:28:56 +02:00
chyndman bcb8cd763e Mix.CLITest redirects stderr to stdout for testing on Windows 2014-05-26 12:37:13 -07:00
chyndman 97c0809845 Mix.Utils.build_project returns :ok always 2014-05-26 11:05:27 -07:00
José Valim e33a9557aa Merge pull request #2346 from alco/md-fixes
Fix formatting in .md files
2014-05-26 14:43:57 +02:00
José Valim 43706a4623 mix new no longer generates a supevision tree by default
Please pass `--sup` instead, closes #2074.
2014-05-26 14:41:56 +02:00
Alexei Sholik b816c98877 Fix formatting in .md files 2014-05-26 15:34:52 +03:00
José Valim 23f0ff81c4 Write consolidated files to _build 2014-05-26 12:27:39 +02:00
José Valim 6aada7fe3c Update CHANGELOG and fix tasks printing 2014-05-26 12:12:15 +02:00
José Valim e24cef0126 Add protocol consolidation with mix compile.protocols 2014-05-26 11:45:22 +02:00
José Valim b161070345 Merge pull request #2344 from madlep/fix_agent_start_link_timeout_option_doc
fix documentation of `:timeout` option in `start_link/2`
2014-05-26 09:48:34 +02:00
José Valim 0b4f9ad486 Merge pull request #2343 from alco/string-doc-null
Escape invalid characters in the docstring for String.chunk
2014-05-26 09:48:19 +02:00
Julian Doherty d484ca8ae2 fix documentation of :timeout option in start_link/2
added line break to make it clearer that `:timeout` is a different
option to `:name`.
2014-05-26 15:39:26 +10:00
Alexei Sholik d77c1a34f4 Escape invalid characters in the docstring for String.chunk 2014-05-26 04:44:57 +03:00
José Valim f6b0f29f5d Add docs for @derive 2014-05-25 19:18:02 +02:00
José Valim 436f563ad3 Ensure variables do not leak on callbacks 2014-05-25 14:22:23 +02:00
José Valim 81b80d5b9c No need to name the agent 2014-05-25 13:33:56 +02:00
José Valim 425cea5a84 Merge pull request #2341 from pma/master
Convert Mix.Tasks.Compile.Elixir.ManifestCompiler from GenServer to Agent
2014-05-25 05:27:29 -06:00
José Valim b4c0826467 Update repo links 2014-05-25 12:41:57 +02:00
José Valim d94491841a Generate docs per app 2014-05-25 12:26:52 +02:00
José Valim bb1093ef2d Improve IEx description 2014-05-25 11:59:58 +02:00
Paulo Almeida 863e22a7e2 Convert Mix.Tasks.Compile.Elixir.ManifestCompiler from a GenServer to an Agent 2014-05-25 10:30:48 +01:00
José Valim aa682a4704 Merge pull request #2340 from chyndman/win-contributing-howto
Instructions for contribution from Windows (first draft)
2014-05-25 16:21:09 +08:00
chyndman 01d5f99437 Instructions for contribution from Windows (first draft) 2014-05-24 23:03:08 -07:00
José Valim 34c2da2c65 Initial work on @derive for structs 2014-05-25 01:43:58 +02:00
José Valim a3d5be91f2 Remove deprecated quote options 2014-05-25 01:43:58 +02:00
José Valim bbb77e0102 Merge pull request #2339 from pma/master
Convert Mix.ProjectStack from a GenServer to an Agent
2014-05-25 01:43:13 +02:00
Paulo Almeida 19164d2e55 Convert Mix.ProjectStack from a GenServer to an Agent 2014-05-24 19:57:11 +01:00
José Valim a950d4fda1 Merge pull request #2337 from patrickgombert/master
Convert Mix.TasksServer from a GenServer to an Agent
2014-05-25 03:01:35 +09:00
Patrick Gombert 33ac879521 Convert Mix.TasksServer from a GenServer to an Agent 2014-05-24 12:37:30 -05:00
José Valim 6ef4c80724 Merge pull request #2338 from jwarwick/autolinks
Fixing doc links
2014-05-25 02:27:36 +09:00
John Warwick 3843ee6ce5 Fixing doc links 2014-05-24 19:09:23 +02:00
José Valim 3ab1db6740 Move Protocol consolidation up to the Protocol module 2014-05-24 18:28:48 +02:00
José Valim 1f2dc0f716 Mark trie structs as private 2014-05-24 14:11:11 +02:00
José Valim 83fa2fbbbf Update CHANGELOG 2014-05-24 13:58:51 +02:00
José Valim cc969f8d01 Remove wrong record wording 2014-05-24 13:53:11 +02:00
José Valim 3de70b909a Merge pull request #2331 from alco/string-chunk
Add String.chunk/2
2014-05-24 20:52:37 +09:00
José Valim 7782227182 Remove duplication on utils test assertion 2014-05-24 13:49:26 +02:00
Alexei Sholik 58cd9e5c11 Add String.chunk/2 2014-05-24 14:45:28 +03:00
José Valim 862ab01f94 Merge pull request #2327 from chyndman/win-copy-always
Mix.Utils.symlink_or_copy always copies on Windows
2014-05-24 20:42:55 +09:00
José Valim 2c630ff33c Enable previously set deprecations 2014-05-24 13:13:30 +02:00
José Valim 007571e979 Remove deprecated records 2014-05-24 12:59:01 +02:00
José Valim 53552d434d Merge pull request #2335 from alco/genserver-doc
Genserver doc
2014-05-24 19:46:03 +09:00
José Valim eb32937858 Get rid of Kernel.access/2 in favor of Access.get/2 2014-05-24 12:45:33 +02:00
José Valim 6720cb6830 Simplify beam writing mechanism 2014-05-24 12:32:39 +02:00
Alexei Sholik 16c0262122 Consistent naming of the “Name Registration” section across docs 2014-05-24 13:29:52 +03:00
Alexei Sholik 2606f47138 Proof-read GenServer docs 2014-05-24 13:28:00 +03:00
José Valim 9337d50775 Merge pull request #2334 from alco/supervisor-doc
Proof-read Supervisor docs
2014-05-24 19:22:00 +09:00
Alexei Sholik 069415595a Proof-read Supervisor docs 2014-05-24 13:13:12 +03:00
José Valim 4e88d82871 Merge pull request #2333 from alco/genevent-doc
Fix formatting and proof-read GenEvent docs
2014-05-24 18:57:05 +09:00
Alexei Sholik 5e8d948e39 Fix formatting and proof-read GenEvent docs 2014-05-24 12:55:31 +03:00
José Valim 893700f65e Start v0.14.0-dev 2014-05-24 11:26:21 +02:00
José Valim c3f0929a77 Merge pull request #2326 from alco/docs-beam-chunks
Docs beam chunks
2014-05-24 18:26:11 +09:00
Alexei Sholik 8448513c8b Update tests that need to read module docs to write BEAM code to disk 2014-05-24 04:33:41 +03:00
Alexei Sholik bc65fc0276 Preserve docs chunk during protocol consolidation 2014-05-24 03:45:30 +03:00
Alexei Sholik b1f0d3ed99 Add version tag to Elixir’s beam chunk
This will allow us to change the layout of the chunk in the future
2014-05-24 03:45:29 +03:00
Alexei Sholik 211d9428b4 Add Code.get_docs/2 function 2014-05-24 03:45:29 +03:00
Alexei Sholik 8a3f2cfc2f Remove :docs and :moduledoc clauses from the __info__ function
Instead, we keep the docs in a custom chunk within the .beam file;
this will allow us to lazily load them
2014-05-24 03:45:29 +03:00
chyndman fff7a85769 Mix.Utils.symlink_or_copy: return types for copying and corresponding tests 2014-05-23 12:47:57 -07:00
chyndman 377c64d1b0 Mix.Utils.symlink_or_copy always copies on Windows 2014-05-23 10:18:11 -07:00
367 changed files with 23859 additions and 11443 deletions
+1 -3
View File
@@ -4,12 +4,10 @@
/ebin
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
/lib/elixir/src/elixir.app.src
/lib/elixir/src/*_lexer.erl
/lib/elixir/src/*_parser.erl
/lib/elixir/test/ebin
/rel/elixir
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.elixir.plt
+2
View File
@@ -7,3 +7,5 @@ notifications:
- eric.meadows.jonsson@gmail.com
otp_release:
- 17.0
- 17.1
- 17.3
+373 -31
View File
@@ -1,4 +1,346 @@
# v0.13.3 (2014-05-24)
# Changelog
## v1.0.2 (2014-10-21)
* Bug fixes
* [EEx] Remove invalid warning when attempting to inline code in EEx
* [EEx] Unify syntax errors to always include `file:line:`
* [IEx] Set null prompt function in dumb term mode
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
* [Kernel] Improve error messages when parsing fails on aliases
* [Kernel] Improve warnings coming from compile-time evaulation of arguments
* [String] Fix `String.replace/4` typespec
## v1.0.1 (2014-10-07)
* Enhancements
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
* Bug fixes
* [CLI] Fix Elixir CLI when running on Cygwin
* [ExUnit] Include stacktraces in ExUnit timeouts
* [Mix] Load dependencies before `deps.check` compiles dependencies
* [Mix] Ensure `Mix.Config` is deep merged
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
* [Mix] Do not crash on malformed proxy env var
* [Stream] Ensure `chunk/4` works correctly when halted
* [System] Do not allow relative paths in `System.cmd/3` as documented
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
## v1.0.0 (2014-09-10)
* Enhancements
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
* Backwards incompatible changes
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
* [List] `List.unzip/2` was removed
## v1.0.0-rc2 (2014-09-07)
* Enhancements
* [ExUnit] Print process mailbox on failing `assert_receive`
* [ExUnit] Tag all doctests with the `:doctest` tag
* [Kernel] Friendlier `unquote_splicing` error on inline quote
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
* [Mix] `mix deps.unlock` warns when given a missing dependency
* [Mix] Fetch hex using HTTPS
* Bug fixes
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
* [GenEvent] Do not consider swap handlers to be failures in a stream
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
* [Logger] Recompute sync/async mode on Logger configuration
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
* Deprecations
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
* Backwards incompatible changes
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
## v1.0.0-rc1 (2014-08-30)
* Enhancements
* [ExUnit] Add timeouts to tests (which is configurable via tags)
* [GenEvent] Add support for `GenEvent.ack_notify/2`
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
* [Logger] Allow any value that implements `String.Chars` to be logger
* [Logger] Allow `Logger.log/3` timeout to be configured
* [Mix] Add `--force` option to `mix local.rebar`
* [Mix] Fix `--no-color`/`--color` option for `mix test`
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
* [Mix] Support protocol consolidation on escripts
* Bug fixes
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
* [GenEvent] Do not allow a handler to be added more than once
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
* [Mix] Do not pre-compile a Mix project if an alias was found
* [Mix] Properly handle compilation errors in the Erlang compiler
* [Mix] Always try to compile project if task cannot be found
* [Mix] Purge in memory modules before consolidating
* [Mix] Always compile a project if a task cannot be found
* Deprecations
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
* Backwards incompatible changes
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
## v0.15.1 (2014-08-10)
* Enhancements
* [GenEvent] Support `:ack` mode for GenEvent streams
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
* [kernel] Print warnings when used ? with characters with escape codes
* [Logger] Add SASL log forwarding option to Logger
* [Logger] Add `$padlevel` to option Logger formatter
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
* [Mix] Load `config/config.exs` inside escripts
* [Mix] Store and check Elixir version requirement for generated archives
* Bug fixes
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
* [Kernel] Do not expand args for unknown functions/macros
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
* [Kernel] Do not raise conflicts on imports used from inside quotes
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
* [Macro] Ensure bitstrings work with `Macro.escape/1`
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
* [Mix] Leave it up to the application to start the Logger after compilation
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
* Deprecations
* [Collectable] Having a function as collectable is deprecated
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
## v0.15.0 (2014-08-02)
* Enhancements
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
* [IEx] Support `--werl` call on Windows
* [Logger] Add `Logger`
* [Map] Add `Map.from_struct/1`
* [Mix] Allow `--app` flag to be passed to `mix new`
* [Mix] Support lowercase `http(s)_proxy` environment variables
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
* [String] `String.slice/2` and `String.slice/3` have been optimized
* Bug fixes
* [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
* [IEx] Ensure remote shells can be started with `--remsh`
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
* [Kernel] Ensure private functions are not exported
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
* [Regex] Do not consider include captures on `Regex.split/3` results
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
* Deprecations
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
* Backwards incompatible changes
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
* [Regex] Do not consider include captures on `Regex.split/3` results
## v0.14.3 (2014-07-12)
* Enhancements
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
* [Enum] Add `Enum.sort_by/3`
* [ExUnit] Match the line filter by proximity instead of exact match
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
* [IO] Add `IO.(bin)read(device, :all)`
* [Kernel] Print a warning if a dangling `@doc` clause is found
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
* [Mix] Issue warning and reset mtime for source files from the future
* [Mix] Support task aliases in Mix
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
* [String] Improve performance of `String.split/1`
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
* Bug fixes
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
* [Kernel] Run command line and escripts in a process that does not trap exits
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
* [Macro] `to_string` correctly displays sigils
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
* Soft deprecations (no warnings emitted)
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
* Deprecations
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
* [System] Giving a char list to `System.find_executable/1` is deprecated
* Backwards incompatible changes
* [Access] No longer fill in missing intermediate values with empty maps
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
* [Kernel] `defstruct` no longer automatically defines a type
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
## v0.14.2 (2014-06-29)
* Enhancements
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
* [Kernel] Ensure compatibility with Erlang 17.1
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
* [Task] Set `:proc_lib` initial call on task to aid debugging
* [Typespec] Delay typespec compilation to after expansion
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
* Bug fixes
* [CLI] Support paths inside archives in `-pa` and `-pz` options
* [IEx] Remove delay when printing data from the application start callback
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
* [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/`
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
* Soft deprecations (no warnings emitted)
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
* [Kernel] `Kernel.xor/2` is deprecated
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
* Deprecations
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
* Backwards incompatible changes
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
* [System] `build_info/0` now returns a map
## v0.14.1 (2014-06-18)
* Enhancements
* [Base] Decoding and encoding functions now accept the `:case` as an option
* [ExUnit] The test process now exits with `:shutdown` reason
* [GenEvent] `GenEvent.stream/2` now accepts `:sync` and `:async` modes
* [Node] Add `Node.start/3` and `Node.stop/0`
* [String] Updated Unicode database to 7.0
* [Task] Log when tasks crash
* Bug fixes
* [Enum] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
* [Kernel] Disambiguate (w)erl to (w)erl.exe
* [Mix] Ensure umbrella project is recompiled when a dependency inside an umbrella child changes
* [OptionParser] Do not allow underscores in option names
* [Path] Fix path expansion of `"/.."`
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
* Deprecations
* [Mix] `:escript_*` options moved into a single `:escript` group
* Backwards incompatible changes
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
* [Kernel] Remove `get_in/1`
## v0.14.0 (2014-06-08)
* Enhancements
* [ExUnit] Add `on_exit/1` callbacks that are guaranteed to run once the test process exits and always in another process
* [Kernel] Store documentation in the abstract code to avoid loading them when the module is loaded
* [Kernel] Add `get_in/2`, `put_in/3`, `update_in/3` and `get_and_update_in/3` to handle nested data structure operations
* [Kernel] Add `get_in/1`, `put_in/2`, `update_in/2` and `get_and_update_in/2` to handle nested data structure operations via paths
* [Mix] Add `Mix.Config` to ease definition of configuration files
* [Mix] Add `mix loadconfig` task that can be called multiple times to load external configs
* [Mix] Support `--config` option on `mix run`
* [Mix] Support `HTTP_PROXY` and `HTTPS_PROXY` on Mix url commands
* [Mix] Support `--names` options in `mix help` which emit only names (useful for autocompletion)
* [Protocol] Add `Protocol.consolidate/2`, `Protocol.consolidated?/1` and a `mix compile.protocols` task for protocol consolidation
* [Protocol] Add `Protocol.derive/3` for runtime deriving of a struct
* [String] Add `String.chunk/2`
* [Struct] Add support for `@derive` before `defstruct/2` definitions
* Bug fixes
* [File] `File.rm` now consistently deletes read-only across operating systems
* [Kernel] Ensure Mix `_build` structure works on Windows when copying projects
* [Kernel] Ensure `1.0E10` (with uppercase E) is also valid syntax
* [Mix] Fix `mix do` task for Windows' powershell users
* [Path] Fix `Path.absname("/")` and `Path.expand("/")` to return the absolute path `"/"`.
* Soft deprecations (no warnings emitted)
* [Kernel] `size/1` is deprecated, please use `byte_size/1` or `tuple_size/1` instead
* [ExUnit] `teardown/2` and `teardown_all/2` are deprecated in favor of `on_exit/1` callbacks
* Deprecations
* [Access] `Access.access/2` is deprecated in favor of `Access.get/2`
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
* [Kernel] Passing an atom `var!/1` is deprecated, variables can be built dynamically with `Macro.var/2`
* [Mix] Exceptions that define a `:mix_error` field to be compatible with Mix are no longer supported. Instead please provide a `:mix` field and use `Mix.raise/1` and `Mix.raise/2`
* Backwards incompatible changes
* [Access] `Kernel.access/2` no longer exists and the `Access` protocol now requires `get/2` (instead of `access/2`) and `get_and_update/3` to be implemented
* [Kernel] Retrieving docs as `module.__info__(:docs)` is no longer supported, please use `Code.get_docs/2` instead
* [Kernel] `Code.compiler_options/1` no longer accepts custom options, only the ones specified by Elixir (use mix config instead)
* [Mix] `mix new` no longer generates a supevision tree by default, please pass `--sup` instead
* [Task] Tasks are automatically linked to callers and a failure in the task will crash the caller directly
## v0.13.3 (2014-05-24)
* Enhancements
* [OptionParser] Add `:strict` option that only parses known switches
@@ -46,7 +388,7 @@
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
# v0.13.2 (2014-05-11)
## v0.13.2 (2014-05-11)
* Enhancements
* [Application] Add an Application module with common functions to work with OTP applications
@@ -55,7 +397,7 @@
* [Mix] `mix deps.clean` now works accross environments
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
* [Mix] Support application configurations in `config/config.exs`
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
* [Mix] `mix help` now uses ANSI formatting to print guides
* [Regex] Support functions in `Regex.replace/4`
@@ -98,7 +440,7 @@
* [URI] The `URI.Info` record has now become the `URI` struct
* [Version] The `Version.Schema` record has now become the `Version` struct
# v0.13.1 (2014-04-27)
## v0.13.1 (2014-04-27)
* Enhancements
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
@@ -124,7 +466,7 @@
* [Path] Atoms are no longer supported in Path functions
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
# v0.13.0 (2014-04-20)
## v0.13.0 (2014-04-20)
* Enhancements
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
@@ -175,7 +517,7 @@
* Backwards incompatible changes
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
# v0.12.5 (2014-03-09)
## v0.12.5 (2014-03-09)
* Bug fixes
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
@@ -193,7 +535,7 @@
* Backwards incompatible changes
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
# v0.12.4 (2014-02-12)
## v0.12.4 (2014-02-12)
* Enhancements
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
@@ -207,7 +549,7 @@
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
# v0.12.3 (2014-02-02)
## v0.12.3 (2014-02-02)
* Enhancements
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
@@ -240,7 +582,7 @@
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
# v0.12.2 (2014-01-15)
## v0.12.2 (2014-01-15)
* Enhancements
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
@@ -269,7 +611,7 @@
* Backwards incompatible changes
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
# v0.12.1 (2014-01-04)
## v0.12.1 (2014-01-04)
* Enhancements
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
@@ -293,7 +635,7 @@
* [GenServer] GenServer now stops on unknown call/cast requests
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
# v0.12.0 (2013-12-15)
## v0.12.0 (2013-12-15)
* Enhancements
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
@@ -330,7 +672,7 @@
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
# v0.11.2 (2013-11-14)
## v0.11.2 (2013-11-14)
* Enhancements
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
@@ -339,7 +681,7 @@
* Backwards incompatible changes
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
# v0.11.1 (2013-11-07)
## v0.11.1 (2013-11-07)
* Enhancements
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
@@ -357,7 +699,7 @@
* Backwards incompatible changes
* [Mix] Setting `:load_paths` in your project configuration is deprecated
# v0.11.0 (2013-11-02)
## v0.11.0 (2013-11-02)
* Enhancements
* [Code] Eval now returns variables from other contexts
@@ -404,7 +746,7 @@
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
# v0.10.3 (2013-10-02)
## v0.10.3 (2013-10-02)
* Enhancements
* [Enum] Add `Enum.take_every/2`
@@ -439,7 +781,7 @@
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
# v0.10.2 (2013-09-03)
## v0.10.2 (2013-09-03)
* Enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
@@ -502,7 +844,7 @@
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
# v0.10.1 (2013-08-03)
## v0.10.1 (2013-08-03)
* Enhancements
* [Behaviour] Add support for `defmacrocallback/1`
@@ -552,7 +894,7 @@
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
# v0.10.0 (2013-07-15)
## v0.10.0 (2013-07-15)
* Enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
@@ -596,7 +938,7 @@
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
# v0.9.3 (2013-06-23)
## v0.9.3 (2013-06-23)
* Enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
@@ -634,7 +976,7 @@
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
* [Mix] No longer support beam files on `mix local`
# v0.9.2 (2013-06-13)
## v0.9.2 (2013-06-13)
* Enhancements
* [ExUnit] `capture_io` now captures prompt by default
@@ -659,7 +1001,7 @@
* Backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
## v0.9.1 (2013-05-30)
* Enhancements
* [IEx] Limit the number of entries kept in history and allow it to be configured
@@ -675,7 +1017,7 @@
* [Record] Fix a bug where nested records cannot be defined
* [Record] Fix a bug where a record named Record cannot be defined
# v0.9.0 (2013-05-23)
## v0.9.0 (2013-05-23)
* Enhancements
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
@@ -688,7 +1030,7 @@
* [Kernel] Erlang R15 is no longer supported
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
# v0.8.3 (2013-05-22)
## v0.8.3 (2013-05-22)
* Enhancements
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
@@ -740,7 +1082,7 @@
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
* [Mix] `mix escriptize` now receives arguments as binaries
# v0.8.2 (2013-04-20)
## v0.8.2 (2013-04-20)
* Enhancements
* [ExUnit] Use ANSI escape codes in CLI output
@@ -780,7 +1122,7 @@
unquote(Macro.escape(x))
end
# v0.8.1 (2013-02-17)
## v0.8.1 (2013-02-17)
* Enhancements
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
@@ -807,7 +1149,7 @@
* [Kernel] Old `:local.(args)` syntax is deprecated
* [Process] `Process.self` is deprecated in favor `Kernel.self`
# v0.8.0 (2013-01-28)
## v0.8.0 (2013-01-28)
* Enhancements
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
@@ -849,7 +1191,7 @@
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
# v0.7.2 (2012-12-04)
## v0.7.2 (2012-12-04)
* Enhancements
* [CLI] `--debug-info` is now true by default
@@ -875,7 +1217,7 @@
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
# v0.7.1 (2012-11-18)
## v0.7.1 (2012-11-18)
* Enhancements
* [IEx] Only show documented functions and also show docs for default generated functions
@@ -906,7 +1248,7 @@
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# v0.7.0 (2012-10-20)
## v0.7.0 (2012-10-20)
* Enhancements
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
@@ -955,7 +1297,7 @@
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
# v0.6.0 (2012-08-01)
## v0.6.0 (2012-08-01)
* Backwards incompatible changes
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
@@ -1005,6 +1347,6 @@
* [Regex] Back references are now properly supported
* [System] Add `System.find_executable`
# v0.5.0 (2012-05-24)
## v0.5.0 (2012-05-24)
* First official release
+3 -4
View File
@@ -10,7 +10,7 @@ Use the issues tracker for:
* [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful
We do our best to keep the issue tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing).
@@ -71,7 +71,7 @@ Please provide as much detail and context as possible.
## Contributing
We incentivate everyone to contribute to Elixir and help us tackle
We incentivize 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:
@@ -259,7 +259,6 @@ We have saved some excellent pull requests we have received in the past in case
you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
+51 -25
View File
@@ -1,4 +1,5 @@
REBAR := rebar
DOCS := v1.0
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -11,7 +12,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
.NOTPARALLEL: compile
#==> Functions
@@ -32,7 +33,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build
@@ -68,10 +69,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile EEx fully
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
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
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
@@ -86,10 +87,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ echo "Embedding the Unicode database... (this may take a while)"
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,logger,Logger))
$(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
@@ -101,7 +103,7 @@ install: compile
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
@@ -124,13 +126,16 @@ clean_exbeam:
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
rm -rf ebin
$(Q) rm -rf docs
$(call DOCS,Elixir,elixir,Kernel)
$(call DOCS,EEx,eex,EEx)
$(call DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit)
$(call DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -138,12 +143,22 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
release_docs: docs
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
rm -rf ../docs/$(DOCS)
mv docs ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
publish_mix: compile
cd lib/mix && MIX_ENV=prod mix escript.build
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
rm lib/mix/mix
rm -rf lib/mix/_build
#==> Tests tasks
@@ -162,7 +177,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
@@ -170,16 +185,27 @@ test_doc_test: compile
test_stdlib: compile
@ echo "==> elixir (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
$(Q) exec epmd & exit
$(Q) if [ "$(OS)" = "Windows_NT" ]; then \
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
else \
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
fi
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
#==> Dialyzer tasks
dialyze: .dialyzer.base_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
PLT = .elixir.plt
$(PLT):
@ echo "==> Building PLT with Elixir's dependencies..."
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
clean_plt:
$(Q) rm -f $(PLT)
build_plt: clean_plt $(PLT)
dialyze: compile $(PLT)
@ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
+3 -1
View File
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
$ cd elixir
$ make clean test
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
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.
@@ -37,7 +39,7 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
## Important links
* #elixir-lang on freenode IRC
* \#elixir-lang on freenode IRC
* [Website][1]
* [Issue tracker][2]
* [elixir-talk Mailing list (questions)][3]
+28 -14
View File
@@ -1,29 +1,43 @@
## Release process
# Release process
## All releases
This document simply outlines the release process:
1) Remove `-dev` extension from VERSION
1. Ensure you are running on the oldest supported Erlang version
2) Ensure CHANGELOG is updated and timestamp
2. Remove all `-dev` extension from versions (see below for all files)
3) Commit changes above with title "Release vVERSION" and generate new tag
3. Ensure CHANGELOG is updated and add current date
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
4. Commit changes above with title "Release vVERSION" and generate new tag
5) Push master and tags
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
6) Release new docs with `make release_docs`, move docs to `docs/stable`
6. Ensure minimum supported Hex works with new release (instructions upcoming)
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
7. Push master and the new tag
8) Merge master into stable branch and push it
8. Release new docs with `make release_docs`, move docs to `docs/stable` if appropriate, and push
9) After release, bump versions, add `-dev` back and commit
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
10) `make release_docs` once again and push it to `elixir-lang.github.com`
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
## New vMAJOR.MINOR releases
11. Create a new branch "vMAJOR.MINOR"
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
14. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
* VERSION
* CHANGELOG
* src/elixir.app.src
* VERSION (make sure there is no newline in this file)
* CHANGELOG.md
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
+1 -1
View File
@@ -1 +1 @@
0.13.3
1.0.2
+9 -6
View File
@@ -17,7 +17,6 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
--gen-debug Turns on default debugging for all GenServers
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
--gen-debug)
ERL="$ERL -generic_debug"
;;
*)
break
;;
@@ -84,7 +80,14 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
# Check for terminal support
if [ "$OS" != "Windows_NT" ]; then
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
fi
if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
@@ -94,4 +97,4 @@ if [ -z "$ERL_PATH" ]; then
fi
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
+31 -22
View File
@@ -1,4 +1,5 @@
@echo off
setlocal
if "%1"=="" goto documentation
if "%1"=="--help" goto documentation
if "%1"=="-h" goto documentation
@@ -22,13 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --werl Uses Erlang's Windows shell GUI
echo --no-halt Does not halt the Erlang VM after execution
echo --gen-debug Turns on default debugging for all GenServers
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF
goto end
:parseopts
@@ -47,6 +48,9 @@ set beforeExtra=
rem Flag which determines whether or not to use werl vs erl
set useWerl=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
@@ -60,24 +64,24 @@ if "%par%"=="""" (
goto :expand_erl_libs
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
IF "%par%"==""--werl"" (Set useWerl=1)
IF "%par%"==""+iex"" (Set runMode="iex")
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
IF """"=="%par:-e=%" (shift)
IF """"=="%par:-r=%" (shift)
IF """"=="%par:-pr=%" (shift)
IF """"=="%par:-pa=%" (shift)
IF """"=="%par:-pz=%" (shift)
IF """"=="%par:--app=%" (shift)
IF """"=="%par:--remsh=%" (shift)
rem ******* ERLANG PARAMETERS **********************
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
goto:startloop
rem ******* assume all pre-params are parsed ********************
@@ -90,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
IF NOT %runMode% == "iex" (
set beforeExtra=-s elixir start_cli %beforeExtra%
)
IF %useWerl% EQU 1 (
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
)
:end
endlocal
+1 -1
View File
@@ -11,7 +11,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
exit 1
fi
+1 -1
View File
@@ -21,6 +21,6 @@ echo --verbose Print informational messages.
echo.
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS >&2
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
:run
call "%~dp0\elixir.bat" +elixirc %*
+3 -2
View File
@@ -16,7 +16,6 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--gen-debug Turns on default debugging for all GenServers
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
@@ -39,4 +38,6 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env elixir
# Reminder: apply any changes in this file to bin\mix.bat
Mix.start
Mix.CLI.main
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
+23
View File
@@ -0,0 +1,23 @@
# Store path to mix.bat as a FileInfo object
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
$newArgs = @()
for ($i = 0; $i -lt $args.length; $i++)
{
if ($args[$i] -is [array])
{
# Commas created the array so we need to reintroduce those commas
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
{
$newArgs += ($args[$i][$j] + ',')
}
$newArgs += $args[$i][-1]
}
else
{
$newArgs += $args[$i]
}
}
# Corrected arguments are ready to pass to batch file
& $mixBatPath $newArgs
+25 -20
View File
@@ -1,5 +1,9 @@
defmodule EEx.SyntaxError do
defexception [:message]
defexception [:message, :file, :line]
def message(exception) do
"#{exception.file}:#{exception.line}: #{exception.message}"
end
end
defmodule EEx do
@@ -14,32 +18,30 @@ defmodule EEx do
This module provides 3 main APIs for you to use:
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
directly. This is the simplest API to use but also the
slowest, since the code is evaluated and not compiled before;
1. Evaluate a string (`eval_string`) or a file (`eval_file`)
directly. This is the simplest API to use but also the
slowest, since the code is evaluated and not compiled before.
2) Define a function from a string (`function_from_string`)
or a file (`function_from_file`). This allows you to embed
the template as a function inside a module which will then
be compiled. This is the preferred API if you have access
to the template at compilation time;
2. Define a function from a string (`function_from_string`)
or a file (`function_from_file`). This allows you to embed
the template as a function inside a module which will then
be compiled. This is the preferred API if you have access
to the template at compilation time.
3) Compile a string (`compile_string`) or a file (`compile_file`)
into Elixir syntax tree. This is the API used by both functions
above and is available to you if you want to provide your own
ways of handling the compiled template.
3. Compile a string (`compile_string`) or a file (`compile_file`)
into Elixir syntax tree. This is the API used by both functions
above and is available to you if you want to provide your own
ways of handling the compiled template.
## Options
All functions in this module accepts EEx-related options.
They are:
* `:line` - the line to be used as the template start.
Defaults to 1;
* `:file` - the file to be used in the template.
Defaults to the given file the template is read from
or to "nofile" when compiling from a string;
* `:engine` - the EEx engine to be used for compilation.
* `:line` - the line to be used as the template start. Defaults to 1.
* `:file` - the file to be used in the template. Defaults to the given
file the template is read from or to "nofile" when compiling from a string.
* `:engine` - the EEx engine to be used for compilation.
## Engine
@@ -83,7 +85,7 @@ defmodule EEx do
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
"1"
In other words, <%= @foo %> is simply translated to:
In other words, `<%= @foo %>` is simply translated to:
<%= Dict.get assigns, :foo %>
@@ -93,6 +95,7 @@ defmodule EEx do
@doc """
Generates a function definition from the string.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
@@ -121,6 +124,7 @@ defmodule EEx do
@doc """
Generates a function definition from the file contents.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
@@ -148,6 +152,7 @@ defmodule EEx do
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))
+14 -9
View File
@@ -1,7 +1,7 @@
defmodule EEx.Compiler do
@moduledoc false
# when changing this setting, don't forget to update the docs for EEx
# When changing this setting, don't forget to update the docs for EEx
@default_engine EEx.SmartEngine
@doc """
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
case EEx.Tokenizer.tokenize(source, line) do
{:ok, tokens} ->
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
{:error, line, message} ->
raise EEx.SyntaxError, line: line, file: file, message: message
end
end
# Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t}
end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% 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
end
# Creates a placeholder and wrap it inside the expression block
+59 -16
View File
@@ -1,27 +1,28 @@
defmodule EEx.Engine do
@moduledoc ~S"""
This is the basic EEx engine that ships with Elixir.
Basic EEx engine that ships with Elixir.
An engine needs to implement three functions:
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression;
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression.
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression;
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression.
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
the marker, the expr and must return a new quoted expression;
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
the marker, the expr and must return a new quoted expression.
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:
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: `""` and `"="`.
* `""`
* `"="`
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
`EEx.Engine` can be used directly if one desires to use the
default implementations for the functions above.
"""
use Behaviour
@@ -30,6 +31,48 @@ defmodule EEx.Engine do
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, expr)
end
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
@doc """
Handles assigns in quoted expressions.
This can be added to any custom engine by invoking
`handle_assign/3` with `Macro.prewalk/1`:
def handle_expr(buffer, token, expr) do
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
EEx.Engine.handle_expr(buffer, token, expr)
end
"""
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote line: line, do: Dict.get(var!(assigns), unquote(name))
end
def handle_assign(arg) do
arg
end
@doc """
The default implementation implementation simply returns the
given expression.
@@ -56,7 +99,7 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> to_string(unquote(expr))
tmp <> String.Chars.to_string(unquote(expr))
end
end
+8 -83
View File
@@ -1,68 +1,12 @@
defmodule EEx.TransformerEngine do
defmodule EEx.SmartEngine do
@moduledoc """
An abstract engine that is meant to be used and
built upon in other modules. This engine implements
the `EEx.Engine` behaviour and provides a `transform`
overridable directive that allows a developer to
customize the expression returned by the engine.
The default engine used by EEx.
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
examples of using this module.
"""
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, transform(expr))
end
defp transform({a, b, c}) do
{transform(a), b, transform(c)}
end
defp transform({a, b}) do
{transform(a), transform(b)}
end
defp transform(list) when is_list(list) do
for i <- list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
end
defmodule EEx.AssignsEngine do
@moduledoc """
An abstract engine that, when used with the
`TransformerEngine`, allows a developer to access
assigns using `@` as syntax.
This engine is included by default on the SmartEngine.
It includes assigns (like `@foo`) and possibly other
conveniences in the future.
## Examples
defmodule MyEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
end
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
"1"
@@ -88,29 +32,10 @@ defmodule EEx.AssignsEngine do
"""
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
use EEx.Engine
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
end
def handle_expr(buffer, mark, expr) do
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
super(buffer, mark, expr)
end
end
defmodule EEx.SmartEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
@moduledoc """
An engine meant for end-user usage that includes
`EEx.AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
+36 -25
View File
@@ -3,41 +3,52 @@ defmodule EEx.Tokenizer do
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* {:text, contents}
* {:expr, line, marker, contents}
* {:start_expr, line, marker, contents}
* {:middle_expr, line, marker, contents}
* {:end_expr, line, marker, contents}
It returns {:ok, list} with the following tokens:
* `{:text, contents}`
* `{:expr, line, marker, contents}`
* `{:start_expr, line, marker, contents}`
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
Or `{:error, line, error}` in case of errors.
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], []))
tokenize(list, line, [], [])
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
case expr(t, line, [?%, ?<|buffer]) do
{:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, expr, new_line, rest} ->
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end
defp tokenize('\n' ++ t, line, buffer, acc) do
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
end
# Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{buffer, line, t}
defp expr([?%, ?>|t], line, buffer) do
{:ok, buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
defp expr('\n' ++ t, line, buffer) do
expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
defp expr([h|t], line, buffer) do
expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
defp expr([], line, _buffer) do
{:error, line, "missing token '%>'"}
end
# Receive an expression content and check
+10
View File
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end
test "compiled preserved 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)
end
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ {: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) == [ {: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) == [ {: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
@@ -32,50 +35,50 @@ baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, '\n'}
]
]}
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
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) == [
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
{:text, 'foo <% true do %>bar<% end %>'}
]
]}
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '}
]
]}
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
{:text, 'foo bar'}
]
]}
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
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 '}
]
]}
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
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 '}
]
]}
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
end
end
+31 -20
View File
@@ -2,7 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
require EEx
defmodule EExText.Compiled do
defmodule EExTest.Compiled do
def before_compile do
fill_in_stacktrace
{__ENV__.line, hd(tl(System.stacktrace))}
@@ -12,6 +12,10 @@ defmodule EExText.Compiled do
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
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]
def file_sample(arg), do: private_file_sample(arg)
def after_compile do
@@ -46,7 +50,8 @@ defmodule EExTest do
use ExUnit.Case, async: true
doctest EEx
doctest EEx.AssignsEngine
doctest EEx.Engine
doctest EEx.SmartEngine
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
@@ -106,25 +111,25 @@ defmodule EExTest do
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% 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 "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
test "raises a syntax error when nested end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
@@ -319,39 +324,45 @@ foo
end
end
test "sets external resource attribute" do
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
end
test "defined from string" do
assert EExText.Compiled.string_sample(1, 2) == "3"
assert EExTest.Compiled.string_sample(1, 2) == "3"
end
test "defined from file" do
assert EExText.Compiled.file_sample(1) == "foo 1\n"
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
end
test "defined from file do not affect backtrace" do
assert EExText.Compiled.before_compile ==
assert EExTest.Compiled.before_compile ==
{8,
{EExText.Compiled,
{EExTest.Compiled,
:before_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
}
}
assert EExText.Compiled.after_compile ==
{19,
{EExText.Compiled,
assert EExTest.Compiled.after_compile ==
{23,
{EExTest.Compiled,
:after_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
}
}
assert EExText.Compiled.unknown ==
{25,
{EExText.Compiled,
assert EExTest.Compiled.unknown ==
{29,
{EExTest.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
[file: 'unknown', line: 28]
}
}
end
+103 -20
View File
@@ -1,13 +1,14 @@
import Kernel, except: [access: 2]
defprotocol Access do
@moduledoc """
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes the `Access.access` protocol.
The Access protocol is used by `foo[bar]` and also
empowers the nested update functions in Kernel.
This protocol is implemented by default for Lists, Maps
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
`Kernel.get_and_update_in/3` are also all powered by the Access
protocol.
This protocol is implemented by default for keywords, maps
and dictionary like types:
iex> keywords = [a: 1, b: 2]
@@ -22,46 +23,128 @@ defprotocol Access do
iex> star_ratings[1.5]
"★☆"
The key access must be implemented using the `===` operator.
This protocol is limited and is implemented only for the
following built-in types: keywords, records and functions.
The key comparison must be implemented using the `===` operator.
"""
@doc """
Receives the element being accessed and the access item.
Accesses the given key in the container.
"""
def access(container, key)
@spec get(t, term) :: t
def get(container, key)
@doc """
Gets a value and updates the given `key` in one pass.
The function must receive the value for the given `key`
(or `nil` if the key doesn't exist in `container`) and
the function must return a tuple containing the `get`
value and the new value to be stored in the `container`.
"""
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
def get_and_update(container, key, fun)
end
defimpl Access, for: List do
def access(dict, key) when is_atom(key) do
def get(dict, key) when is_atom(key) do
case :lists.keyfind(key, 1, dict) do
{^key, value} -> value
false -> nil
end
end
def access(_dict, key) do
raise ArgumentError, "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
def get(_dict, key) do
raise ArgumentError,
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
end
def get_and_update(dict, key, fun) when is_atom(key) do
get_and_update(dict, [], key, fun)
end
defp get_and_update([{key, value}|t], acc, key, fun) do
{get, update} = fun.(value)
{get, :lists.reverse(acc, [{key, update}|t])}
end
defp get_and_update([h|t], acc, key, fun) do
get_and_update(t, [h|acc], key, fun)
end
defp get_and_update([], acc, key, fun) do
{get, update} = fun.(nil)
{get, [{key, update}|:lists.reverse(acc)]}
end
end
defimpl Access, for: Map do
def access(map, key) do
def get(map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
end
def get_and_update(map, key, fun) do
value =
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
end
def get!(%{} = map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: map
end
end
def get!(other, key) do
raise ArgumentError,
"could not get key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
def get_and_update!(%{} = map, key, fun) do
case :maps.find(key, map) do
{:ok, value} ->
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
:error ->
raise KeyError, key: key, term: map
end
end
def get_and_update!(other, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
end
defimpl Access, for: Atom do
def access(nil, _) do
def get(nil, _) do
nil
end
def access(atom, _) do
raise "The access protocol can only be invoked for atoms at " <>
"compilation time, tried to invoke it for #{inspect atom}"
def get(atom, _) do
undefined(atom)
end
def get_and_update(nil, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end
def get_and_update(atom, _key, _fun) do
undefined(atom)
end
defp undefined(atom) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: atom,
description: "only the nil atom is supported"
end
end
+102 -29
View File
@@ -3,8 +3,8 @@ defmodule Agent do
Agents are a simple abstraction around state.
Often in Elixir there is a need to share or store state that
must be accessed from different processes or by a same process
in different points in time.
must be accessed from different processes or by the same process
at different points in time.
The Agent module provides a basic server implementation that
allows state to be retrieved and updated via a simple API.
@@ -35,12 +35,12 @@ defmodule Agent do
end
end
Note that agents still provide a segregation in between the
Note that agents still provide a segregation between the
client and server APIs, as seen in GenServers. In particular,
all code inside the function passed to the agent is executed
by the agent. This distinction is important because you may
want to avoid expensive operations inside the agent, as it will
effectively block the agent until the request is fullfilled.
effectively block the agent until the request is fulfilled.
Consider these two examples:
@@ -60,7 +60,7 @@ defmodule Agent do
sent to the client cheaply or large enough to require processing on
the server (or at least some initial processing).
## Name registration
## Name Registration
An Agent is bound to the same name registration rules as GenServers.
Read more about it in the `GenServer` docs.
@@ -68,22 +68,22 @@ defmodule Agent do
## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions in between the caller and the agent.
In a distributed setup with multiple nodes, agents only work if the caller
(client) and the agent have the same version of a given module.
provides two APIs, one that works with anonymous functions and another
that expects explicit module, function and arguments.
This setup may exhibit issues when doing "rolling upgrades". By rolling
upgrades we mean the following situation: you wish to deploy a new version of
your software by *shutting down* some of your nodes and replacing them by
nodes running a new version of the software. In this setup, part of your
environment will have one version of a given module and the other part
another version (the newer one) of the same module; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
In a distributed setup with multiple nodes, the API that accepts anonymous
functions only works if the caller (client) and the agent have the same
version of the caller module.
Note, however, that agents work fine if you want to perform hot code
swapping, as it keeps both the old and new versions of a given module.
We detail how to do hot code swapping with agents in the next section.
Keep in mind this issue also shows up when performing "rolling upgrades"
with agents. By rolling upgrades we mean the following situation: you wish
to deploy a new version of your software by *shutting down* some of your
nodes and replacing them with nodes running a new version of the software.
In this setup, part of your environment will have one version of a given
module and the other part another version (the newer one) of the same module.
The best solution is to simply use the explicit module, function and arguments
APIs when working with distributed agents.
## Hot code swapping
@@ -111,7 +111,7 @@ defmodule Agent do
@type state :: term
@doc """
Starts an agent linked to the current process.
Starts an agent linked to the current process with the given function.
This is often used to start the agent as part of a supervision tree.
@@ -122,20 +122,22 @@ defmodule Agent do
## Options
The `:name` option is used for registration as described in the module
documentation. If the `:timeout` option is present, the agent is allowed to
spend at most the given amount of milliseconds on initialization or it will
be terminated and the start function will return `{:error, :timeout}`.
documentation.
If the `:timeout` option is present, the agent is allowed to spend at most
the given number of milliseconds on initialization or it will be terminated
and the start function will return `{:error, :timeout}`.
If the `:debug` option is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the `:spawn_opt` option is present, its value will be passed as options
to the underlying process as in `Process.spawn/3`.
to the underlying process as in `Process.spawn/4`.
## Return values
If the server is successfully created and initialized, the function returns
`{:ok, pid}`, where pid is the pid of the server. If there already exists
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
an agent with the specified name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
@@ -147,6 +149,18 @@ defmodule Agent do
GenServer.start_link(Agent.Server, fun, options)
end
@doc """
Starts an agent linked to the current process with the given module
function and arguments.
Same as `start_link/2` but a module, function and args are expected
instead of an anonymous function.
"""
@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
@doc """
Starts an agent process without links (outside of a supervision tree).
@@ -158,7 +172,18 @@ defmodule Agent do
end
@doc """
Gets the agent value and executes the given function.
Starts an agent with the given module function and arguments.
Similar to `start/2` but a module, function and args are expected
instead of an anonymous function.
"""
@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
@doc """
Gets an agent value via the given function.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The result of the function invocation is
@@ -171,12 +196,24 @@ defmodule Agent do
GenServer.call(agent, {:get, fun}, timeout)
end
@doc """
Gets an agent value via the given function.
Same as `get/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get(agent, module, atom, [term], timeout) :: any
def get(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
end
@doc """
Gets and updates the agent state in one operation.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return a tuple with two
elements, the first being the value to return (i.e. the get value)
elements, the first being the value to return (i.e. the `get` value)
and the second one is the new state.
A timeout can also be specified (it has a default value of 5000).
@@ -186,6 +223,18 @@ defmodule Agent do
GenServer.call(agent, {:get_and_update, fun}, timeout)
end
@doc """
Gets and updates the agent state in one operation.
Same as `get_and_update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get_and_update(agent, module, atom, [term], timeout) :: any
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
end
@doc """
Updates the agent state.
@@ -195,11 +244,23 @@ defmodule Agent do
A timeout can also be specified (it has a default value of 5000).
This function always returns `:ok`.
"""
@spec update(agent, (state -> state)) :: :ok
@spec update(agent, (state -> state), timeout) :: :ok
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:update, fun}, timeout)
end
@doc """
Updates the agent state.
Same as `update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec update(agent, module, atom, [term], timeout) :: :ok
def update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
@@ -211,7 +272,19 @@ defmodule Agent do
"""
@spec cast(agent, (state -> state)) :: :ok
def cast(agent, fun) when is_function(fun, 1) do
GenServer.cast(agent, fun)
GenServer.cast(agent, {:cast, fun})
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
Same as `cast/2` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec cast(agent, module, atom, [term]) :: :ok
def cast(agent, module, fun, args) do
GenServer.cast(agent, {:cast, {module, fun, args}})
end
@doc """
+12 -9
View File
@@ -4,20 +4,20 @@ defmodule Agent.Server do
use GenServer
def init(fun) do
{:ok, fun.()}
{:ok, run(fun, [])}
end
def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state}
{:reply, run(fun, [state]), state}
end
def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state)
{reply, state} = run(fun, [state])
{:reply, reply, state}
end
def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)}
{:reply, :ok, run(fun, [state])}
end
def handle_call(:stop, _from, state) do
@@ -28,26 +28,29 @@ defmodule Agent.Server do
super(msg, from, state)
end
def handle_cast(fun, state) when is_function(fun, 1) do
{:noreply, fun.(state)}
def handle_cast({:cast, fun}, state) do
{:noreply, run(fun, [state])}
end
def handle_cast(msg, state) do
super(msg, state)
end
def code_change(_old, state, { m, f, a }) do
{:ok, apply(m, f, [state|a])}
def code_change(_old, state, fun) do
{:ok, run(fun, [state])}
end
def terminate(_reason, _state) do
# There is a race condition if the agent is
# restarted too fast and it is registered.
try do
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
rescue
_ -> :ok
end
:ok
end
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
defp run(fun, extra), do: apply(fun, extra)
end
+18 -16
View File
@@ -13,7 +13,7 @@ defmodule Application do
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 stoping your application
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.
@@ -48,7 +48,7 @@ defmodule Application do
developers to configure the environment of their dependencies.
Keep in mind that each application is responsible for its environment.
Do not use the fucntions in this module for directly access or modify
Do not use the functions in this module for directly access or modify
the environment of other application (as it may lead to inconsistent
data in the application environment).
@@ -83,8 +83,8 @@ defmodule Application do
setup where applications takeover and failovers are configured. This particular
aspect of applications can be read with more detail in the OTP documentation:
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://www.erlang.org/doc/man/application.html
* 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
@@ -152,9 +152,8 @@ defmodule Application do
## Options
* `:timeout` - the timeout for the change (defaults to 5000ms);
* `:persistent` - persists the given value on application load and reloads;
* `:timeout` - the timeout for the change (defaults to 5000ms)
* `:persistent` - persists the given value on application load and reloads
If `put_env/4` is called before the application is loaded, the application
environment values specified in the `.app` file will override the ones
@@ -202,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
@@ -223,13 +222,16 @@ defmodule Application do
The `type` argument specifies the type of the application:
* `:permanent` - if `app` terminates, all other applications and the entire
node are also terminated;
* `:transient` - if `app` terminates with `:normal` reason, it is reported
but no other applications are terminated. If a transient application terminates
abnormally, all other applications and the entire node are also terminated;
* `:temporary` - if `app` termiantes, it is reported but no other applications
are terminated (the default);
* `:permanent` - if `app` terminates, all other applications and the entire
node are also terminated.
* `:transient` - if `app` terminates with `:normal` reason, it is reported
but no other applications are terminated. If a transient application
terminates abnormally, all other applications and the entire node are
also terminated.
* `:temporary` - if `app` terminates, it is reported but no other
applications are terminated (the default).
Note that it is always possible to stop an application explicitly by calling
`stop/1`. Regardless of the type of the application, no other applications will
@@ -320,7 +322,7 @@ defmodule Application do
@doc """
Formats the error reason returned by `start/2`,
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
returns a string.
"""
@spec format_error(any) :: String.t
-16
View File
@@ -1,16 +0,0 @@
defmodule Application.Behaviour do
@moduledoc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :application
@doc false
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
end
+230 -102
View File
@@ -5,8 +5,91 @@ defmodule Base do
This module provides data encoding and decoding functions
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
This document defines the commonly used base 64, base 32, and base
16 encoding schemes.
This document defines the commonly used base 16, base 32, and base
64 encoding schemes.
## Base 16 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
## Base 32 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
## Base 32 (extended hex) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
## Base 64 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Base 64 (URL and filename safe) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
"""
b16_alphabet = Enum.with_index '0123456789ABCDEF'
@@ -29,41 +112,82 @@ defmodule Base do
end
end
defp encode_case(:upper, func),
do: func
defp encode_case(:lower, func),
do: &to_lower(func.(&1))
defp decode_case(:upper, func),
do: func
defp decode_case(:lower, func),
do: &func.(from_lower(&1))
defp decode_case(:mixed, func),
do: &func.(from_mixed(&1))
defp to_lower(char) when char in ?A..?Z,
do: char + (?a - ?A)
defp to_lower(char),
do: char
defp from_lower(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_lower(char) when not char in ?A..?Z,
do: char
defp from_lower(char),
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
defp from_mixed(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_mixed(char),
do: char
@doc """
Encodes a binary string into a base 16 encoded string.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.encode16("foobar")
"666F6F626172"
iex> Base.encode16("foobar", case: :lower)
"666f6f626172"
"""
@spec encode16(binary) :: binary
def encode16(data) when is_binary(data) do
do_encode16(data, &enc16/1)
@spec encode16(binary, Keyword.t) :: binary
def encode16(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode16(data, encode_case(case, &enc16/1))
end
@doc """
Decodes a base 16 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.decode16("666F6F626172")
{:ok, "foobar"}
iex> Base.decode16("666f6f626172", case: :lower)
{:ok, "foobar"}
iex> Base.decode16("666f6F626172", case: :mixed)
{:ok, "foobar"}
"""
@spec decode16(binary) :: {:ok, binary} | :error
def decode16(string) when is_binary(string) do
{:ok, decode16!(string)}
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
def decode16(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
rescue
ArgumentError -> :error
end
@@ -71,6 +195,10 @@ defmodule Base do
@doc """
Decodes a base 16 encoded string into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -79,10 +207,18 @@ defmodule Base do
iex> Base.decode16!("666F6F626172")
"foobar"
iex> Base.decode16!("666f6f626172", case: :lower)
"foobar"
iex> Base.decode16!("666f6F626172", case: :mixed)
"foobar"
"""
@spec decode16!(binary) :: binary
def decode16!(string) when is_binary(string) do
do_decode16(string, &dec16/1)
@spec decode16!(binary, Keyword.t) :: binary
def decode16!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode16(string, decode_case(case, &dec16/1))
end
@doc """
@@ -102,28 +238,6 @@ defmodule Base do
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.decode64("Zm9vYmFy")
@@ -175,28 +289,6 @@ defmodule Base do
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.url_decode64("_3_-_A==")
@@ -231,43 +323,49 @@ defmodule Base do
@doc """
Encodes a binary string into a base 32 encoded string.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.encode32("foobar")
"MZXW6YTBOI======"
iex> Base.encode32("foobar", case: :lower)
"mzxw6ytboi======"
"""
@spec encode32(binary) :: binary
def encode32(data) when is_binary(data) do
do_encode32(data, &enc32/1)
@spec encode32(binary, Keyword.t) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32/1))
end
@doc """
Decodes a base 32 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.decode32("MZXW6YTBOI======")
{:ok, "foobar"}
iex> Base.decode32("mzxw6ytboi======", case: :lower)
{:ok, "foobar"}
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
{:ok, "foobar"}
"""
@spec decode32(binary) :: {:ok, binary} | :error
def decode32(string) do
{:ok, do_decode32(string, &dec32/1)}
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
rescue
ArgumentError -> :error
end
@@ -275,6 +373,10 @@ defmodule Base do
@doc """
Decodes a base 32 encoded string into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -283,54 +385,68 @@ defmodule Base do
iex> Base.decode32!("MZXW6YTBOI======")
"foobar"
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
"foobar"
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
"foobar"
"""
@spec decode32!(binary) :: binary
def decode32!(string) do
do_decode32(string, &dec32/1)
@spec decode32!(binary, Keyword.t) :: binary
def decode32!(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32/1))
end
@doc """
Encodes a binary string into a base 32 encoded string with an
extended hexadecimal alphabet.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.hex_encode32("foobar")
"CPNMUOJ1E8======"
iex> Base.hex_encode32("foobar", case: :lower)
"cpnmuoj1e8======"
"""
@spec hex_encode32(binary) :: binary
def hex_encode32(data) when is_binary(data) do
do_encode32(data, &enc32hex/1)
@spec hex_encode32(binary, Keyword.t) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32hex/1))
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
{:ok, "foobar"}
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
{:ok, "foobar"}
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
def hex_decode32(string) when is_binary(string) do
{:ok, do_decode32(string, &dec32hex/1)}
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
rescue
ArgumentError -> :error
end
@@ -339,6 +455,10 @@ defmodule Base do
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -347,10 +467,18 @@ defmodule Base do
iex> Base.hex_decode32!("CPNMUOJ1E8======")
"foobar"
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
"foobar"
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
"foobar"
"""
@spec hex_decode32!(binary) :: binary
def hex_decode32!(string) when is_binary(string) do
do_decode32(string, &dec32hex/1)
@spec hex_decode32!(binary, Keyword.t) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32hex/1))
end
defp do_encode16(<<>>, _), do: <<>>
@@ -371,7 +499,7 @@ defmodule Base do
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3)
<<main::[size(split), binary], rest::binary>> = data
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::6, c2::6, c3::4>> ->
@@ -386,7 +514,7 @@ defmodule Base do
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::[size(split), binary], rest::binary>> = string
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
@@ -406,7 +534,7 @@ defmodule Base do
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5)
<<main::[size(split), binary], rest::binary>> = data
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
@@ -433,7 +561,7 @@ defmodule Base do
defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::[size(split), binary], rest::binary>> = string
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
+2 -2
View File
@@ -24,8 +24,8 @@ defmodule Bitwise do
Allow a developer to use this module in their programs with
the following options:
* `:only_operators` - Include only operators;
* `:skip_operators` - Skip operators;
* `:only_operators` - include only operators
* `:skip_operators` - skip operators
"""
defmacro __using__(options) do
+116 -27
View File
@@ -51,6 +51,23 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
:code.add_pathsz(pz)
:ok
end
@doc """
Evaluate the contents given by `string`.
@@ -59,21 +76,24 @@ defmodule Code do
Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Additionally, the following scope values can be configured:
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity. The list
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity; the list
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity; the list
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
@@ -198,13 +218,13 @@ defmodule Code do
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the `__ENV__` variable.
* `:file` - the filename to be used in stacktraces
and the file reported in the `__ENV__` variable
* `:line` - The line reported in the `__ENV__` variable.
* `:line` - the line reported in the `__ENV__` variable
* `:existing_atoms_only` - When `true`, raises an error
when non-existing atoms are found by the tokenizer.
* `:existing_atoms_only` - when `true`, raises an error
when non-existing atoms are found by the tokenizer
## Macro.to_string/2
@@ -240,12 +260,12 @@ defmodule Code do
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
byte code, `eval_file` simply evaluates the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
file = find_file(file, relative_to)
eval_string File.read!(file), [], []
eval_string File.read!(file), [], [file: file, line: 1]
end
@doc """
@@ -308,6 +328,15 @@ defmodule Code do
:elixir_code_server.call :compiler_options
end
@doc """
Returns a list with the available compiler options.
See `Code.compiler_options/1` for more info.
"""
def available_compiler_options do
[:docs, :debug_info, :ignore_module_conflict, :warnings_as_errors]
end
@doc """
Sets compilation options.
@@ -315,20 +344,26 @@ defmodule Code do
Available options are:
* `:docs` - when `true`, retain documentation in the compiled module,
`true` by default;
* `: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, `false` by default;
* `:debug_info` - when `true`, retain debug information in the compiled
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, `false` by default;
* `:ignore_module_conflict` - when `true`, override modules that were
already defined without raising errors, `false` by default
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
* `:warnings_as_errors` - cause compilation to fail when warnings are
generated
"""
def compiler_options(opts) do
{opts, bad} = Keyword.split(opts, available_compiler_options)
if bad != [] do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
end
@@ -448,6 +483,60 @@ defmodule Code do
match?({:module, ^module}, ensure_compiled(module))
end
@doc """
Returns the docs for the given module.
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
file.
The return value depends on the `kind` value:
* `:docs` - list of all docstrings attached to functions and macros
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
* `:all` - a keyword list with both `:docs` and `:moduledoc`
"""
def get_docs(module, kind) when is_atom(module) do
case :code.get_object_code(module) do
{_module, bin, _beam_path} ->
do_get_docs(bin, kind)
:error -> nil
end
end
def get_docs(binpath, kind) when is_binary(binpath) do
do_get_docs(String.to_char_list(binpath), kind)
end
@docs_chunk 'ExDc'
defp do_get_docs(bin_or_path, kind) do
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
{:ok, {_module, [{@docs_chunk, bin}]}} ->
lookup_docs(:erlang.binary_to_term(bin), kind)
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
end
end
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) when kind in [:docs, :moduledoc],
do: Keyword.get(docs, kind)
## Helpers
# Finds the file given the relative_to path.
+5 -45
View File
@@ -8,12 +8,6 @@ defprotocol Collectable do
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
If a collection implements both `Enumerable` and `Collectable`, both
operations can be combined with `Enum.traverse/2`:
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
## Why Collectable?
The `Enumerable` protocol is useful to take values out of a collection.
@@ -27,25 +21,13 @@ defprotocol Collectable do
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`empty/1` receives a collectable and returns an empty version of the
same collectable. By combining the enumerable functionality with `into/1`
and `empty/1`, one can, for example, implement a traversal mechanism.
`Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`Collectable.into/1` is about collecting those values into a structure.
"""
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
@@ -65,10 +47,6 @@ defprotocol Collectable do
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{[], fn
list, {:cont, x} -> [x|list]
@@ -79,34 +57,16 @@ defimpl Collectable, for: List do
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
acc, :done -> IO.iodata_to_binary(acc)
_, :halt -> :ok
end}
end
end
defimpl Collectable, for: Function do
def empty(function) do
function
end
def into(function) do
{function, function}
end
end
defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
+102 -102
View File
@@ -40,7 +40,7 @@ defmodule Dict do
For example:
defmodule MyDict do
use Dict.Behaviour
use Dict
# implement required functions (see below)
# override default implementations if optimization
@@ -49,11 +49,11 @@ defmodule Dict do
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
@@ -61,24 +61,24 @@ defmodule Dict do
Based on these functions, `Dict` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `update/4`
* `update!/3`
* `values/1`
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `update/4`
* `update!/3`
* `values/1`
All of these functions are defined as overridable, so you can provide
your own implementation if needed.
@@ -288,8 +288,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d))
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(dict))
[:a,:b]
"""
@@ -304,8 +304,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d))
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(dict))
[1,2]
"""
@@ -319,8 +319,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(dict)
2
"""
@@ -334,10 +334,10 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(dict, :a)
true
iex> Dict.has_key?(d, :b)
iex> Dict.has_key?(dict, :b)
false
"""
@@ -352,12 +352,12 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(dict, :a)
1
iex> Dict.get(d, :b)
iex> Dict.get(dict, :b)
nil
iex> Dict.get(d, :b, 3)
iex> Dict.get(dict, :b, 3)
3
"""
@spec get(t, key, value) :: value
@@ -371,10 +371,10 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(dict, :a)
{:ok, 1}
iex> Dict.fetch(d, :b)
iex> Dict.fetch(dict, :b)
:error
"""
@@ -389,8 +389,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(dict, :a)
1
"""
@@ -405,9 +405,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put(dict, :a, 3)
iex> Dict.get(dict, :a)
3
"""
@@ -421,9 +421,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put_new(dict, :a, 3)
iex> Dict.get(dict, :a)
1
"""
@@ -438,13 +438,13 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.delete(dict, :a)
iex> Dict.get(dict, :a)
nil
iex> d = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d
iex> dict = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(dict, :a) == dict
true
"""
@@ -454,10 +454,10 @@ defmodule Dict do
end
@doc """
Merges the dict `b` into dict `a`.
Merges the dict `dict2` into dict `dict1`.
If one of the dict `b` entries already exists in the `dict`,
the functions in entries in `b` have higher precedence unless a
If one of the `dict2` entries already exists in `dict1`, the
functions in entries in `dict2` have higher precedence unless a
function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any
@@ -466,18 +466,18 @@ defmodule Dict do
## Examples
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2)
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 3, b: 2, d: 4]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 4, b: 2, d: 4]
"""
@@ -502,18 +502,18 @@ defmodule Dict do
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :a
iex> {v, Enum.sort(dict)}
{1,[]}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :b
iex> {v, Enum.sort(dict)}
{nil,[a: 1]}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(dict)}
{3,[a: 1]}
"""
@@ -528,9 +528,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
iex> Dict.get(dict, :a)
-1
"""
@@ -546,9 +546,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
iex> Dict.get(dict, :c)
3
"""
@@ -566,19 +566,19 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]}
iex> d = Enum.into([], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)}
iex> dict = Enum.into([], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
{[], []}
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
{[a: 1, b: 2], []}
"""
@@ -593,14 +593,14 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d])
iex> Dict.to_list(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[b: 2]
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:c, :d])
iex> Dict.to_list(dict) |> Enum.sort
[a: 1, b: 2]
"""
@@ -616,12 +616,12 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d])
iex> Dict.to_list(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.take(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[a: 1]
iex> d = Dict.take(d, [:c, :d])
iex> Dict.to_list(d)
iex> dict = Dict.take(dict, [:c, :d])
iex> Dict.to_list(dict)
[]
"""
@@ -645,14 +645,14 @@ defmodule Dict do
## Examples
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b)
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(dict1, dict2)
true
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = []
iex> Dict.equal?(a, b)
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = []
iex> Dict.equal?(dict1, dict2)
false
"""
-9
View File
@@ -1,9 +0,0 @@
defmodule Dict.Behaviour do
@moduledoc false
defmacro __using__(_) do
quote do
use Dict
end
end
end
+111 -64
View File
@@ -39,9 +39,9 @@ defprotocol Enumerable do
It must be a tagged tuple with one of the following "tags":
* `:cont` - the enumeration should continue
* `:halt` - the enumeration should halt immediately
* `:suspend` - the enumeration should be suspended immediately
* `:cont` - the enumeration should continue
* `:halt` - the enumeration should halt immediately
* `:suspend` - the enumeration should be suspended immediately
Depending on the accumulator value, the result returned by
`Enumerable.reduce/3` will change. Please check the `result`
@@ -270,8 +270,13 @@ defmodule Enum do
@doc """
Finds the element at the given index (zero-based).
Returns `default` if index is out of bounds.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples
iex> Enum.at([2, 4, 6], 0)
@@ -287,7 +292,6 @@ defmodule Enum do
:none
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
@@ -330,7 +334,6 @@ defmodule Enum do
[[1, 2, 3], [4, 5, 6]]
"""
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
@@ -338,7 +341,7 @@ defmodule Enum do
{_, {acc, {buffer, i}}} =
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
if nil?(pad) || i == 0 do
if is_nil(pad) || i == 0 do
:lists.reverse(acc)
else
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
@@ -566,12 +569,17 @@ defmodule Enum do
@doc """
Finds the element at the given index (zero-based).
Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e.
`-1` fetches the last element).
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples
iex> Enum.fetch([2, 4, 6], 0)
@@ -585,11 +593,11 @@ defmodule Enum do
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
def fetch(collection, n) when is_integer(n) and n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
@@ -605,15 +613,20 @@ defmodule Enum do
end
end
def fetch(collection, n) when n < 0 do
def fetch(collection, n) when is_integer(n) and n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@doc """
Finds the element at the given index (zero-based).
Raises `OutOfBoundsError` if the given position
is outside the range of the collection.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples
iex> Enum.fetch!([2, 4, 6], 0)
@@ -689,7 +702,6 @@ defmodule Enum do
3
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun)
@@ -716,7 +728,6 @@ defmodule Enum do
true
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun)
@@ -931,19 +942,18 @@ defmodule Enum do
"1 = 2 = 3"
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(entry)
entry, acc -> acc <> joiner <> to_string(entry)
entry, :first -> enum_to_string(entry)
entry, acc -> [acc, joiner|enum_to_string(entry)]
end)
if reduced == :first do
""
else
reduced
IO.iodata_to_binary reduced
end
end
@@ -990,20 +1000,19 @@ defmodule Enum do
"2 = 4 = 6"
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
entry, :first -> enum_to_string(mapper.(entry))
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
end)
if reduced == :first do
""
else
reduced
IO.iodata_to_binary reduced
end
end
@@ -1396,7 +1405,7 @@ defmodule Enum do
result. For example, one could do the following to set a seed
dynamically:
:random.seed(:erlang.now)
:random.seed(:os.timestamp)
## Examples
@@ -1417,18 +1426,33 @@ defmodule Enum do
@doc """
Returns a subset list of the given collection. Drops elements
until element position `start`, then takes `count` elements.
If the count is greater than collection length, it returns as
much as possible. If zero, then it returns `[]`.
## Examples
iex> Enum.slice(1..100, 5, 10)
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
iex> Enum.slice(1..10, 5, 100)
[6, 7, 8, 9, 10]
iex> Enum.slice(1..10, 5, 0)
[]
"""
@spec slice(t, integer, non_neg_integer) :: list
def slice(_coll, _start, 0), do: []
def slice(coll, start, count) when start < 0 do
{list, new_start} = enumerate_and_count(coll, start)
if new_start >= 0, do: slice(list, new_start, count)
if new_start >= 0 do
slice(list, new_start, count)
else
[]
end
end
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
@@ -1436,7 +1460,7 @@ defmodule Enum do
end
def slice(coll, start, count) when start >= 0 and count > 0 do
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
{_, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
_entry, {start, count, _list} when start > 0 ->
{:cont, {start-1, count, []}}
entry, {start, count, list} when count > 1 ->
@@ -1445,15 +1469,7 @@ defmodule Enum do
{:halt, {start, count, [entry|list]}}
end) |> elem(1)
if start <= 0, do: :lists.reverse(list)
end
def slice(coll, start, 0) do
res =
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
if start > 0, do: {:cont, start-1}, else: {:halt, []}
end) |> elem(1)
if is_list(res), do: res
:lists.reverse(list)
end
@doc """
@@ -1468,17 +1484,31 @@ defmodule Enum do
The first position (after adding count to negative positions) must be smaller
or equal to the last position.
If the start of the range is not a valid offset for the given
collection or if the range is in reverse order, returns `[]`.
## Examples
iex> Enum.slice(1..100, 5..10)
[6, 7, 8, 9, 10, 11]
iex> Enum.slice(1..10, 5..20)
[6, 7, 8, 9, 10]
iex> Enum.slice(1..10, 11..20)
[]
iex> Enum.slice(1..10, 6..5)
[]
"""
@spec slice(t, Range.t) :: list
def slice(coll, first..last) when first >= 0 and last >= 0 do
# Simple case, which works on infinite collections
if last - first >= 0 do
slice(coll, first, last - first + 1)
else
[]
end
end
@@ -1489,6 +1519,8 @@ defmodule Enum do
length = corr_last - corr_first + 1
if corr_first >= 0 and length > 0 do
slice(list, corr_first, length)
else
[]
end
end
@@ -1545,6 +1577,46 @@ defmodule Enum do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
end
@doc """
Sorts the mapped results of the `collection` according to the `sorter` function.
This function maps each element of the collection using the `mapper`
function. The collection is then sorted by the mapped elements using the
`sorter` function, which defaults to `<=/2`
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
value for each element in the collection once instead of once for each
element in each comparison. If the same function is being called on both
element, it's also more compact to use `sort_by/3`.
This technique is also known as a
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
original `collection`, then `sorter` is sorting the decorations, and finally
the `collection` is being undecorated so only the original elements remain,
but now in sorted order.
## Examples
Using the default `sorter` of `<=/2`:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
["of", "some", "kind", "monster"]
Using a custom `sorter` to override the order:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
["monster", "some", "kind", "of"]
"""
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
def sort_by(collection, mapper, sorter \\ &<=/2) do
collection
|> map(&{&1, mapper.(&1)})
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|> map(&elem(&1, 0))
end
@doc """
Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number,
@@ -1726,27 +1798,6 @@ defmodule Enum do
reverse(collection) |> :lists.reverse
end
@doc """
Traverses the given enumerable keeping its shape.
It also expects the enumerable to implement the `Collectable` protocol.
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
"""
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, Collectable.empty(collection), transform)
end
@doc """
Enumerates the collection, removing all duplicated items.
@@ -1816,7 +1867,7 @@ defmodule Enum do
## Helpers
@compile {:inline, to_string: 2}
@compile {:inline, enum_to_string: 1}
defp enumerate_and_count(collection, count) when is_list(collection) do
{collection, length(collection) - abs(count)}
@@ -1826,12 +1877,8 @@ defmodule Enum do
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
end
defp to_string(mapper, entry) do
case mapper.(entry) do
x when is_binary(x) -> x
o -> String.Chars.to_string(o)
end
end
defp enum_to_string(entry) when is_binary(entry), do: entry
defp enum_to_string(entry), do: String.Chars.to_string(entry)
## Implementations
@@ -2142,12 +2189,12 @@ defmodule Enum do
## slice
defp do_slice([], start, _count) do
if start == 0, do: []
defp do_slice([], _start, _count) do
[]
end
defp do_slice(list, start, 0) do
if start < length(list), do: []
defp do_slice(_list, _start, 0) do
[]
end
defp do_slice([h|t], 0, count) do
@@ -2195,7 +2242,7 @@ defimpl Enumerable, for: Map do
end
defimpl Enumerable, for: Function do
def reduce(function, acc, fun),
def reduce(function, acc, fun) when is_function(function, 2),
do: function.(acc, fun)
def member?(_function, _value),
do: {:error, __MODULE__}
+36 -13
View File
@@ -5,16 +5,16 @@ defmodule Exception do
Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process.
last throw/error/exit that occurred in the current process.
Do not rely on the particular format returned by the `format`
functions in this module. They may be changed in future releases
in order to better suit Elixir's tool chain. In other words,
by using the functions in this module it is guarantee you will
by using the functions in this module it is guaranteed you will
format exceptions as in the current Elixir version being used.
"""
@typedoc "The exception type (as generated by defexception)"
@typedoc "The exception type"
@type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions"
@@ -482,6 +482,7 @@ end
defmodule RuntimeError do
defexception message: "runtime error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg}
end
@@ -494,6 +495,7 @@ end
defmodule ArgumentError do
defexception message: "argument error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg}
end
@@ -578,6 +580,14 @@ defmodule CaseClauseError do
end
end
defmodule CondClauseError do
defexception []
def message(_exception) do
"no cond clause evaluated to a true value"
end
end
defmodule TryClauseError do
defexception [term: nil]
@@ -603,12 +613,17 @@ defmodule BadArityError do
end
defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil]
defexception [module: nil, function: nil, arity: nil, self: false]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
def message(%{function: function, module: module, arity: arity, self: self}) do
if function do
formatted = Exception.format_mfa module, function, arity
suffix = if self or is_nil(module) or :code.is_loaded(module) do
""
else
" (module #{inspect module} is not available)"
end
"undefined function: #{formatted}" <> suffix
else
"undefined function"
end
@@ -632,7 +647,7 @@ defmodule Code.LoadError do
defexception [:file, :message]
def exception(opts) do
file = opts[:file]
file = Keyword.fetch!(opts, :file)
%Code.LoadError{message: "could not load #{file}", file: file}
end
end
@@ -663,8 +678,8 @@ defmodule UnicodeConversionError do
def exception(opts) do
%UnicodeConversionError{
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
encoded: Keyword.fetch!(opts, :encoded),
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
}
end
@@ -733,6 +748,10 @@ defmodule ErlangError do
%SystemLimitError{}
end
def normalize(:cond_clause, _stacktrace) do
%CondClauseError{}
end
def normalize({:badarity, {fun, args}}, _stacktrace) do
%BadArityError{function: fun, args: args}
end
@@ -758,8 +777,9 @@ defmodule ErlangError do
end
def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
stacktrace = stacktrace || :erlang.get_stacktrace
{mod, fun, arity} = from_stacktrace(stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
end
def normalize(:function_clause, stacktrace) do
@@ -786,4 +806,7 @@ defmodule ErlangError do
defp from_stacktrace(_) do
{nil, nil, nil}
end
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
defp from_self(_), do: false
end
+183 -249
View File
@@ -1,144 +1,3 @@
require Record
defmodule File.Stat do
@moduledoc """
A struct responsible to hold file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
in between the Erlang record and the Elixir struct.
Its fields are:
* `size` - Size of file in bytes.
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
the file.
* `atime` - The last time the file was read.
* `mtime` - The last time the file was written.
* `ctime` - The interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was
changed. In Windows, it is the create time.
* `mode` - The file permissions.
* `links` - The number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - Identifies the file system where the file is located.
In windows, the number indicates a drive as follows:
0 means A:, 1 means B:, and so on.
* `minor_device` - Only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - Gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - Indicates the owner of the file.
* `gid` - Gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
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)
defstruct keys
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
"""
def to_record(%File.Stat{unquote_splicing(pairs)}) do
{:file_info, unquote_splicing(vals)}
end
@doc """
Converts a `:file_info` record into a `File.Stat`.
"""
def from_record({:file_info, unquote_splicing(vals)}) do
%File.Stat{unquote_splicing(pairs)}
end
end
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/2`.
The following fields are public:
* `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 amount of bytes
"""
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{path: path, modes: modes, raw: raw} = stream) do
modes = for mode <- modes, not mode in [:read], do: mode
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
end
defp into(device, stream, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, :done ->
:file.close(device)
stream
:ok, :halt ->
:file.close(device)
end
end
end
defimpl Enumerable do
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
modes = for mode <- modes, not mode in [:write, :append], do: mode
start_fun =
fn ->
case :file.open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
defmodule File do
@moduledoc ~S"""
This module contains functions to manipulate files.
@@ -211,12 +70,17 @@ defmodule File do
about such options and other performance considerations.
"""
alias :file, as: F
alias :filelib, as: FL
alias :file, as: F
@type posix :: :file.posix()
@type io_device :: :file.io_device()
@type stat_options :: [time: :local | :universal | :posix]
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
:raw | :read | :read_ahead | :sync | :write |
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
{:utf16, :big | :little} | {:utf32, :big | :little}} |
{:read_ahead, pos_integer} |
{:delayed_write, non_neg_integer, non_neg_integer}
@doc """
Returns `true` if the path is a regular file.
@@ -228,7 +92,7 @@ defmodule File do
"""
@spec regular?(Path.t) :: boolean
def regular?(path) do
FL.is_regular(IO.chardata_to_string(path))
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
end
@doc """
@@ -236,7 +100,7 @@ defmodule File do
"""
@spec dir?(Path.t) :: boolean
def dir?(path) do
FL.is_dir(IO.chardata_to_string(path))
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
end
@doc """
@@ -267,12 +131,13 @@ defmodule File do
Typical error reasons are:
* :eacces - Missing search or write permissions for the parent directories of `path`.
* :eexist - There is already a file or directory named `path`.
* :enoent - A component of `path` does not exist.
* :enospc - There is a no space left on the device.
* :enotdir - A component of `path` is not a directory
On some platforms, `:enoent` is returned instead.
* `:eacces` - missing search or write permissions for the parent
directories of `path`
* `:eexist` - there is already a file or directory named `path`
* `:enoent` - a component of `path` does not exist
* `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory;
on some platforms, `:enoent` is returned instead
"""
@spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do
@@ -298,13 +163,38 @@ defmodule File do
Typical error reasons are:
* :eacces - Missing search or write permissions for the parent directories of `path`.
* :enospc - There is a no space left on the device.
* :enotdir - A component of `path` is not a directory.
* `:eacces` - missing search or write permissions for the parent
directories of `path`
* `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory
"""
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
def mkdir_p(path) do
FL.ensure_dir(Path.join(path, "."))
do_mkdir_p(IO.chardata_to_string(path))
end
defp do_mkdir_p("/") do
:ok
end
defp do_mkdir_p(path) do
if dir?(path) do
:ok
else
parent = Path.dirname(path)
if parent == path do
# Protect against infinite loop
{:error, :einval}
else
_ = do_mkdir_p(parent)
case :file.make_dir(path) do
{:error, :eexist} = error ->
if dir?(path), do: :ok, else: error
other ->
other
end
end
end
end
@doc """
@@ -326,13 +216,13 @@ defmodule File do
Typical error reasons:
* :enoent - The file does not exist.
* :eacces - Missing permission for reading the file,
or for searching one of the parent directories.
* :eisdir - The named file is a directory.
* :enotdir - A component of the file name is not a directory.
On some platforms, `:enoent` is returned instead.
* :enomem - There is not enough memory for the contents of the file.
* `:enoent` - the file does not exist
* `:eacces` - missing permission for reading the file,
or for searching one of the parent directories
* `:eisdir` - the named file is a directory
* `:enotdir` - a component of the file name is not a directory;
on some platforms, `:enoent` is returned instead
* `:enomem` - there is not enough memory for the contents of the file
You can use `:file.format_error/1` to get a descriptive string of the error.
"""
@@ -359,15 +249,14 @@ defmodule File do
@doc """
Returns information about the `path`. If it exists, it
returns a `{:ok, info}` tuple, where info is a
`File.Stat` record. Returns `{:error, reason}` with
`File.Stat` struct. Returns `{:error, reason}` with
the same reasons as `read/1` if a failure occurs.
## Options
The accepted options are:
* `:time` if the time should be `:local`, `:universal` or `:posix`.
Default is `:local`.
* `:time` - `:local | :universal | :posix`; default: `:local`
"""
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
@@ -425,11 +314,15 @@ defmodule File do
def touch(path, time \\ :calendar.local_time) do
path = IO.chardata_to_string(path)
case F.change_time(path, time) do
{:error, :enoent} ->
write(path, "")
F.change_time(path, time)
other ->
other
{:error, :enoent} -> touch_new(path, time)
other -> other
end
end
defp touch_new(path, time) do
case write(path, "", [:append]) do
:ok -> F.change_time(path, time)
{:error, _reason} = error -> error
end
end
@@ -622,7 +515,7 @@ defmodule File do
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case :elixir_utils.file_type(src) do
case :elixir_utils.read_link_type(src) do
{:ok, :regular} ->
do_cp_file(src, dest, callback, acc)
{:ok, :symlink} ->
@@ -665,7 +558,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
if callback.(src, dest) do
rm(dest)
# If rm/1 fails, copy/2 will fail
_ = rm(dest)
case copy(src, dest) do
{:ok, _} ->
copy_file_mode!(src, dest)
@@ -686,7 +580,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
if callback.(src, dest) do
rm(dest)
# If rm/1 fails, iF.make_symlink/2 will fail
_ = rm(dest)
case F.make_symlink(link, dest) do
:ok -> [dest|acc]
{:error, reason} -> {:error, reason, src}
@@ -713,25 +608,25 @@ defmodule File do
Typical error reasons are:
* :enoent - A component of the file name does not exist.
* :enotdir - A component of the file name is not a directory.
On some platforms, enoent is returned instead.
* :enospc - There is a no space left on the device.
* :eacces - Missing permission for writing the file or searching one of the parent directories.
* :eisdir - The named file is a directory.
* `:enoent` - a component of the file name does not exist
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
* `:enospc` - there is a no space left on the device
* `:eacces` - missing permission for writing the file or searching one of
the parent directories
* `:eisdir` - the named file is a directory
The writing is automatically done in `:raw` mode. Check
`File.open/2` for other available options.
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
F.write_file(IO.chardata_to_string(path), content, modes)
end
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
@@ -743,16 +638,18 @@ defmodule File do
@doc """
Tries to delete the file `path`.
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
Note the file is deleted even if in read-only mode.
Typical error reasons are:
* :enoent - The file does not exist.
* :eacces - Missing permission for the file or one of its parents.
* :eperm - The file is a directory and user is not super-user.
* :enotdir - A component of the file name is not a directory.
On some platforms, enoent is returned instead.
* :einval - Filename had an improper type, such as tuple.
* `:enoent` - the file does not exist
* `:eacces` - missing permission for the file or one of its parents
* `:eperm` - the file is a directory and user is not super-user
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
* `:einval` - filename had an improper type, such as tuple
## Examples
@@ -765,7 +662,33 @@ defmodule File do
"""
@spec rm(Path.t) :: :ok | {:error, posix}
def rm(path) do
F.delete(IO.chardata_to_string(path))
path = IO.chardata_to_string(path)
case F.delete(path) do
:ok ->
:ok
{:error, :eacces} = e ->
change_mode_windows(path) || e
{:error, _} = e ->
e
end
end
defp change_mode_windows(path) do
if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
change_mode_windows(path, file_info)
_ ->
nil
end
end
end
defp change_mode_windows(path, file_info) do
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
:ok -> F.delete(path)
{:error, _reason} = error -> error
end
end
@doc """
@@ -886,9 +809,9 @@ defmodule File do
end
defp safe_list_dir(path) do
case :elixir_utils.file_type(path) do
case :elixir_utils.read_link_type(path) do
{:ok, :symlink} ->
case :elixir_utils.file_type(path, :read_file_info) do
case :elixir_utils.read_file_type(path) do
{:ok, :directory} -> {:ok, :directory}
_ -> {:ok, :regular}
end
@@ -928,43 +851,55 @@ defmodule File do
The allowed modes:
* `:read` - The file, which must exist, is opened for reading.
* `:read` - the file, which must exist, is opened for reading.
* `:write` - The file is opened for writing. It is created if it does not exist.
If the file exists, and if write is not combined with read, the file will be truncated.
* `:write` - the file is opened for writing. It is created if it does not
exist.
* `:append` - The file will be opened for writing, and it will be created if it does not exist.
Every write operation to a file opened with append will take place at the end of the file.
If the file does exists, and if write is not combined with read, the file
will be truncated.
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
If the file exists, open will return `{:error, :eexist}`.
* `:append` - the file will be opened for writing, and it will be created
if it does not exist. Every write operation to a file opened with append
will take place at the end of the file.
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
* `:exclusive` - the file, when opened for writing, is created if it does
not exist. If the file exists, open will return `{:error, :eexist}`.
* `:compressed` - Makes it possible to read or write gzip compressed files.
The compressed option must be combined with either read or write, but not both.
Note that the file size obtained with `stat/1` will most probably not
match the number of bytes that can be read from a compressed file.
* `:char_list` - when this term is given, read operations on the file will
return char lists rather than binaries.
* `:utf8` - This option denotes how data is actually stored in the disk file and
makes the file perform automatic translation of characters to and from utf-8.
If data is sent to a file in a format that cannot be converted to the utf-8
or if data is read by a function that returns data in a format that cannot cope
with the character range of the data, an error occurs and the file will be closed.
* `:compressed` - makes it possible to read or write gzip compressed files.
The compressed option must be combined with either read or write, but not
both. Note that the file size obtained with `stat/1` will most probably
not match the number of bytes that can be read from a compressed file.
* `:utf8` - this option denotes how data is actually stored in the disk
file and makes the file perform automatic translation of characters to
and from utf-8.
If data is sent to a file in a format that cannot be converted to the
utf-8 or if data is read by a function that returns data in a format that
cannot cope with the character range of the data, an error occurs and the
file will be closed.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
other options like `:read_ahead` and `:delayed_write`.
This function returns:
* `{:ok, io_device}` - The file has been opened in the requested mode.
`io_device` is actually the pid of the process which handles the file.
This process is linked to the process which originally opened the file.
If any process to which the `io_device` is linked terminates, the file will
be closed and the process itself will be terminated. An `io_device` returned
from this call can be used as an argument to the `IO` module functions.
* `{:ok, io_device}` - the file has been opened in the requested mode.
* `{:error, reason}` - The file could not be opened.
`io_device` is actually the pid of the process which handles the file.
This process is linked to the process which originally opened the file.
If any process to which the `io_device` is linked terminates, the file
will be closed and the process itself will be terminated.
An `io_device` returned from this call can be used as an argument to the
`IO` module functions.
* `{:error, reason}` - the file could not be opened.
## Examples
@@ -973,7 +908,8 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
@@ -989,14 +925,15 @@ defmodule File do
The file is opened, given to the function as argument and
automatically closed after the function returns, regardless
if there was an error or not.
if there was an error when executing the function.
It returns `{:ok, function_result}` in case of success,
`{:error, reason}` otherwise.
Do not use this function with `:delayed_write` option
since automatically closing the file may fail
(as writes are delayed).
This function expects the file to be closed with success,
which is usually the case unless the `:delayed_write` option
is given. For this reason, we do not recommend passing
`:delayed_write` to this function.
## Examples
@@ -1005,7 +942,7 @@ defmodule File do
end)
"""
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes, function) do
case open(path, modes) do
{:ok, device} ->
@@ -1020,9 +957,10 @@ defmodule File do
@doc """
Same as `open/2` but raises an error if file could not be opened.
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
@spec open!(Path.t, [mode]) :: io_device | no_return
def open!(path, modes \\ []) do
path = IO.chardata_to_string(path)
case open(path, modes) do
@@ -1034,9 +972,10 @@ defmodule File do
@doc """
Same as `open/3` but raises an error if file could not be opened.
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = IO.chardata_to_string(path)
case open(path, modes, function) do
@@ -1057,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
{:error, _} = error -> error
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
The same as `cwd/0`, but raises an exception if it fails.
"""
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> IO.chardata_to_string(cwd)
case cwd() do
{:ok, cwd} -> cwd
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1184,21 +1132,7 @@ defmodule File do
"""
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
modes = open_defaults(modes, true)
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
if raw do
if :lists.keyfind(:read_ahead, 1, modes) do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
raw: raw, line_or_bytes: line_or_bytes}
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
end
@doc """
@@ -1206,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1214,7 +1148,7 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
def chmod!(path, mode) do
path = IO.chardata_to_string(path)
case chmod(path, mode) do
@@ -1229,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid)
end
@@ -1237,7 +1171,7 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
def chgrp!(path, gid) do
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
@@ -1252,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1260,7 +1194,7 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chown!(Path.t, integer) :: :ok | no_return
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
def chown!(path, uid) do
path = IO.chardata_to_string(path)
case chown(path, uid) do
+75
View File
@@ -0,0 +1,75 @@
require Record
defmodule File.Stat do
@moduledoc """
A struct responsible to hold file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
in between the Erlang record and the Elixir struct.
Its fields are:
* `size` - size of file in bytes.
* `type` - `:device | :directory | :regular | :other`; the type of the
file.
* `access` - `:read | :write | :read_write | :none`; the current system
access to the file.
* `atime` - the last time the file was read.
* `mtime` - the last time the file was written.
* `ctime` - the interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was changed.
In Windows, it is the time of creation.
* `mode` - the file permissions.
* `links` - the number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - identifies the file system where the file is located.
In windows, the number indicates a drive as follows: 0 means A:, 1 means
B:, and so on.
* `minor_device` - only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - indicates the owner of the file.
* `gid` - gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
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)
defstruct keys
@type t :: %__MODULE__{}
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
"""
def to_record(%File.Stat{unquote_splicing(pairs)}) do
{:file_info, unquote_splicing(vals)}
end
@doc """
Converts a `:file_info` record into a `File.Stat`.
"""
def from_record({:file_info, unquote_splicing(vals)}) do
%File.Stat{unquote_splicing(pairs)}
end
end
+102
View File
@@ -0,0 +1,102 @@
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/3`.
The following fields are public:
* `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 amount of bytes
"""
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
@type t :: %__MODULE__{}
@doc false
def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
if raw do
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
end
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{path: path, modes: modes, raw: raw} = stream) do
modes = for mode <- modes, not mode in [:read], do: mode
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
end
defp into(device, stream, raw) do
fn
:ok, {:cont, x} ->
case raw do
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.
:ok = :file.close(device)
end
end
end
defimpl Enumerable do
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
modes = for mode <- modes, not mode in [:write, :append], do: mode
start_fun =
fn ->
case :file.open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
+59 -35
View File
@@ -1,3 +1,5 @@
import Kernel, except: [round: 1]
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
@@ -99,57 +101,71 @@ defmodule Float do
@doc """
Rounds a float to the largest integer less than or equal to `num`.
Floor also accepts a precision to round a floating point value down
to an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
34.0
iex> Float.floor(-56.5)
-57
-57.0
iex> Float.floor(34.253, 2)
34.25
"""
@spec floor(float | integer) :: integer
def floor(num) when is_integer(num), do: num
def floor(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num < 0 -> truncated - 1
_ -> truncated
end
@spec floor(float, 0..15) :: float
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated < 0, do: -1.0, else: 0.0
(truncated + variance) / power
end
@doc """
Rounds a float to the largest integer greater than or equal to `num`.
Ceil also accepts a precision to round a floating point value down to
an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
35.0
iex> Float.ceil(-56.5)
-56
-56.0
iex> Float.ceil(34.253, 2)
34.26
"""
@spec ceil(float | integer) :: integer
def ceil(num) when is_integer(num), do: num
def ceil(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num > 0 -> truncated + 1
_ -> truncated
end
@spec ceil(float, 0..15) :: float
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated > 0, do: 1.0, else: 0.0
(truncated + variance) / power
end
@doc """
Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15).
This function only accepts floats and returns floats. Use `Kernel.round/1`
if you want a function that accepts both floats and integers and always
returns an integer.
## Examples
iex> Float.round(5.5674, 3)
@@ -165,9 +181,15 @@ defmodule Float do
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
@spec round(float, 0..15) :: float
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
Kernel.round(number * power) / power
end
Enum.reduce 0..15, 1, fn x, acc ->
defp power_of_10(unquote(x)), do: unquote(acc)
acc * 10
end
@doc """
@@ -192,9 +214,10 @@ defmodule Float do
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored
with the `scientific` option)
## Examples
@@ -230,9 +253,10 @@ defmodule Float do
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored
with the `scientific` option)
## Examples
File diff suppressed because it is too large Load Diff
-45
View File
@@ -1,45 +0,0 @@
defmodule GenEvent.Behaviour do
@moduledoc false
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_event
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_event(_event, state) do
{:ok, state}
end
@doc false
def handle_call(_request, state) do
{:ok, :ok, state}
end
@doc false
def handle_info(_msg, state) do
{:ok, state}
end
@doc false
def terminate(reason, state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1,
handle_event: 2,
handle_call: 2, handle_info: 2,
terminate: 2, code_change: 3]
end
end
end
+168
View File
@@ -0,0 +1,168 @@
defmodule GenEvent.Stream do
@moduledoc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:timeout` - the timeout in between events, defaults to `:infinity`
"""
defstruct manager: nil, timeout: :infinity
@type t :: %__MODULE__{
manager: GenEvent.manager,
timeout: timeout}
@doc false
def init({_pid, _ref} = state) do
{:ok, state}
end
@doc false
def handle_event(event, _state) do
exit({:bad_event, event})
end
@doc false
def handle_call(msg, _state) do
exit({:bad_call, msg})
end
@doc false
def handle_info(_msg, state) do
{:ok, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
end
defimpl Enumerable, for: GenEvent.Stream do
def reduce(stream, acc, fun) do
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))
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _item) do
{:error, __MODULE__}
end
defp wrap_reducer(fun) do
fn
{:ack, manager, ref, event}, acc ->
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}
end
end
end
defp start(%{manager: manager} = stream) do
try do
{:ok, {pid, ref}} = :gen.call(manager, self(),
{:add_process_handler, self(), self()}, :infinity)
mon_ref = Process.monitor(pid)
{pid, ref, mon_ref}
catch
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
end
end
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
self = self()
receive do
# Got an async event.
{_from, {^pid, ^ref}, {:notify, event}} ->
{[{:async, pid, ref, event}], acc}
# Got a sync event.
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
{[{:sync, pid, ref, event}], acc}
# Got an ack event.
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
{[{:ack, pid, ref, event}], acc}
# The handler was removed. Stop iteration, resolve the
# event later. We need to demonitor now, otherwise DOWN
# appears with higher priority in the shutdown process.
{:gen_event_EXIT, {^pid, ^ref}, _reason} = event ->
Process.demonitor(mon_ref, [:flush])
send(self, event)
{:halt, {:removed, acc}}
# The manager died. Stop iteration, resolve the event later.
{:DOWN, ^mon_ref, _, _, _} = event ->
send(self, event)
{:halt, {:removed, acc}}
after
timeout ->
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
end
end
# If we reach this branch, we know the handler was already
# removed, so we don't trigger a request for doing so.
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
case wait_for_handler_removal(pid, ref, mon_ref) do
:ok ->
flush_events(ref)
{:error, reason} ->
exit({reason, {__MODULE__, :stop, [stream, acc]}})
end
end
# If we reach this branch, the handler was not removed yet,
# so we trigger a request for doing so.
defp stop(stream, {pid, ref, _} = acc) do
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
stop(stream, {:removed, acc})
end
defp wait_for_handler_removal(pid, ref, mon_ref) do
receive do
{:gen_event_EXIT, {^pid, ^ref}, reason}
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
Process.demonitor(mon_ref, [:flush])
:ok
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
Process.demonitor(mon_ref, [:flush])
{:error, reason}
{:DOWN, ^mon_ref, _, _, reason} ->
{:error, reason}
end
end
defp flush_events(ref) do
receive do
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
flush_events(ref)
after
0 -> :ok
end
end
end
+132 -76
View File
@@ -11,7 +11,7 @@ defmodule GenServer do
## Example
The GenServer behaviour abstracts the common client-server interaction.
Developer are only required to implement the callbacks and functionality they are
Developers are only required to implement the callbacks and functionality they are
interested in.
Let's start with a code example and then explore the available callbacks.
@@ -45,14 +45,14 @@ defmodule GenServer do
GenServer.call(pid, :pop)
#=> :world
We start our `Stack` by calling `start_link/2`, passing the module
We start our `Stack` by calling `start_link/3`, passing the module
with the server implementation and its initial argument (a list
representing the stack containing the item `:hello`). We can primarily
interact with the server by sending two types of messages. **call**
messages expect a reply from the server (and is therefore synchronous)
messages expect a reply from the server (and are therefore synchronous)
while **cast** messages do not.
Every time you do a `GenServer.call/2`, the client will send a message
Every time you do a `GenServer.call/3`, the client will send a message
that must be handled by the `handle_call/3` callback in the GenServer.
A `cast/2` message must be handled by `handle_cast/2`.
@@ -63,64 +63,64 @@ defmodule GenServer do
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
* `init(args)` - invoked when the server is started
* `init(args)` - invoked when the server is started.
It must return:
It must return:
{:ok, state}
{:ok, state, timeout}
:ignore
{:stop, reason}
- `{:ok, state}`
- `{:ok, state, timeout}`
- `:ignore`
- `{:stop, reason}`
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
invoked to handle call (sync) and cast (async) messages.
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
invoked to handle call (sync) and cast (async) messages.
It must return:
It must return:
{:reply, reply, new_state}
{:reply, reply, new_state, timeout}
{:reply, reply, new_state, :hibernate}
{:noreply, new_state}
{:noreply, new_state, timeout}
{:noreply, new_state, :hibernate}
{:stop, reason, new_state}
{:stop, reason, reply, new_state}
- `{:reply, reply, new_state}`
- `{:reply, reply, new_state, timeout}`
- `{:reply, reply, new_state, :hibernate}`
- `{:noreply, new_state}`
- `{:noreply, new_state, timeout}`
- `{:noreply, new_state, :hibernate}`
- `{:stop, reason, new_state}`
- `{:stop, reason, reply, new_state}`
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process.
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process.
It must return:
It must return:
{:noreply, state}
{:noreply, state, timeout}
{:stop, reason, state}
- `{:noreply, state}`
- `{:noreply, state, timeout}`
- `{:stop, reason, state}`
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up. It must return `:ok`
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up. It must return `:ok`.
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap).
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swapping).
It must return:
It must return:
{:ok, new_state}
{:error, reason}
- `{:ok, new_state}`
- `{:error, reason}`
## Names registering
## Name Registration
Both `start_link/3` and `start/3` support the `GenServer` to register
a name on start via the `:name` option. Registered names are also
automatically clean up on termination. The supported values are:
automatically cleaned up on termination. The supported values are:
* an atom - the GenServer is registered locally with the given name
using `Process.register/2`;
* an atom - the GenServer is registered locally with the given name
using `Process.register/2`.
* `{:global, term}`- the GenServer is registered globally with the given
term using the functions in the `:global` module;
* `{:global, term}`- the GenServer is registered globally with the given
term using the functions in the `:global` module.
* `{:via, module, term}` - the GenServer is registered with the given
mechanism and name. The `:via` option expects a module name to control
the registration mechanism along side a name which can be any term;
* `{:via, module, term}` - the GenServer is registered with the given
mechanism and name. The `:via` option expects a module name to control
the registration mechanism alongside a name which can be any term.
For example, we could start and register our Stack server locally as follows:
@@ -130,22 +130,23 @@ defmodule GenServer do
# Now messages can be sent directly to MyStack
GenServer.call(MyStack, :pop) #=> :hello
Once the server is started, the remaining functions in this module (`call/2`,
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
Once the server is started, the remaining functions in this module (`call/3`,
`cast/2`, and friends) will also accept an atom, or any `:global` or `:via`
tuples. In general, the following formats are supported:
* a `pid`
* an `atom` if the server is locally registered
* `{atom, node}` if the server is locally registered at another node
* `{:global, term}` if the server is globally registered
* `{:via, module, name}` if the server is registered through an alternative registry
* a `pid`
* an `atom` if the server is locally registered
* `{atom, node}` if the server is locally registered at another node
* `{:global, term}` if the server is globally registered
* `{:via, module, name}` if the server is registered through an alternative
registry
## Client / Server APIs
Although in the example above we have used `GenServer.start_link/2` and
Although in the example above we have used `GenServer.start_link/3` and
friends to directly start and communicate with the server, most of the
time we don't call the `GenServer` functions directly, instead, we wrap
them in functions too.
time we don't call the `GenServer` functions directly. Instead, we wrap
the calls in new functions representing the public API of the server.
Here is a better implementation of our Stack module:
@@ -192,14 +193,14 @@ defmodule GenServer do
## Learn more
In case you desire to learn more about gen servers, Elixir getting started
If you wish to find out more about gen servers, Elixir getting started
guides provide a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@typedoc "Return values of `start*` functions"
@@ -269,27 +270,32 @@ defmodule GenServer do
passing the given `args` to initialize it. To ensure a synchronized start-up
procedure, this function does not return until `init/1` has returned.
Note that a `GenServer` started with `start_link/3` is linked to the
parent process and will exit in case of crashes. The GenServer will also
exit due to the `:normal` reasons in case it is configured to trap exits
in the `init/1` callback.
## Options
The `:name` option is used for name registered as described in the module
The `:name` option is used for name registration as described in the module
documentation. If the option `:timeout` option is present, the server is
allowed to spend the given milliseconds initializing or it will be
terminated and the start function will return `{:error, :timeout}`.
If the option `:debug` is present, the corresponding function in the
If the `:debug` option is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the option `:spawn_opt` is present, the given options will be passed
to the underlying process as in `Process.spawn/3`.
If the `:spawn_opt` option is present, its value will be passed as options
to the underlying process as in `Process.spawn/4`.
## Return values
If the server is successfully created and initialized the function returns
If the server is successfully created and initialized, the function returns
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
process with the specified server name the function returns
process with the specified server name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
If the `init/1` callback fails with reason, the function returns
If the `init/1` callback fails with `reason`, the function returns
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
or `:ignore`, the process is terminated and the function returns
`{:error, reason}` or `:ignore`, respectively.
@@ -321,13 +327,13 @@ defmodule GenServer do
end
@doc """
Makes a synchronous call to the `server` and wait for its reply.
Makes a synchronous call to the `server` and waits for its reply.
The client sends the given `request` to the server and waits until a reply
arrives or a timeout occurs. `handle_call/3` will be called on the server
to handle the request.
The server can be any of the values described in the `Name Registering`
The server can be any of the values described in the `Name Registration`
section of the module documentation.
## Timeouts
@@ -343,18 +349,57 @@ defmodule GenServer do
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
:gen_server.call(server, request, timeout)
try do
:gen.call(server, :"$gen_call", request, timeout)
catch
:exit, reason ->
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
else
{:ok, res} -> res
end
end
@doc """
Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, ignoring if the destination node
or server does not exist. `handle_cast/2` will be called on the server
to handle the request.
This function returns `:ok` immediately, regardless of
whether the destination node or server does exists, unless
the server is specified as an atom.
`handle_cast/2` will be called on the server to handle
the request. In case the server is a node which is not
yet connected to the caller one, the call is going to
block until a connection happens. This is different than
the behaviour in OTP's `:gen_server` where the message
would be sent by another process, which could cause
messages to arrive out of order.
"""
@spec cast(server, term) :: :ok
defdelegate cast(server, request), to: :gen_server
def cast(server, request)
def cast({:global, name}, request) do
try do
:global.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
def cast({:via, mod, name}, request) do
try do
mod.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
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))
@doc """
Casts all servers locally registered as `name` at the specified nodes.
@@ -365,8 +410,19 @@ defmodule GenServer do
See `multi_call/4` for more information.
"""
@spec abcast([node], name :: atom, term) :: :abcast
def abcast(nodes \\ nodes(), name, request) do
:gen_server.abcast(nodes, name, request)
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
msg = cast_msg(request)
_ = for node <- nodes, do: do_send({name, node}, msg)
:abcast
end
defp cast_msg(req) do
{:"$gen_cast", req}
end
defp do_send(dest, msg) do
send(dest, msg)
:ok
end
@doc """
@@ -392,7 +448,7 @@ defmodule GenServer do
end
@doc """
Replies a client.
Replies to a client.
This function can be used by a server to explicitly send a reply to a
client that called `call/3` or `multi_call/4`. When the reply cannot be
@@ -402,7 +458,7 @@ defmodule GenServer do
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
given back to the client as the return value of the call.
This function always return `:ok`.
This function always returns `:ok`.
"""
@spec reply({pid, reference}, term) :: :ok
def reply(client, reply)
-43
View File
@@ -1,43 +0,0 @@
defmodule GenServer.Behaviour do
@moduledoc false
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_server
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_call(request, _from, state) do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
+22 -5
View File
@@ -14,14 +14,15 @@ defmodule HashDict do
functions and their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
use Dict
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
# Inline common instructions
@@ -85,10 +86,11 @@ defmodule HashDict do
## General helpers
defp dict_delete(%HashDict{root: root, size: size}, key) do
@doc false
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
@@ -232,7 +234,14 @@ defimpl Enumerable, for: HashDict do
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
def get(dict, key) do
HashDict.get(dict, key, nil)
end
def get_and_update(dict, key, fun) do
{get, update} = fun.(HashDict.get(dict, key, nil))
{get, HashDict.put(dict, key, update)}
end
end
defimpl Collectable, for: HashDict do
@@ -248,3 +257,11 @@ defimpl Collectable, for: HashDict do
end}
end
end
defimpl Inspect, for: HashDict do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
+10 -1
View File
@@ -21,7 +21,8 @@ defmodule HashSet do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
# Inline common instructions
@@ -264,3 +265,11 @@ defimpl Collectable, for: HashSet do
end}
end
end
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+43 -108
View File
@@ -8,7 +8,7 @@ defprotocol Inspect do
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the record
followed by the inspecting options, represented by the struct
`Inspect.Opts`.
Inspection is done using the functions available in `Inspect.Algebra`.
@@ -210,33 +210,30 @@ defimpl Inspect, for: BitString do
end
defp escape(<<>>, _char, binary), do: binary
@doc false
# also used by Regex
def escape_char(char) when char in ?\000..?\377,
do: octify(char)
def escape_char(char), do: hexify(char)
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
# Also used by Regex
def escape_char(0) do
<<?\\, ?0>>
end
defp hexify(char) when char < 0x10000 do
def escape_char(char) when char < 0x100 do
<<a::4, b::4>> = <<char::size(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::size(16)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end
defp hexify(char) when char < 0x1000000 do
def escape_char(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(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
defp to_hex(c) when c in 10..15, do: ?A+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary
@@ -274,25 +271,6 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]"
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
@@ -300,9 +278,9 @@ defimpl Inspect, for: List do
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
surround_many("[", thing, "]", opts, &keyword/2)
true ->
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
surround_many("[", thing, "]", opts, &to_doc/2)
end
end
@@ -349,75 +327,25 @@ defimpl Inspect, for: Tuple do
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
if opts.records do
record_inspect(tuple, opts)
else
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
end
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = Tuple.to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
surround_record(name, fields, tail, opts)
else
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
end
end
defp record_fields(name) do
case Atom.to_string(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
rescue
_ -> nil
end
_ -> nil
end
end
defp surround_record(name, fields, tail, opts) do
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp zip_fields([{key, _}|tk], [value|tv]) do
case Atom.to_string(key) do
"_" <> _ -> zip_fields(tk, tv)
key -> [{key, value}|zip_fields(tk, tv)]
end
end
defp zip_fields([], []) do
[]
end
defp keyword({k, v}, opts) do
concat(k <> ": ", to_doc(v, opts))
surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
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
map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
end
defp traverse_fun(list, opts) do
defp traverse_fun(list) do
if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts)
&Inspect.List.keyword/2
else
&to_map(&1, opts)
&to_map/2
end
end
@@ -430,8 +358,28 @@ defimpl Inspect, for: Map do
end
defimpl Inspect, for: Integer do
def inspect(thing, _opts) do
Integer.to_string(thing)
def inspect(thing, %Inspect.Opts{base: base}) do
Integer.to_string(thing, base_to_value(base))
|> prepend_prefix(base)
end
defp base_to_value(base) do
case base do
:binary -> 2
:decimal -> 10
:octal -> 8
:hex -> 16
end
end
defp prepend_prefix(value, :decimal), do: value
defp prepend_prefix(value, base) do
prefix = case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end
prefix <> value
end
end
@@ -443,13 +391,12 @@ end
defimpl Inspect, for: Regex do
def inspect(regex, _opts) do
delim =?/
delim = ?/
concat ["~r",
<<delim, escape(regex.source, delim)::binary, delim>>,
regex.opts]
end
defp escape(bin, term),
do: escape(bin, <<>>, term)
@@ -575,15 +522,3 @@ defimpl Inspect, for: Any do
end
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
defimpl Inspect, for: HashSet do
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+157 -137
View File
@@ -4,36 +4,55 @@ defmodule Inspect.Opts do
The following fields are available:
* `:structs` - when false, structs are not formatted by the inspect protocol,
they are instead printed as maps, defaults to true;
* `:structs` - when false, structs are not formatted by the inspect
protocol, they are instead printed as maps, defaults to true.
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped; when `:as_binaries` all
binaries will be printed in bit syntax; when the default
`:infer`, the binary will be printed as a string if it is
printable, otherwise in bit syntax;
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped.
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
non-printable elements will be escaped; when `:as_lists` all
lists will be printed as lists; when the default `:infer`, the
list will be printed as a char list if it is printable,
otherwise as list;
When `:as_binaries` all binaries will be printed in bit syntax.
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
and lists, does not apply to strings nor char lists, defaults to 50;
When the default `:infer`, the binary will be printed as a string if it
is printable, otherwise in bit syntax.
* `:pretty` - if set to true enables pretty printing, defaults to false;
* `:char_lists` - when `:as_char_lists` all lists will be printed as char
lists, non-printable elements will be escaped.
When `:as_lists` all lists will be printed as lists.
When the default `:infer`, the list will be printed as a char list if it
is printable, otherwise as list.
* `:limit` - limits the number of items that are printed for tuples,
bitstrings, and lists, does not apply to strings nor char lists, defaults
to 50.
* `:pretty` - if set to true enables pretty printing, defaults to false.
* `:width` - defaults to the 80 characters, used when pretty is true or
when printing to IO devices.
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
to :decimal
* `:width` - defaults to the 80 characters;
"""
defstruct structs: true :: boolean,
binaries: :infer :: :infer | :as_binaries | :as_strings,
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
limit: 50 :: pos_integer,
width: 80 :: pos_integer | :infinity,
pretty: false :: boolean,
records: true :: boolean
defstruct structs: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
width: 80,
base: :decimal,
pretty: false
@type t :: %__MODULE__{
structs: boolean,
binaries: :infer | :as_binaries | :as_strings,
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
base: :decimal | :binary | :hex | :octal,
pretty: boolean}
end
defmodule Inspect.Algebra do
@@ -54,8 +73,8 @@ defmodule Inspect.Algebra do
elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
iex> Inspect.Algebra.format(doc, 80)
["foo"]
The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document
@@ -65,15 +84,15 @@ defmodule Inspect.Algebra do
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
iex> Inspect.Algebra.format(doc, 80)
["a", " ", "b"]
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
iex> Inspect.Algebra.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
@@ -160,29 +179,32 @@ defmodule Inspect.Algebra do
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, %Inspect.Opts{} = opts) when is_tuple(arg) and is_atom(elem(arg, 0)) do
# Remove this code when protocols+records are removed"
if opts.records do
try do
Inspect.inspect(arg, opts)
rescue
_ -> Inspect.Tuple.inspect(arg, opts)
end
else
Inspect.Tuple.inspect(arg, opts)
end
end
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
if opts.structs do
try do
Inspect.inspect(map, opts)
rescue
e ->
res = Inspect.Map.inspect(map, opts)
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
# we need to trap the inspected messages to guarantee
# we won't try to render any failed instruct when building
# the error message.
if Process.get(:inspect_trap) do
Inspect.Map.inspect(map, opts)
else
try do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
after
Process.delete(:inspect_trap)
end
end
end
else
Inspect.Map.inspect(map, opts)
@@ -194,8 +216,7 @@ defmodule Inspect.Algebra do
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
Returns a document entity used to represent nothingness.
## Examples
@@ -207,14 +228,13 @@ defmodule Inspect.Algebra do
def empty, do: :doc_nil
@doc """
Concatenates two document entities. Takes two arguments:
left doc and right doc. Returns a DocCons doc
Concatenates two document entities.
## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
iex> doc = Inspect.Algebra.concat "hello", "world"
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]
"""
@spec concat(t, t) :: doc_cons
@@ -230,15 +250,16 @@ defmodule Inspect.Algebra do
folddoc(docs, &concat(&1, &2))
end
@doc """
Nests document entity `x` positions deep. Nesting will be
appended to the line breaks.
@doc ~S"""
Nests document entity `x` positions deep.
Nesting will be appended to the line breaks.
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.format(doc, 5)
["hello", "\n ", "world"]
"""
@spec nest(t, non_neg_integer) :: doc_nest
@@ -251,24 +272,26 @@ defmodule Inspect.Algebra do
end
@doc ~S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
Document entity representing a break.
This break can be rendered as a linebreak or as spaces,
depending on the `mode` of the chosen layout or the provided
separator.
## Examples
Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
iex> Inspect.Algebra.format(doc, 80)
["a", " ", "b"]
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
iex> Inspect.Algebra.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
"""
@spec break(binary) :: doc_break
@@ -311,10 +334,10 @@ defmodule Inspect.Algebra do
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
iex> Inspect.Algebra.format(doc, 80)
["Hello,", " ", "A", " ", "B"]
iex> Inspect.Algebra.format(doc, 6)
["Hello,", "\n", "A", " ", "B"]
"""
@spec group(t) :: doc_group
@@ -328,8 +351,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
iex> Inspect.Algebra.format(doc, 80)
["Hughes", " ", "Wadler"]
"""
@spec space(t, t) :: doc_cons
@@ -341,8 +364,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]
"""
@spec line(t, t) :: doc_cons
@@ -358,8 +381,8 @@ defmodule Inspect.Algebra do
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
iex> Inspect.Algebra.format(doc, 80)
["A", "!", "B"]
"""
@spec folddoc([t], ((t, t) -> t)) :: t
@@ -379,8 +402,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
iex> Inspect.Algebra.format(doc, 3)
["[", "a", "\n ", "b", "]"]
"""
@spec surround(binary, t, binary) :: t
@@ -389,55 +412,63 @@ defmodule Inspect.Algebra do
end
@doc ~S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
Maps and glues a collection of items.
It uses the given left and right as surrounding and a separator for
each item. A limit can be passed which, once reached, stops gluing
and outputs "..." instead.
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 5)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 20)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
def surround_many(left, [], right, _, _fun, _) do
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun, sep) do
surround(left, surround_many(docs, limit, fun, sep), right)
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end
defp surround_many(_, 0, _fun, _sep) do
defp do_surround_many(_, 0, _opts, _fun, _sep) do
"..."
end
defp surround_many([h], _limit, fun, _sep) do
fun.(h)
defp do_surround_many([h], limit, opts, fun, _sep) do
fun.(h, %{opts | limit: limit})
end
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
limit = decrement(limit)
glue(
concat(fun.(h), sep),
surround_many(t, decrement(limit), fun, sep)
concat(fun.(h, %{opts | limit: limit}), sep),
do_surround_many(t, limit, opts, fun, sep)
)
end
defp surround_many([h|t], _limit, fun, _sep) do
defp do_surround_many([h|t], limit, opts, fun, _sep) do
limit = decrement(limit)
glue(
concat(fun.(h), @tail_separator),
fun.(t)
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
fun.(t, %{opts | limit: limit})
)
end
@@ -445,50 +476,45 @@ defmodule Inspect.Algebra do
defp decrement(counter), do: counter - 1
@doc """
The pretty printing function.
The formatting function.
Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the
and returns an IO data representation of the best layout for the
document to fit in the given width.
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
render(sdoc)
@spec format(t, non_neg_integer | :infinity) :: iodata
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
# Rendering and internal helpers
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
defp fits?(w, _) when w < 0, do: false
defp fits?(_, []), do: true
defp fits?(_, [{_, _, :doc_line} | _]), do: true
defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(s)} | t]) do
defp format(_, _, []), do: []
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
defp format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do
@@ -500,10 +526,4 @@ defmodule Inspect.Algebra do
defp indent(0), do: @newline
defp indent(i), do: @newline <> :binary.copy(" ", i)
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
IO.iodata_to_binary sdoc
end
end
+9 -7
View File
@@ -9,9 +9,10 @@ defmodule Integer do
Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
"""
defmacro odd?(n) do
defmacro is_odd(n) do
quote do: (unquote(n) &&& 1) == 1
end
@@ -19,9 +20,10 @@ defmodule Integer do
Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
"""
defmacro even?(n) do
defmacro is_even(n) do
quote do: (unquote(n) &&& 1) == 0
end
@@ -99,7 +101,7 @@ defmodule Integer do
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
@spec to_string(integer, 2..36) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@@ -115,7 +117,7 @@ defmodule Integer do
'7'
"""
@spec to_char_list(integer) :: list
@spec to_char_list(integer) :: char_list
def to_char_list(number) do
:erlang.integer_to_list(number)
end
@@ -132,7 +134,7 @@ defmodule Integer do
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: list
@spec to_char_list(integer, 2..36) :: char_list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
+87 -49
View File
@@ -21,12 +21,12 @@ defmodule IO do
the atom must be the name of a registered process. However,
there are three exceptions for this rule:
* `:standard_io` - when the `:standard_io` atom is given,
it is treated as a shortcut for `Process.group_leader`
* `:standard_io` - when the `:standard_io` atom is given,
it is treated as a shortcut for `Process.group_leader`
* `:stdio` - is a shortcut for `:standard_io`
* `:stdio` - is a shortcut for `:standard_io`
* `:stderr` - is a shortcut for `:standard_error`
* `:stderr` - is a shortcut for `:standard_error`
"""
@@ -43,20 +43,29 @@ defmodule IO do
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
* `data` - The input characters.
It returns:
* `:eof` - End of file was encountered.
* `data` - the input characters
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `:eof` - end of file was encountered
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
@spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line)
def read(device, :all) do
do_read_all(map_dev(device), "")
end
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
@@ -65,21 +74,41 @@ defmodule IO do
:io.get_chars(map_dev(device), '', count)
end
defp do_read_all(mapped_dev, acc) do
case :io.get_line(mapped_dev, "") do
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
:eof -> acc
other -> other
end
end
@doc """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
* `data` - The input characters.
It returns:
* `:eof` - End of file was encountered.
* `data` - the input characters
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `:eof` - end of file was encountered
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
@spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
def binread(device \\ group_leader, chars_or_line)
def binread(device, :all) do
do_binread_all(map_dev(device), "")
end
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{:ok, data} -> data
@@ -94,6 +123,14 @@ defmodule IO do
end
end
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
end
end
@doc """
Writes the given argument to the given device.
@@ -119,6 +156,9 @@ defmodule IO do
as a binary, no unicode conversion happens.
Check `write/2` for more information.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
"""
@spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
@@ -139,17 +179,13 @@ defmodule IO do
@doc """
Inspects and writes the given argument to the device.
It sets by default pretty printing to true and returns
the item itself.
Note this function does not use the IO device width
because some IO devices does not implement the
appropriate functions. Setting the width must be done
explicitly by passing the `:width` option.
It enables pretty printing by default with width of
80 characters. The width can be changed by explicitly
passing the `:width` option.
## Examples
IO.inspect Process.list
IO.inspect Process.list, width: 40
"""
@spec inspect(term, Keyword.t) :: term
@@ -162,8 +198,9 @@ defmodule IO do
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
puts device, Kernel.inspect(item, opts)
opts = struct(Inspect.Opts, opts)
iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
puts device, iodata
item
end
@@ -174,13 +211,13 @@ defmodule IO do
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
* `data` - the input characters
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
@@ -208,14 +245,14 @@ defmodule IO do
@doc """
Reads a line from the IO device. It returns:
* `data` - The characters in the line terminated
by a LF (or end of file).
* `data` - the characters in the line terminated
by a LF (or end of file)
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
def gets(device \\ group_leader(), prompt) do
@@ -248,8 +285,7 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
struct IO.Stream,
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
end
@doc """
@@ -264,11 +300,13 @@ defmodule IO do
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
Finally, do not use this function on IO devices in unicode
mode as it will return the wrong result.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
struct IO.Stream,
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
end
@doc """
@@ -356,11 +394,11 @@ defmodule IO do
def each_stream(device, what) do
case read(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
@@ -368,11 +406,11 @@ defmodule IO do
def each_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
+49 -68
View File
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(Atom.to_char_list(name))) do
defp format_sequence(unquote(name)) do
unquote(name)()
end
end
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
import IO.ANSI.Sequence
@doc """
Checks whether the default I/O device is a terminal or a file.
@typep ansicode :: atom()
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
@type ansidata :: ansilist() | ansicode() | binary()
Used to identify whether printing ANSI escape sequences will likely
be displayed as intended. This is checked by sending a message to
the group leader. In case the group leader does not support the message,
it will likely lead to a timeout (and a slow down on execution time).
@doc """
Checks if ANSI coloring is supported and enabled on this machine.
This function simply reads the configuration value for
`:ansi_enabled` in the `:elixir` application. The value is by
default false unless Elixir can detect during startup that
both `stdout` and `stderr` are terminals.
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device \\ :erlang.group_leader) do
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
@spec enabled? :: boolean
def enabled? do
Application.get_env(:elixir, :ansi_enabled, false)
end
@doc "Resets all attributes"
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
@doc "Clear screen"
defsequence :clear, "2", "J"
defp escape_sequence(other) do
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
The named sequences are represented by atoms.
It will also append a `%{reset}` to the string. If you don't want this
behaviour, use `escape_fragment/2`.
It will also append an `IO.ANSI.reset` to the chardata when a conversion is
performed. If you don't want this behaviour, use `format_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
[[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit \\ terminal?) do
{rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do
rendered <> reset
else
rendered
end
def format(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, :maybe)
end
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
The named sequences are represented by atoms.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit \\ terminal?) do
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, false)
end
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
sequences =
buffer
|> Enum.reverse()
|> :string.tokens(',')
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|> Enum.reverse()
if emit and sequences != [] do
do_escape(t, emit, true, nil, sequences ++ acc)
else
do_escape(t, emit, emitted, nil, acc)
end
defp do_format([term | rest], rem, acc, emit, append_reset) do
do_format(term, [rest | rem], acc, emit, append_reset)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
do_escape(t, emit, emitted, [h|buffer], acc)
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = IO.iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, "missing } for escape fragment #{buffer}"
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
do_format([], rem, acc, false, append_reset)
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, [], acc)
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
do_format([], rem, [acc | [term]], emit, append_reset)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, nil, [h|acc])
defp do_format([], [next | rest], acc, emit, append_reset) do
do_format(next, rest, acc, emit, append_reset)
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
defp do_format([], [], acc, true, true) do
[acc | IO.ANSI.reset]
end
defp do_format([], [], acc, _emit, _append_reset) do
acc
end
end
+219 -116
View File
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
The supported values are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
* `:enabled` - toggles coloring on and off (true)
* `:doc_bold` - bold text (bright)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_table_heading` - style for table headings
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
Values for the color settings are strings with
comma-separated ANSI values.
"""
def default_options do
[enabled: true,
doc_code: "cyan,bright",
doc_inline_code: "cyan",
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright",
doc_bold: "bright",
doc_underline: "underline",
width: 80]
[enabled: true,
doc_bold: [:bright],
doc_code: [:cyan, :bright],
doc_headings: [:yellow, :bright],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow, :bright],
doc_underline: [:underline],
width: 80]
end
@doc """
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, options)
newline_after_block
end
@doc """
Prints the documentation body.
In addition to the priting string, takes a set of options
In addition to the printing string, takes a set of options
defined in `default_options/1`.
"""
def print(doc, options \\ []) do
@@ -56,52 +59,63 @@ defmodule IO.ANSI.Docs do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("", options)
|> process([], "", options)
end
defp process([], _indent, _options), do: nil
defp process([], text, indent, options) do
write_text(text, indent, options)
end
defp process(["# " <> heading | rest], _indent, options) do
defp process(["# " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h1(String.strip(heading), options)
process(rest, "", options)
process(rest, [], "", options)
end
defp process(["## " <> heading | rest], _indent, options) do
defp process(["## " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h2(String.strip(heading), options)
process(rest, "", options)
process(rest, [], "", options)
end
defp process(["### " <> heading | rest], indent, options) do
defp process(["### " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h3(String.strip(heading), indent, options)
process(rest, indent, options)
process(rest, [], "", options)
end
defp process(["" | rest], indent, options) do
process(rest, indent, options)
defp process(["" | rest], text, indent, options) do
write_text(text, indent, options)
process(rest, [], indent, options)
end
defp process([" " <> line | rest], indent, options) do
defp process([" " <> line | rest], text, indent, options) do
write_text(text, indent, options)
process_code(rest, [line], indent, options)
end
defp process([line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
process_list(item, rest, count, indent, options)
_ ->
process_text(rest, [line], indent, false, options)
defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0, :infinity)
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
write_text(text, indent, options)
process_table(all, indent, options)
else
case stripped 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
end
end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{rest, acc}
end
## Headings
defp write_h1(heading, options) do
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
defp write_h2(heading, options) do
write(:doc_headings, heading, options)
newline_after_block
end
defp write_h3(heading, indent, options) do
IO.write(indent)
write(:doc_headings, heading, options)
newline_after_block
end
## Lists
defp process_list(line, rest, count, indent, options) do
IO.write indent <> "• "
{contents, rest, done} = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, options)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, options)
defp process_list(entry, line, rest, count, indent, options) do
# The first list always win some extra padding
if indent == "", do: entry = " " <> entry
new_indent = indent <> String.duplicate(" ", String.length(entry))
{contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
if done, do: newline_after_block()
process(rest, [], indent, options)
end
# Process the thing after a list item entry. It can be either:
#
# * Continuation of the list
# * A nested list
# * The end of the list
#
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
case list_next(line, count) do
:done -> {Enum.reverse(acc), [line|rest], false}
chopped -> process_list_next(rest, count, false, [chopped|acc])
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}
:list -> {Enum.reverse(acc), [line | rest], false}
end
end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
{Enum.reverse(acc), rest, false}
defp process_list_next([], _count, _max, acc) do
{Enum.reverse(acc), [], true}
end
defp process_list_next(["" | rest], count, _done, acc) do
process_list_next(rest, count, true, [""|acc])
end
defp process_list_next(rest, _count, done, acc) do
{Enum.reverse(acc), rest, done}
end
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
defp list_next(line, 0), do: chop(line, 2)
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
defp list_next(line, _acc), do: line
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
defp chop(line, _acc), do: line
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
process(doc, indent, options)
end
defp process_text([], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
end
defp process_text([line | rest], para, indent, true, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(Enum.reverse(para), indent, true, options)
process_list(item, rest, count, indent, options)
defp process_list_next_kind(stripped, rest, count, next_count) 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 ->
:list
{"", [" " <> _ | _]} ->
:next
{"", _} ->
:done
_ ->
process_text(rest, [line | para], indent, true, options)
:next
end
end
defp process_text([line | rest], para, indent, from_list, options) do
process_text(rest, [line | para], indent, from_list, options)
## Text
defp write_text(text, indent, options) do
case Enum.reverse(text) do
[:no_wrap|rest] -> write_text(rest, indent, options, true)
rest -> write_text(rest, indent, options, false)
end
end
defp write_text(lines, indent, from_list, options) do
defp write_text([], _indent, _options, _no_wrap) do
:ok
end
defp write_text(lines, indent, options, no_wrap) do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
unless from_list, do: IO.puts(IO.ANSI.reset)
unless no_wrap, do: newline_after_block()
end
## Code blocks
@@ -204,28 +208,124 @@ defmodule IO.ANSI.Docs do
end
# Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, options) do
defp process_code(["", " " <> line | rest], code, indent, options) do
process_code(rest, [line, "" | code], indent, options)
end
defp process_code([ " " <> line | rest ], code, indent, options) do
defp process_code([" " <> line | rest], code, indent, options) do
process_code(rest, [line|code], indent, options)
end
defp process_code(rest, code, indent, options) do
write_code(code, indent, options)
process(rest, indent, options)
process(rest, [], indent, options)
end
defp write_code(code, indent, options) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
newline_after_block
end
## Tables
defp process_table(lines, indent, options) do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, [], indent, options)
end
defp table_lines(lines, options) do
lines = Enum.map(lines, &split_into_columns(&1, options))
count = Enum.map(lines, &length/1) |> Enum.max
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
widths = for line <- lines, do:
(for {_col, length} <- line, do: length)
col_widths = Enum.reduce(widths,
List.duplicate(0, count),
&max_column_widths/2)
render_table(lines, col_widths, options)
end
defp split_into_columns(line, options) do
line
|> String.strip(?|)
|> String.strip()
|> String.split(~r/\s\|\s/)
|> Enum.map(&render_column(&1, options))
end
defp render_column(col, options) do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
{col, length_without_escape(col, 0)}
end
defp pad_to_number_of_columns(cols, col_count),
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
defp max_column_widths(cols, widths),
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
# 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
draw_table_row(combined, options, :heading)
render_table(rest, widths, options)
else
draw_table_row(combined, options)
render_table([second | rest], widths, options)
end
end
defp render_table([first | rest], widths, options) do
combined = Enum.zip(first, widths)
draw_table_row(combined, options)
render_table(rest, widths, options)
end
defp render_table([], _, _),
do: nil
defp table_header?(row), do:
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
defp draw_table_row(cols_and_widths, options, heading \\ false) do
columns =
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
col <> String.duplicate(" ", width - length)
end)
if heading do
write(:doc_table_heading, columns, options)
else
IO.puts columns
end
end
defp is_table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x, line)
end
## Helpers
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 IO.ANSI.reset
IO.puts [color(style, options), string, IO.ANSI.reset]
end
defp write_with_wrap([], _available, _indent, _first) do
@@ -270,7 +370,7 @@ defmodule IO.ANSI.Docs do
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
{_, rest} -> length_without_escape(rest, count + 1)
{_, rest} -> length_without_escape(rest, count + 1)
nil -> count
end
end
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
defp escape_underlines_in_link(text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
true -> Regex.replace(~r{_}, text, "\\\\_")
_ -> text
end
end
@@ -363,14 +461,19 @@ defmodule IO.ANSI.Docs do
[color_for(h, options)|t]
end
defp color_for("`", colors), do: color(:doc_inline_code, colors)
defp color_for("_", colors), do: color(:doc_underline, colors)
defp color_for("*", colors), do: color(:doc_bold, colors)
defp color_for("**", colors), do: color(:doc_bold, colors)
defp color_for(mark, colors) do
case mark do
"`" -> color(:doc_inline_code, colors)
"_" -> color(:doc_underline, colors)
"*" -> color(:doc_bold, colors)
"**" -> color(:doc_bold, colors)
end
end
defp color(style, colors) do
color = colors[style]
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{color}}", enabled)
IO.ANSI.format_fragment(color, colors[:enabled])
end
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
end
+11 -4
View File
@@ -14,14 +14,21 @@ defmodule IO.Stream do
The following fields are public:
* `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 amount of bytes
* `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 amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
@type t :: %__MODULE__{}
@doc false
def __build__(device, raw, line_or_bytes) do
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
end
defimpl Collectable do
def empty(stream) do
stream
@@ -50,7 +57,7 @@ defmodule IO.Stream do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
end
def count(_stream) do
+621 -607
View File
File diff suppressed because it is too large Load Diff
+82 -62
View File
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
@blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false}
pa: [], pz: [], verbose_compile: false}
@doc """
This is the API invoked by Elixir boot process.
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = parse_argv(argv)
:elixir_code_server.cast({:paths, config.pa, config.pz})
System.argv(argv)
run fn ->
run fn _ ->
errors = process_commands(config)
if errors != [] do
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
by escripts generated by Elixir.
"""
def run(fun, halt \\ true) do
try do
fun.()
if halt do
at_exit(0)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, reason, System.stacktrace)
System.halt(1)
res = exec_fun(fun, {:ok, 0})
if elem(res, 0) == :shutdown or halt do
{_, int} = at_exit(res)
System.halt(int)
end
end
@doc """
Parses ARGV returning the CLI config and trailing args.
"""
@doc false
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc """
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
@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
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(status) do
defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit)
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
for hook <- hooks do
try do
hook.(status)
catch
kind, reason ->
print_error(kind, reason, System.stacktrace)
defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
parent = self()
{pid, ref} =
spawn_monitor fn ->
try do
fun.(elem(res, 1))
catch
:exit, {:shutdown, int} when is_integer(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}}
exit(reason)
kind, reason ->
stack = System.stacktrace
print_error(kind, reason, stack)
send parent, {self, {:shutdown, 1}}
:erlang.raise(kind, reason, stack)
else
_ ->
send parent, {self, res}
end
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
receive do
{^pid, res} ->
:erlang.demonitor(ref, [:flush])
res
{:DOWN, ^ref, _, _, other} ->
print_error({:EXIT, pid}, other, [])
{:shutdown, 1}
end
end
defp shared_option?(list, config, callback) do
@@ -103,7 +113,8 @@ defmodule Kernel.CLI do
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
@@ -129,17 +140,19 @@ defmodule Kernel.CLI do
end
defp parse_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_patha/1)
parse_shared t, %{config | pa: config.pa ++ paths}
end
defp parse_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_pathz/1)
parse_shared t, %{config | pz: config.pz ++ paths}
end
defp parse_shared(["--app", h|t], config) do
parse_shared t, %{config | commands: &[{:app, h}|&1]}
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
end
defp parse_shared(["--no-halt"|t], config) do
@@ -162,7 +175,7 @@ defmodule Kernel.CLI do
parse_shared t, config
end
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
parse_shared t, config
end
@@ -170,6 +183,14 @@ defmodule Kernel.CLI do
{list, config}
end
defp expand_code_path(path) do
path = Path.expand(path)
case Path.wildcard(path) do
[] -> [to_char_list(path)]
list -> Enum.map(list, &to_char_list/1)
end
end
# Process init options
defp parse_argv(["--"|t], config) do
@@ -240,7 +261,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, &parse_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
parse_compiler t, %{config | compile: [pattern | config.compile]}
end
end
@@ -315,7 +336,7 @@ defmodule Kernel.CLI do
end
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
if File.regular?(file) do
wrapper fn -> Code.require_file(file) end
else
{:error, "No file named #{file}"}
@@ -323,9 +344,7 @@ defmodule Kernel.CLI do
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
files = filter_patterns(pattern)
if files != [] do
wrapper fn -> Enum.map files, &Code.require_file(&1) end
@@ -335,9 +354,7 @@ defmodule Kernel.CLI do
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
files = filter_patterns(pattern)
if files != [] do
wrapper fn -> Kernel.ParallelRequire.files(files) end
@@ -347,9 +364,10 @@ defmodule Kernel.CLI do
end
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
# If ensuring the dir returns an error no files will be found.
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
case match_regular_files(patterns) do
case filter_multiple_patterns(patterns) do
{:ok, []} ->
{:error, "No files matched provided patterns"}
{:ok, files} ->
@@ -363,9 +381,13 @@ defmodule Kernel.CLI do
end
end
defp match_regular_files(patterns) do
defp filter_patterns(pattern) do
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
end
defp filter_multiple_patterns(patterns) do
matched_files = Enum.map patterns, fn(pattern) ->
case Path.wildcard(pattern) do
case filter_patterns(pattern) do
[] -> {:missing, pattern}
files -> {:ok, files}
end
@@ -380,16 +402,14 @@ defmodule Kernel.CLI do
&elem(&1, 1)
if missing_patterns == [] do
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
{:ok, files}
{:ok, Enum.uniq(Enum.concat(files))}
else
{:missing, Enum.uniq(missing_patterns)}
end
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
@@ -400,8 +420,8 @@ defmodule Kernel.CLI do
# the actual Elixir executable.
case :os.type() do
{:win32, _} ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
base = Path.rootname(exec)
if File.regular?(base), do: base, else: exec
_ ->
exec
end
+2 -2
View File
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{:module, _} -> []
{:module, _} -> :ok
{:error, _} ->
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref
send parent, {:waiting, module, self(), ref, module}
send parent, {:waiting, :module, self(), ref, module}
:erlang.garbage_collect(self)
receive do
{^ref, :ready} -> :ok
+46 -38
View File
@@ -18,20 +18,22 @@ defmodule Kernel.LexicalTracker do
Returns all remotes linked to in this lexical scope.
"""
def remotes(arg) do
# If the module is compiled from a function, its lexical
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
else
[]
end
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
end
@doc """
Gets the destination the lexical scope is meant to
compile to.
"""
def dest(arg) do
:gen_server.call(to_pid(arg), :dest, @timeout)
end
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, :__lexical_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
val
end
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
def start_link(dest) do
:gen_server.start_link(__MODULE__, dest, [])
end
@doc false
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
# Callbacks
def init([]) do
{:ok, :ets.new(:lexical, [:protected])}
def init(dest) do
{:ok, {:ets.new(:lexical, [:protected]), dest}}
end
def handle_call(:ets, _from, d) do
{:reply, d, d}
@doc false
def handle_call(:ets, _from, {d, dest}) do
{:reply, d, {d, dest}}
end
def handle_call(request, _from, d) do
{:stop, {:bad_call, request}, d}
def handle_call(:dest, _from, {d, dest}) do
{:reply, dest, {d, dest}}
end
def handle_cast({:remote_dispatch, module}, d) do
def handle_call(request, _from, {d, dest}) do
{:stop, {:bad_call, request}, {d, dest}}
end
def handle_cast({:remote_dispatch, module}, {d, dest}) do
add_module(d, module)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:import_dispatch, module}, d) do
def handle_cast({:import_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @import)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:alias_dispatch, module}, d) do
def handle_cast({:alias_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @alias)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:add_import, module, line, warn}, d) do
def handle_cast({:add_import, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @import)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:add_alias, module, line, warn}, d) do
def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @alias)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast(:stop, d) do
{:stop, :normal, d}
def handle_cast(:stop, {d, dest}) do
{:stop, :normal, {d, dest}}
end
def handle_cast(msg, d) do
{:stop, {:bad_cast, msg}, d}
def handle_cast(msg, {d, dest}) do
{:stop, {:bad_cast, msg}, {d, dest}}
end
def handle_info(_msg, d) do
{:noreply, d}
@doc false
def handle_info(_msg, {d, dest}) do
{:noreply, {d, dest}}
end
def terminate(_reason, _d) do
@doc false
def terminate(_reason, _state) do
:ok
end
def code_change(_old, d, _extra) do
{:ok, d}
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
# Callbacks helpers
+45 -38
View File
@@ -15,63 +15,69 @@ defmodule Kernel.ParallelCompiler do
is set to `true` and there is a warning, this function will fail
with an exception.
This function receives a set of callbacks as options:
This function accepts the following 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
* `: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
* `: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 writen to
`dest`, use `files_to_path/3` instead.
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks \\ [])
def files(files, options \\ [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
def files(files, options) when is_list(options) do
spawn_compilers(files, nil, options)
end
@doc """
Compiles the given files to the given path.
Read `files/2` for more information.
"""
def files_to_path(files, path, callbacks \\ [])
def files_to_path(files, path, options \\ [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
spawn_compilers(files, path, options)
end
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
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_compilers(files, files, path, callbacks, [], [], schedulers, [])
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result
:error -> exit(1)
:error -> exit({:shutdown, 1})
end
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
send h, {ref, :ready}
waiting = List.keydelete(waiting, h, 1)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
parent = self()
{pid, ref} =
@@ -85,10 +91,10 @@ defmodule Kernel.ParallelCompiler do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
exit(try do
if output do
_ = if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
:elixir_compiler.file(h, Keyword.get(options, :dest))
end
{:compiled, h}
catch
@@ -97,31 +103,31 @@ defmodule Kernel.ParallelCompiler do
end)
end
spawn_compilers(t, original, output, callbacks, waiting,
spawn_compilers(t, original, output, options, waiting,
[{pid, ref, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, 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_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn {child, _, _} ->
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
send child, {ref, :release}
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
receive do
{:struct_available, module} ->
available = for {:struct, pid, _, waiting_module} <- waiting,
@@ -129,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:struct, module}|result])
{:module_available, child, ref, file, module, binary} ->
if callback = Keyword.get(callbacks, :each_module) do
if callback = Keyword.get(options, :each_module) do
callback.(file, module, binary)
end
@@ -145,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:module, module}|result])
{:waiting, kind, child, ref, on} ->
@@ -158,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
waiting = [{kind, child, ref, on}|waiting]
end
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
if callback = Keyword.get(callbacks, :each_file) do
if callback = Keyword.get(options, :each_file) do
callback.(file)
end
@@ -170,11 +176,11 @@ defmodule Kernel.ParallelCompiler do
new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0)
new_waiting = List.keydelete(waiting, down_pid, 1)
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
{:DOWN, down_ref, :process, _down_pid, reason} ->
handle_failure(down_ref, reason, entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end
end
@@ -184,7 +190,7 @@ defmodule Kernel.ParallelCompiler do
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
exit(1)
exit({:shutdown, 1})
end
end
@@ -209,7 +215,8 @@ defmodule Kernel.ParallelCompiler do
IO.puts Exception.format(:exit, reason, [])
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
+114 -100
View File
@@ -114,24 +114,31 @@ defmodule Kernel.SpecialForms do
defmodule User do
def __struct__ do
%{name: "josé", age: 27}
%{name: "john", age: 27}
end
end
Now a struct can be created as follow:
In practice though, structs are usually defined with the
`Kernel.defstruct/1` macro:
defmodule User do
defstruct name: "john", age: 27
end
Now a struct can be created as follows:
%User{}
Underneath, a struct is just a map with a `__struct__` field
pointing to the User module:
Underneath a struct is just a map with a `__struct__` field
pointing to the `User` module:
%User{} == %{__struct__: User, name: "josé", age: 27}
%User{} == %{__struct__: User, name: "john", age: 27}
A struct also validates the given keys are part of the defined
A struct also validates that the given keys are part of the defined
struct. The example below will fail because there is no key
`:full_name` in the user struct:
`:full_name` in the `User` struct:
%User{full_name: "José Valim"}
%User{full_name: "john doe"}
Note that a struct specifies a minimum set of keys required
for operations. Other keys can be added to structs via the
@@ -148,8 +155,10 @@ defmodule Kernel.SpecialForms do
compilation time and it will guarantee at runtime the given
argument is a struct, failing with `BadStructError` otherwise.
Check `Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also,
Although structs are maps, by default structs do not implement
any of the protocols implemented for maps. Check
`Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also
see `Kernel.struct/2` for examples on how to create and update
structs dynamically.
"""
@@ -204,7 +213,7 @@ defmodule Kernel.SpecialForms do
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
The utf8, utf16, and utf32 types are for UTF code points. They
The utf8, utf16, and utf32 types are for unicode codepoints. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
@@ -214,8 +223,8 @@ defmodule Kernel.SpecialForms do
alias for binary.
The signedness can also be given as signed or unsigned. The
signedness only matters for matching. If unspecified, it
defaults to unsigned. Example:
signedness only matters for matching and relevant only for
integers. If unspecified, it defaults to unsigned. Example:
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
<<156,102,111,111>>
@@ -232,17 +241,15 @@ defmodule Kernel.SpecialForms do
Here, `val` is interpreted as unsigned.
Signedness is only relevant on integers.
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
latter meaning it will be resolved at VM load time). Many options
can be given by using `-` as separator:
<<102 :: [integer, native], rest :: binary>>
<<102 :: integer-native, rest :: binary>>
Or:
<<102 :: [unsigned, big, integer], rest :: binary>>
<<102 :: unsigned-big-integer, rest :: binary>>
And so on.
@@ -251,43 +258,45 @@ defmodule Kernel.SpecialForms do
Finally, we can also specify size and unit for each segment. The
unit is multiplied by the size to give the effective size of
the segment:
the segment in bits. The default unit for integers, floats,
and bitstrings is 1. For binaries, it is 8.
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
"foo"
Since integers are default, the default unit is 1. The example below
matches because the string "foo" takes 24 bits and we match it
against a segment of 24 bits, 8 of which are taken by the integer
102 and the remaining 16 bits are specified on the rest.
iex> <<102, _rest :: size(16)>> = "foo"
"foo"
We can also match by specifying size and unit explicitly:
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo"
However, if we expect a size of 32, it won't match:
iex> <<102, _rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
In the example above, the first two expressions matches
because the string "foo" takes 24 bits and we are matching
against a segment of 24 bits as well, 8 of which are taken by
the integer 102 and the remaining 16 bits are specified on
the rest. On the last example, we expect a rest with size 32,
which won't match.
Size and unit are not applicable to utf8, utf16, and utf32.
The default size for integers is 8. For floats, it is 64. For
binaries, it is the size of the binary. Only the last binary
in a binary match can use the default size (all others must
have their size specified explicitly). Bitstrings do not have
a default size.
have their size specified explicitly).
Size can also be specified using a syntax shortcut. Instead of
writing `size(8)`, one can write just `8` and it will be interpreted
as `size(8)`
Size and unit can also be specified using a syntax shortcut
when passing integer values:
iex> << 1 :: 3 >> == << 1 :: size(3) >>
true
<< x :: 8 >> == << x :: size(8) >>
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
The default unit for integers, floats, and bitstrings is 1. For
binaries, it is 8.
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
For floats, unit * size must result in 32 or 64, corresponding
For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
"""
defmacro unquote(:<<>>)(args)
@@ -334,28 +343,9 @@ defmodule Kernel.SpecialForms do
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
This choice was done so every time single- or double-quotes are used, we have
a remote call irregardless of the quote contents. This decision is also reflected
a remote call regardless of the quote contents. This decision is also reflected
in the quoted expressions discussed below.
## Runtime (dynamic) behaviour
The result returned by `.` is always specified by the right-side:
iex> x = String
iex> x.downcase("FOO")
"foo"
iex> x.Sample
String.Sample
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
at runtime via `apply/3` and `Module.concat/2`:
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
## Quoted expression
When `.` is used, the quoted expression may take two distinct
@@ -580,7 +570,7 @@ defmodule Kernel.SpecialForms do
defmacro import(module, opts)
@doc """
Returns the current environment information as a `Macro.Env[]` record.
Returns the current environment information as a `Macro.Env` struct.
In the environment you can access the current filename,
line numbers, set up aliases, the current function and others.
@@ -603,6 +593,14 @@ defmodule Kernel.SpecialForms do
"""
defmacro __DIR__
@doc """
Returns the current calling environment as a `Macro.Env` struct.
In the environment you can access the filename, line numbers,
set up aliases, the function and others.
"""
defmacro __CALLER__
@doc """
Accesses an already bound variable in match clauses.
@@ -653,23 +651,28 @@ defmodule Kernel.SpecialForms do
The tuple above represents a function call to `sum` passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple represents metadata;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call);
* The first element of the tuple is always an atom or
another tuple in the same representation.
* The second element of the tuple represents metadata.
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call).
## Options
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file from quote.
Read the Stacktrace information section below for more information;
* `:context` - Sets the resolution context;
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
* `:unquote` - when false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is able to unquote.
* `:location` - when set to `:keep`, keeps the current line and file from
quote. Read the Stacktrace information section below for more
information.
* `:context` - sets the resolution context.
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
given, `unquote` is automatically disabled.
## Quote literals
@@ -1047,9 +1050,9 @@ defmodule Kernel.SpecialForms do
we are passing the representation of the variable `kv`, our
code fails.
This is actually a common pitfall when developing macros. In
practice, we want to avoid doing work at compilation time as
much as possible. That said, let's attempt to improve our macro:
This is actually a common pitfall when developing macros. We are
assuming a particular shape in the macro. We can work around it
by unquoting the variable inside the quoted expression:
defmacro defkv(kv) do
quote do
@@ -1090,7 +1093,7 @@ defmodule Kernel.SpecialForms do
## Examples
Imagine the situation you have a variable `name` and
Imagine the situation you have a variable `value` and
you want to inject it inside some quote. The first attempt
would be:
@@ -1152,10 +1155,10 @@ defmodule Kernel.SpecialForms do
Note generators can also be used to filter as it removes any value
that doesn't match the left side of `<-`:
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
...> String.upcase(name)
...> end
["JOSE", "ERIC"]
["JOHN", "MEG"]
Bitstring generators are also supported and are very useful when you
need to organize bitstring streams:
@@ -1305,21 +1308,12 @@ defmodule Kernel.SpecialForms do
On the other hand, aliases holds some properties:
1) The head element of aliases can be any term;
1. The head element of aliases can be any term that must expand to
an atom at compilation time.
2) The tail elements of aliases are guaranteed to always be atoms;
2. The tail elements of aliases are guaranteed to always be atoms.
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
4) When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
"""
defmacro __aliases__(args)
@@ -1375,7 +1369,27 @@ defmodule Kernel.SpecialForms do
case, clauses that do not explicitly bind a value have the variable
bound to nil.
"""
defmacro case(condition, blocks)
defmacro case(condition, clauses)
@doc """
Evaluates the expression corresponding to the first clause that
evaluates to truth value.
Raises an error if all conditions evaluate to to nil or false.
## Examples
cond do
1 + 1 == 1 ->
"This will never match"
2 * 2 != 4 ->
"Nor this"
true ->
"This will"
end
"""
defmacro cond(clauses)
@doc ~S"""
Evaluate the given expressions and handle any error, exit
@@ -1474,9 +1488,9 @@ defmodule Kernel.SpecialForms do
The catch clause can be used to catch throws values and exits.
try do
exit(1)
exit(:shutdown)
catch
:exit, 1 -> IO.puts "Exited with 1"
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
end
try do
@@ -1634,11 +1648,11 @@ defmodule Kernel.SpecialForms do
The `after` clause can be specified even if there are no match clauses.
There are two special cases for the timeout value given to `after`
* `:infinity` - The process should wait indefinitely for a matching
message, this is the same as not using a timeout.
* `:infinity` - the process should wait indefinitely for a matching
message, this is the same as not using a timeout
* 0 - if there is no matching message in the mailbox, the timeout
will occur immediately.
* 0 - if there is no matching message in the mailbox, the timeout
will occur immediately
## Variables handling
+210 -116
View File
@@ -1,5 +1,5 @@
defmodule Kernel.Typespec do
@moduledoc """
@moduledoc ~S"""
Provides macros and functions for working with typespecs.
Elixir comes with a notation for declaring types and specifications. Elixir is
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
| Fun
| Integer
| List
| Map
| Tuple
| Union
| UserDefined # Described in section "Defining a type"
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
| nonempty_list(Type) # proper non-empty list
| [] # empty list
| [Type] # shorthand for list(Type)
| [...] # shorthand for nonempty_list()
| [Type, ...] # shorthand for nonempty_list(Type)
| [Keyword]
Tuple :: tuple # a tuple of any size
| {} # empty tuple
Map :: map() # map of any size
| %{} # map of any size
| %Struct{} # struct (see defstruct/1)
| %Struct{Keyword}
| %{Keyword}
| %{Pairs}
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| {TList}
| record(Atom) # record (see Record)
| record(Atom, Keyword)
Keyword :: ElixirAtom: Type
| ElixirAtom: Type, Keyword
Pairs :: Type => Type
| Type => Type, Pairs
TList :: Type
| Type, TList
Union :: Type1 | Type2
Union :: Type | Type
### Bit strings
@@ -109,23 +127,25 @@ defmodule Kernel.Typespec do
`term` | `any`
`binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`boolean` | `false` \| `true`
`byte` | `0..255`
`char` | `0..0xffff`
`number` | `integer` &#124; `float`
`char` | `0..0x10ffff`
`number` | `integer` \| `float`
`char_list` | `[char]`
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`iodata` | `iolist` \| `binary`
`iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
`module` | `atom` \| `tuple`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
`timeout` | `:infinity` \| `non_neg_integer`
`no_return` | `none`
`fun` | `(... -> any)`
Some built-in types cannot be expressed with valid syntax according to the
language defined above.
@@ -262,60 +282,18 @@ defmodule Kernel.Typespec do
end
end
## Helpers
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
"""
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
{kind, export} =
case kind do
:type -> {:type, true}
:typep -> {:type, false}
:opaque -> {:opaque, true}
end
module = caller.module
arity = length(vars)
Module.compile_typespec module, kind, type
if export do
Module.compile_typespec(module, :export_type, [{name, arity}])
end
define_doc(caller, kind, name, arity, export)
type
end
defp define_doc(caller, kind, name, arity, export) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
if doc do
if export do
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
else
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
Module.delete_attribute(module, :typedoc)
def define_type(kind, expr, doc \\ nil, env) do
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
end
@doc """
Defines a `spec` by receiving Erlang's typespec.
Defines a `spec` by receiving a typespec expression.
"""
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, {tuple, definition}
end
@doc """
Defines a `callback` by receiving Erlang's typespec.
"""
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, {tuple, definition}
def define_spec(kind, expr, env) do
Module.store_typespec(env.module, kind, {kind, expr, env})
end
@doc """
@@ -324,9 +302,12 @@ defmodule Kernel.Typespec do
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
Enum.any?(Module.get_attribute(module, :type), finder) or
Enum.any?(Module.get_attribute(module, :opaque), finder)
finder = fn {_kind, expr, _doc, _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))
end
@doc """
@@ -334,8 +315,10 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
finder = fn {_kind, expr, _caller} ->
spec_to_signature(expr) == {name, arity}
end
:lists.any(finder, Module.get_attribute(module, :spec))
end
@doc """
@@ -343,8 +326,10 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
finder = fn {_kind, expr, _caller} ->
spec_to_signature(expr) == {name, arity}
end
:lists.any(finder, Module.get_attribute(module, :callback))
end
@doc """
@@ -423,7 +408,7 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{:ok, abstract_code} ->
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
List.flatten(type_docs)
:lists.flatten(type_docs)
_ ->
nil
end
@@ -443,7 +428,7 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{:ok, abstract_code} ->
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
exported_types = List.flatten(exported_types)
exported_types = :lists.flatten(exported_types)
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
cond do
@@ -514,10 +499,40 @@ defmodule Kernel.Typespec do
binary
end
## Helpers
@doc false
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, _, args}, _]}) when is_atom(name),
do: {name, length(args)}
## Macro callbacks
@doc false
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
def defspec(kind, expr, caller) do
Module.store_typespec(caller.module, kind, {kind, expr, caller})
end
@doc false
def deftype(kind, expr, caller) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
Module.delete_attribute(module, :typedoc)
Module.store_typespec(module, kind, {kind, expr, doc, caller})
end
## Translation from Elixir AST to typespec AST
@doc false
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
args =
if is_atom(args) do
[]
@@ -525,31 +540,43 @@ 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
spec = typespec(definition, vars, caller)
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
arity = length(vars)
define_type(caller, kind, type)
{kind, export} =
case kind do
:type -> {:type, true}
:typep -> {:type, false}
:opaque -> {:opaque, true}
end
if not export and doc do
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types")
end
{{kind, {name, arity}, type}, caller.line, export, doc}
end
def deftype(_kind, other, caller) do
def translate_type(_kind, other, _doc, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification: #{type_spec}"
end
@doc false
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
defspec(type, spec, guard, caller)
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
translate_spec(kind, spec, guard, caller)
end
def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
def translate_spec(kind, spec, caller) do
translate_spec(kind, spec, [], caller)
end
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
unless Keyword.keyword?(guard) do
@@ -565,12 +592,11 @@ defmodule Kernel.Typespec do
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
end
code = {{name, Kernel.length(args)}, spec}
Module.compile_typespec(caller.module, type, code)
code
arity = length(args)
{{kind, {name, arity}, spec}, caller.line}
end
defp defspec(_type, spec, _guard, caller) do
defp translate_spec(_kind, spec, _guard, caller) do
spec = Macro.to_string(spec)
compile_error caller, "invalid function type specification: #{spec}"
end
@@ -578,14 +604,14 @@ defmodule Kernel.Typespec do
defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)
Enum.reduce(guard, [], 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)]]
type = {:type, line, :constraint, constraint}
[type|acc]
end) |> Enum.reverse
end, [], guard) |> :lists.reverse
end
## To AST conversion
@@ -634,8 +660,20 @@ defmodule Kernel.Typespec do
end
end
defp typespec_to_ast({:type, _line, :list, args}) do
for arg <- args, do: typespec_to_ast(arg)
defp typespec_to_ast({:type, line, :list, []}) do
{:list, [line: line], []}
end
defp typespec_to_ast({:type, _line, :list, [arg]}) do
[typespec_to_ast(arg)]
end
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
[{:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
[typespec_to_ast(arg), {:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :map, fields}) do
@@ -684,7 +722,11 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({:type, line, :range, [left, right]}) do
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
{:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
end
defp typespec_to_ast({:type, _line, nil, []}) do
[]
end
defp typespec_to_ast({:type, line, name, args}) do
@@ -741,14 +783,14 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(var) do
case Atom.to_string(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
<<"_", c :: binary-size(1), rest :: binary>> ->
String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
<<c :: binary-size(1), rest :: binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
## From AST conversion
## To typespec conversion
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
@@ -779,14 +821,65 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn {k, v} ->
fields = :lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end)
end, fields)
{:type, line(meta), :map, fields}
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
module = Macro.expand(name, caller)
struct =
if module == caller.module do
Module.get_attribute(module, :struct) ||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
else
module.__struct__
end
struct =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, Map.to_list(struct))
: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, fields)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
end
# Handle records
defp typespec({:record, meta, [atom]}, vars, caller) do
typespec({:record, meta, [atom, []]}, vars, caller)
end
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
keyword when is_list(keyword) ->
keyword =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, keyword)
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(keyword, field) do
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
end
end, fields)
fields = Keyword.merge(keyword, fields)
types = Keyword.values(fields)
typespec({:{}, meta, [atom|types]}, vars, caller)
_ ->
compile_error(caller, "unknown record #{inspect atom}")
end
end
# Handle ranges
@@ -822,13 +915,6 @@ defmodule Kernel.Typespec do
{:op, line(meta), op, {:integer, line(meta), integer}}
end
# Handle access macro
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: {:any, [], []}]]}
typespec(Macro.expand(access, caller), vars, caller)
end
# Handle remote calls
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
remote = Macro.expand remote, caller
@@ -859,7 +945,7 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if name in vars do
if :lists.member(name, vars) do
{:var, line(meta), name}
else
typespec({name, meta, []}, vars, caller)
@@ -867,11 +953,11 @@ defmodule Kernel.Typespec do
end
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
:elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
"char_list() 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}
{:type, line(meta), type, arguments}
end
defp typespec({:char_list, _meta, []}, vars, caller) do
@@ -900,20 +986,28 @@ defmodule Kernel.Typespec do
typespec({nil, [], []}, vars, caller)
end
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], []}, vars, caller)
end
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
defp typespec(list, vars, caller) when is_list(list) do
[h|t] = :lists.reverse(list)
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(list, vars, caller) do
[h|t] = Enum.reverse(list)
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
{:|, [], [validate_kw(x, list, caller), acc]}
end)
typespec({:list, [], [union]}, vars, caller)
defp typespec(other, _vars, caller) do
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
end
## Helpers
@@ -942,7 +1036,7 @@ defmodule Kernel.Typespec do
end
end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
{:type, line(meta), :any}
end
+67 -47
View File
@@ -9,21 +9,22 @@ defmodule Keyword do
behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching
For example, `Keyword.get/3` will get the first entry matching
the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all
Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
a given key and `delete_first` deletes just one of the existing
particular, `Enum.into/2` allows creating new keywords without
removing duplicated keys, `get_values/2` returns all values for
a given key and `delete_first/2` deletes just one of the existing
entries.
Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied.
in `Enum` and `List` can be applied.
"""
@compile :inline_list_funcs
@behaviour Dict
@type key :: atom
@@ -33,7 +34,7 @@ defmodule Keyword do
@type t(value) :: [{key, value}]
@doc """
Checks if the given argument is a keywords list or not.
Checks if the given argument is a keyword list or not.
"""
@spec keyword?(term) :: boolean
def keyword?([{key, _value} | rest]) when is_atom(key) do
@@ -55,7 +56,7 @@ defmodule Keyword do
Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [])`,
Unlike `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique.
## Examples
@@ -75,7 +76,7 @@ defmodule Keyword do
Creates a keyword from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [], fun)`,
Unlike `Enum.into(enumerable, [], fun)`,
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
## Examples
@@ -95,7 +96,7 @@ defmodule Keyword do
@doc """
Gets the value for a specific `key`.
If `key` does not exist, return default value (`nil` if no default value).
If `key` does not exist, return the default value (`nil` if no default value).
If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries.
@@ -123,6 +124,7 @@ defmodule Keyword do
@doc """
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
@@ -134,7 +136,7 @@ defmodule Keyword do
:error
"""
@spec fetch(t, key) :: {:ok, value}
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value}
@@ -143,8 +145,9 @@ defmodule Keyword do
end
@doc """
Fetches the value for specific `key`. If `key` does not exist,
a `KeyError` is raised.
Fetches the value for specific `key`.
If `key` does not exist, a `KeyError` is raised.
## Examples
@@ -174,12 +177,18 @@ defmodule Keyword do
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, v} <- keywords, key == k, do: v
fun = fn
{k, v} when k === key -> {true, v}
{_, _} -> false
end
:lists.filtermap(fun, keywords)
end
@doc """
Returns all keys from the keyword list. Duplicated
keys appear duplicated in the final list of keys.
Returns all keys from the keyword list.
Duplicated keys appear duplicated in the final list of keys.
## Examples
@@ -192,7 +201,7 @@ defmodule Keyword do
"""
@spec keys(t) :: [key]
def keys(keywords) when is_list(keywords) do
for {key, _} <- keywords, do: key
:lists.map(fn {k, _} -> k end, keywords)
end
@doc """
@@ -206,11 +215,12 @@ defmodule Keyword do
"""
@spec values(t) :: [value]
def values(keywords) when is_list(keywords) do
for {_, value} <- keywords, do: value
:lists.map(fn {_, v} -> v end, keywords)
end
@doc """
Deletes the entry in the keyword list for a `key` with `value`.
Deletes the entries in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged.
## Examples
@@ -227,13 +237,14 @@ defmodule Keyword do
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
end
@doc """
Deletes all entries in the keyword list for a specific `key`.
Deletes the entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of
Use `delete_first/2` to delete just the first entry in case of
duplicated keys.
## Examples
@@ -250,11 +261,12 @@ defmodule Keyword do
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, _} = tuple <- keywords, key != k, do: tuple
:lists.filter(fn {k, _} -> k != key end, keywords)
end
@doc """
Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
## Examples
@@ -313,7 +325,9 @@ defmodule Keyword do
end
@doc """
Checks if two keywords are equal. I.e. they contain
Checks if two keywords are equal.
Two keywords are considered to be equal if they contain
the same keys and those keys contain the same values.
## Examples
@@ -328,8 +342,9 @@ defmodule Keyword do
end
@doc """
Merges two keyword lists into one. If they have duplicated
entries, the one given as second argument wins.
Merges two keyword lists into one.
If they have duplicated keys, the one given in the second argument wins.
## Examples
@@ -339,12 +354,14 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) when is_list(d1) and is_list(d2) do
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
fun = fn {k, _v} -> not has_key?(d2, k) end
d2 ++ :lists.filter(fun, d1)
end
@doc """
Merges two keyword lists into one. If they have duplicated
entries, the given function is invoked to solve conflicts.
Merges two keyword lists into one.
If they have duplicated keys, the given function is invoked to solve conflicts.
## Examples
@@ -385,10 +402,11 @@ defmodule Keyword do
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
Updates the `key` with the given function.
If the `key` does not exist, raises `KeyError`.
If there are duplicated entries, they are all removed and only the first one
If there are duplicated keys, they are all removed and only the first one
is updated.
## Examples
@@ -418,10 +436,11 @@ defmodule Keyword do
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
Updates the `key` with the given function.
If the `key` does not exist, inserts the given `initial` value.
If there are duplicated entries, they are all removed and only the first one
If there are duplicated keys, they are all removed and only the first one
is updated.
## Examples
@@ -448,8 +467,9 @@ defmodule Keyword do
@doc """
Takes all entries corresponding to the given keys and extracts them into a
separate keyword list. Returns a tuple with the new list and the old list
with removed keys.
separate keyword list.
Returns a tuple with the new list and the old list with removed keys.
Keys for which there are no entires in the keyword list are ignored.
@@ -467,16 +487,16 @@ defmodule Keyword do
"""
def split(keywords, keys) when is_list(keywords) do
acc = {[], []}
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
fun = fn {k, v}, {take, drop} ->
case k in keys do
true -> {[{k, v}|take], drop}
false -> {take, [{k, v}|drop]}
end
end
{Enum.reverse(take), Enum.reverse(drop)}
acc = {[], []}
{take, drop} = :lists.foldl(fun, acc, keywords)
{:lists.reverse(take), :lists.reverse(drop)}
end
@doc """
@@ -497,11 +517,11 @@ defmodule Keyword do
"""
def take(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, k in keys, do: tuple
:lists.filter(fn {k, _} -> k in keys end, keywords)
end
@doc """
Drops the given keys from the dict.
Drops the given keys from the keyword list.
Duplicated keys are preserved in the new keyword list.
@@ -517,14 +537,14 @@ defmodule Keyword do
"""
def drop(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, not k in keys, do: tuple
:lists.filter(fn {k, _} -> not k in keys end, keywords)
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for
All duplicated keys are removed. See `pop_first/3` for
removing only the first entry.
## Examples
@@ -551,10 +571,10 @@ defmodule Keyword do
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence
list as well as the keyword list without that particular occurrence
of `key`.
Duplicated entries are not removed.
Duplicated keys are not removed.
## Examples
+5 -49
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -307,6 +307,8 @@ defmodule List do
@doc """
Zips corresponding elements from each list in `list_of_lists`.
The zipping finishes as soon as any list terminates.
## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]])
@@ -322,24 +324,6 @@ defmodule List do
do_zip(list_of_lists, [])
end
@doc """
Unzips the given list of lists or tuples into separate lists and returns a
list of lists.
## Examples
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
[[1, 2, 3], [:a, :b, :c]]
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
@@ -528,7 +512,7 @@ defmodule List do
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
@spec to_integer(char_list, 2..36) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@@ -566,7 +550,7 @@ defmodule List do
"abc"
"""
@spec to_string(:unicode.char_list) :: String.t
@spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
@@ -580,34 +564,6 @@ defmodule List do
end
end
@doc false
def from_char_data(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
{:ok, result}
{:error, _, _} = error ->
error
{:incomplete, _, _} = incomplete ->
incomplete
end
end
@doc false
def from_char_data!(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
## Helpers
# replace_at
+141 -50
View File
@@ -16,7 +16,8 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in,
:and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -30,20 +31,23 @@ defmodule Macro do
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
defp binary_op_props(o) do
case o do
o when o in [:<-, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
:^^^ -> {:left, 250}
:. -> {:left, 310}
o when o in [:<-, :\\] -> {:left, 40}
:when -> {:right, 50}
::: -> {:right, 60}
:| -> {:right, 70}
:= -> {:right, 90}
o when o in [:||, :|||, :or] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@@ -53,12 +57,16 @@ defmodule Macro do
Raises if the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({:|> , _, [left, right]}) do
[{left, 0}|unpipe(right)]
def unpipe(expr) do
:lists.reverse(unpipe(expr, []))
end
def unpipe(other) do
[{other, 0}]
defp unpipe({:|>, _, [left, right]}, acc) do
unpipe(right, unpipe(left, acc))
end
defp unpipe(other, acc) do
[{other, 0}|acc]
end
@doc """
@@ -68,7 +76,7 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
bad_pipe(expr, call_args)
end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -80,7 +88,12 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
end
@doc """
@@ -109,6 +122,31 @@ defmodule Macro do
other
end
@doc """
Genrates a AST node representing the variable given
by the atoms `var` and `context`.
## Examples
In order to build a variable, a context is expected.
Most of the times, in order to preserve hygiene, the
context must be `__MODULE__`:
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}
However, if there is a need to access the user variable,
nil can be given:
iex> Macro.var(:foo, nil)
{:foo, [], nil}
"""
@spec var(var, context) :: {var, [], context} when var: atom, context: atom
def var(var, context) when is_atom(var) and is_atom(context) do
{var, [], context}
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions.
"""
@@ -269,6 +307,40 @@ defmodule Macro do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc """
Validates the given expressions are valid quoted expressions.
Check the `type:Macro.t` for the specification of a valid
quoted expression.
"""
@spec validate(term) :: :ok | {:error, term}
def validate(expr) do
find_invalid(expr) || :ok
end
defp find_invalid({left, right}), do:
find_invalid(left) || find_invalid(right)
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
find_invalid(left) || find_invalid(right)
defp find_invalid(list) when is_list(list), do:
Enum.find_value(list, &find_invalid/1)
defp find_invalid(pid) when is_pid(pid), do: nil
defp find_invalid(atom) when is_atom(atom), do: nil
defp find_invalid(num) when is_number(num), do: nil
defp find_invalid(bin) when is_binary(bin), do: nil
defp find_invalid(fun) when is_function(fun) do
unless :erlang.fun_info(fun, :env) == {:env, []} and
:erlang.fun_info(fun, :type) == {:type, :external} do
{:error, fun}
end
end
defp find_invalid(other), do: {:error, other}
@doc ~S"""
Unescape the given chars.
@@ -276,9 +348,9 @@ defmodule Macro do
single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
also escaped according to the latin1 set they represent.
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
are also supported via `\xNN` and `\x{NN...}` syntax.
This function is commonly used on sigil implementations
(like `~r`, `~s` and others) which receive a raw, unescaped
@@ -309,6 +381,7 @@ defmodule Macro do
representing the codepoint of the character it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
@@ -319,20 +392,14 @@ defmodule Macro do
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(?x), do: true
def unescape_map(e), do: e
If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list.
## Octals
Octals will by default be escaped unless the map function
returns `false` for `?0`.
## Hex
Hexadecimals will by default be escaped unless the map function
returns `false` for `?x`.
Hexadecimals will be escaped if the map function returns `true`
for `?x`.
## Examples
@@ -488,17 +555,21 @@ defmodule Macro do
end
# Access
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string(right, fun))
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string([right], fun))
end
# All other calls
def to_string({target, _, args} = ast, fun) when is_list(args) do
{list, last} = :elixir_utils.split_last(args)
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
if sigil = sigil_call(ast, fun) do
sigil
else
{list, last} = :elixir_utils.split_last(args)
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
end
# Two-item tuples
@@ -521,7 +592,7 @@ defmodule Macro do
end
# All other structures
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
def to_string(other, fun), do: fun.(other, inspect(other, []))
# Block keywords
@kw_keywords [:do, :catch, :rescue, :after, :else]
@@ -531,12 +602,32 @@ defmodule Macro do
end
defp kw_blocks?(_), do: false
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
"~" <> <<name>> <>
fun.(string, inspect(string, [])) <>
sigil_args(args, fun)
_ ->
nil
end
fun.(ast, sigil)
end
defp sigil_call(_other, _fun) do
nil
end
defp sigil_args([], _fun), do: ""
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args, fun) do
@@ -661,10 +752,10 @@ defmodule Macro do
The following contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
* Module attributes reader (`@foo`);
* Macros (local or remote)
* Aliases are expanded (if possible) and return atoms
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`)
* Module attributes reader (`@foo`)
If the expression cannot be expanded, it returns the expression
itself. Notice that `expand_once/2` performs the expansion just
@@ -807,7 +898,7 @@ defmodule Macro do
end
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra)
env, extra, true)
case expand do
{:ok, receiver, quoted} ->
@@ -848,7 +939,7 @@ defmodule Macro do
be expanded.
This function uses `expand_once/2` under the hood. Check
`expand_once/2` for more information and exmaples.
`expand_once/2` for more information and examples.
"""
def expand(tree, env) do
expand_until({tree, true}, env)
+42 -25
View File
@@ -4,30 +4,47 @@ defmodule Macro.Env do
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
accessed as `__CALLER__`.
* `module` - the current module name.
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity. Returns
`nil` if not inside a function
* `context` - the context of the environment. It can be nil
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `macro_aliases` - a list of aliases defined inside the current macro
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as {var, context}
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
* `local` - the module to expand local functions to
An instance of `Macro.Env` must not be modified by hand. If you need to
create a custom environment to pass to `Code.eval_quoted/3`, use the
following trick:
def make_custom_env do
import SomeModule, only: [some_function: 2]
alias A.B.C
__ENV__
end
You may then call `make_custom_env()` to get a struct with the desired
imports and aliases included.
It contains the following fields:
* `module` - the current module name
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity; returns
`nil` if not inside a function
* `context` - the context of the environment; it can be `nil`
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `macro_aliases` - a list of aliases defined inside the current macro
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as `{var, context}`
* `export_vars` - a list keeping all variables to be exported in a
construct (may be `nil`)
* `lexical_tracker` - PID of the lexical tracker which is responsible to
keep user info
* `local` - the module to expand local functions to
"""
@type name_arity :: {atom, non_neg_integer}
@type name_arity :: {atom, arity}
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{module, module}]
@@ -40,10 +57,10 @@ defmodule Macro.Env do
@type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil
@type lexical_tracker :: pid
@type local :: module | nil
@type local :: atom | nil
@type t :: %{__struct__: __MODULE__,
module: module,
module: atom,
file: file,
line: line,
function: name_arity | nil,
@@ -103,9 +120,9 @@ defmodule Macro.Env do
"""
def stacktrace(%{__struct__: Macro.Env} = env) do
cond do
nil?(env.module) ->
is_nil(env.module) ->
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
nil?(env.function) ->
is_nil(env.function) ->
[{env.module, :__MODULE__, 0, relative_location(env)}]
true ->
{name, arity} = env.function
+28 -1
View File
@@ -11,7 +11,7 @@ defmodule Map do
their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
use Dict
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@@ -38,5 +38,32 @@ defmodule Map do
end, map1, map2
end
@doc """
Converts a struct to map.
It accepts the struct module or a struct itself and
simply removes the `__struct__` field from the struct.
## Example
defmodule User do
defstruct [:name]
end
Map.from_struct(User)
#=> %{name: nil}
Map.from_struct(%User{name: "john"})
#=> %{name: "john"}
"""
def from_struct(struct) when is_atom(struct) do
:maps.remove(:__struct__, struct.__struct__)
end
def from_struct(%{__struct__: _} = struct) do
:maps.remove(:__struct__, struct)
end
def equal?(%{} = map1, %{} = map2), do: map1 === map2
end
+117 -120
View File
@@ -14,7 +14,7 @@ defmodule Module do
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
@@ -33,7 +33,7 @@ defmodule Module do
end
end
* `@before_compile`
* `@before_compile`
A hook that will be invoked before the module is compiled.
@@ -63,7 +63,7 @@ defmodule Module do
@before_compile A
end
* `@behaviour` (notice the British spelling)
* `@behaviour` (notice the British spelling)
Specify an OTP or user-defined behaviour.
@@ -75,7 +75,7 @@ defmodule Module do
# ...
end
* `@compile`
* `@compile`
Define options for module compilation that are passed to the Erlang
compiler.
@@ -85,6 +85,9 @@ defmodule Module do
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
Several uses of `@compile` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@@ -95,7 +98,7 @@ defmodule Module do
end
end
* `@doc`
* `@doc`
Provide documentation for the function or macro that follows the
attribute.
@@ -122,7 +125,7 @@ defmodule Module do
end
end
* `@file`
* `@file`
Change the filename used in stacktraces for the function or macro that
follows the attribute.
@@ -139,7 +142,7 @@ defmodule Module do
end
end
* `@moduledoc`
* `@moduledoc`
Provide documentation for the current module.
@@ -156,7 +159,7 @@ defmodule Module do
end
* `@on_definition`
* `@on_definition`
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
@@ -211,7 +214,7 @@ defmodule Module do
end
end
* `@on_load`
* `@on_load`
A hook that will be invoked whenever the module is loaded.
@@ -237,7 +240,7 @@ defmodule Module do
end
end
* `@vsn`
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
@@ -247,14 +250,25 @@ defmodule Module do
@vsn "1.0"
end
* `@external_resource`
Specify an external resource to the current module.
Many times a module embeds information from an external file. This
attribute allows the module to annotate which external resources
have been used.
Tools like Mix may use this information to ensure the module is
recompiled in case any of the external resources change.
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behaviour callback
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behaviour callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
@@ -280,18 +294,11 @@ defmodule Module do
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
* `:functions` - keyword list of public functions along with their arities
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:docs` - list of all docstrings attached to functions and macros
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
* `:module` - module name (`Module == Module.__info__(:module)`)
* `:module` - module name (`Module == Module.__info__(:module)`)
In addition to the above, you may also pass to `__info__/1` any atom supported
by Erlang's `module_info` function which also gets defined for each compiled
@@ -356,8 +363,10 @@ defmodule Module do
@doc """
Creates a module with the given name and defined by
the given quoted expressions. The line where the module
is defined and its file can be passed as options.
the given quoted expressions.
The line where the module is defined and its file **must**
be passed as options.
## Examples
@@ -383,13 +392,16 @@ defmodule Module do
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts \\ [])
def create(module, quoted, opts)
def create(module, quoted, %Macro.Env{} = env) do
create(module, quoted, Map.to_list(env))
end
def create(module, quoted, opts) when is_atom(module) do
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
unless Keyword.has_key?(opts, :file) do
raise ArgumentError, "expected :file to be given as option"
end
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
end
@@ -467,19 +479,6 @@ defmodule Module do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """
Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative
@@ -505,23 +504,23 @@ defmodule Module do
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
table = data_table_for(module)
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
{signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end
end, 1, signature
case :ets.lookup(table, tuple) do
case :ets.lookup(table, {:doc, tuple}) do
[] ->
:ets.insert(table, {tuple, line, kind, signature, doc})
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
:ok
[{tuple, line, _old_kind, old_sign, old_doc}] ->
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
tuple,
doc_tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc)
if(is_nil(doc), do: old_doc, else: doc)
})
:ok
end
@@ -544,7 +543,7 @@ defmodule Module do
end
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
case atom_to_binary(var) do
case Atom.to_string(var) do
"_" <> rest -> {String.to_atom(rest), [], Elixir}
_ -> {var, [], nil}
end
@@ -613,7 +612,7 @@ defmodule Module do
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
:ets.lookup(table, tuple) != []
end
@@ -633,7 +632,7 @@ defmodule Module do
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
case :ets.lookup(table, tuple) do
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
@@ -653,8 +652,8 @@ defmodule Module do
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
end
@doc """
@@ -672,8 +671,8 @@ defmodule Module do
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
end
@doc """
@@ -699,12 +698,11 @@ defmodule Module do
[]
end
old = get_attribute(module, :__overridable)
old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
put_attribute(module, :__overridable, merged)
:elixir_def_overridable.overridable(module, merged)
end
end
end
@@ -713,7 +711,7 @@ defmodule Module do
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end
@doc """
@@ -732,7 +730,7 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
new =
if :lists.member(key, acc) do
@@ -757,15 +755,9 @@ defmodule Module do
@foo
Expands to:
Expands close to:
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
Module.get_attribute(__MODULE__, :foo)
## Examples
@@ -779,37 +771,9 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
def get_attribute(module, key, warn \\ nil) when
is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
@spec get_attribute(atom, atom) :: term
def get_attribute(module, key) do
get_attribute(module, key, nil)
end
@doc """
@@ -823,10 +787,12 @@ defmodule Module do
end
"""
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
:ets.delete(table, key)
:ok
end
@doc """
@@ -838,12 +804,12 @@ defmodule Module do
When registering an attribute, two options can be given:
* `:accumulate` - Several calls to the same attribute will
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:accumulate` - several calls to the same attribute will
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
* `:persist` - the attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are `false`.
@@ -865,13 +831,13 @@ defmodule Module do
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, {:__persisted_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, {:__acc_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
end
end
@@ -902,7 +868,9 @@ defmodule Module do
:ok ->
:ok
{:error, :private_doc} ->
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
:elixir_errors.warn line, env.file,
"function #{name}/#{arity} is private, " <>
"@doc's are always discarded for private functions"
end
delete_attribute(module, :doc)
@@ -911,7 +879,7 @@ defmodule Module do
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def compile_typespec(module, key, value) when is_atom(key) do
def store_typespec(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
@@ -924,6 +892,38 @@ defmodule Module do
:ets.insert(table, {key, new})
end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
@@ -931,7 +931,8 @@ defmodule Module do
end
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom)
# Attempt to compile behaviour but ignore failure (will warn later)
_ = Code.ensure_compiled(atom)
atom
end
@@ -954,15 +955,11 @@ defmodule Module do
end
defp data_table_for(module) do
module
:elixir_module.data_table(module)
end
defp function_table_for(module) do
:elixir_def.table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(module)
defp defs_table_for(module) do
:elixir_module.defs_table(module)
end
defp assert_not_compiled!(fun, module) do
+68 -30
View File
@@ -65,22 +65,25 @@ defmodule Module.LocalsTracker do
"""
@spec reachable(ref) :: [local]
def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
reduce_reachable(d, :local, [])
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
end
defp reachable_from(d, starting) do
:sets.to_list(reduce_reachable(d, starting, :sets.new))
end
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
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
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, :__locals_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val
end
@@ -89,8 +92,7 @@ defmodule Module.LocalsTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
:gen_server.start_link(__MODULE__, [], [])
end
# Adds a definition into the tracker. A public
@@ -134,9 +136,7 @@ defmodule Module.LocalsTracker do
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, {:reattach, tuple, neighbours})
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
end
# Collecting all conflicting imports with the given functions
@@ -156,12 +156,42 @@ defmodule Module.LocalsTracker do
# given also accounting the expected amount of default
# clauses a private function have.
@doc false
def collect_unused_locals(pid, private) do
reachable = reachable(pid)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
def collect_unused_locals(ref, private) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
{unreachable(d, private), collect_warnings(d, private)}
end
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
defp unreachable(d, private) do
unreachable = for {tuple, _, _} <- private, do: tuple
private =
for {tuple, :defp, _} <- private do
neighbours = :digraph.in_neighbours(d, tuple)
neighbours = for {_, _} = t <- neighbours, do: t
{tuple, :sets.from_list(neighbours)}
end
reduce_unreachable(private, [], :sets.from_list(unreachable))
end
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
if :sets.is_subset(callers, unreachable) do
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
else
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
end
end
defp reduce_unreachable([], _acc, unreachable) do
:sets.to_list(unreachable)
end
defp collect_warnings(d, private) do
reachable = reachable_from(d, :local)
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
end
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
if :lists.member(tuple, reachable) do
acc
else
@@ -169,7 +199,7 @@ defmodule Module.LocalsTracker do
end
end
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
{name, arity} = tuple
min = arity - default
max = arity
@@ -211,6 +241,7 @@ defmodule Module.LocalsTracker do
{:ok, {d, []}}
end
@doc false
def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do
[{i,^env}|_] ->
@@ -227,10 +258,9 @@ defmodule Module.LocalsTracker do
end
def handle_call({:yank, local}, _from, {d, _} = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{:reply, {in_vertices, out_vertices}, state}
:digraph.del_edges(d, :digraph.out_edges(d, local))
{:reply, {[], out_vertices}, state}
end
def handle_call(:digraph, _from, {d, _} = state) do
@@ -241,6 +271,7 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
{:noreply, state}
end
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple)
for to <- out_neigh, do: replace_edge(d, tuple, to)
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh do
:digraph.add_vertex(d, from)
replace_edge!(d, from, tuple)
end
for to <- out_neigh do
:digraph.add_vertex(d, to)
replace_edge!(d, tuple, to)
end
{:noreply, state}
end
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
if function != nil do
replace_edge!(d, function, tuple)
end
:ok
end
defp handle_add_local(d, from, to) do
@@ -317,14 +360,9 @@ defmodule Module.LocalsTracker do
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
end
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
:ok
end
end
+25
View File
@@ -10,6 +10,31 @@ defmodule Node do
@type t :: node
@doc """
Turns a non-distributed node into a distributed node.
This functionality starts the `:net_kernel` and other
related processes.
"""
@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
@doc """
Turns a distributed node into a non-distributed node.
For other nodes in the network, this is the same as the node going down.
Only possible when the node was started with `Node.start/3`, otherwise
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do
:net_kernel.stop()
end
@doc """
Returns the current node.
+176 -92
View File
@@ -32,7 +32,9 @@ defmodule OptionParser do
Note Elixir also converts the switches to underscore atoms, as
`--source-path` becomes `:source_path`, to better suit Elixir
conventions.
conventions. This means that option names on the command line cannot contain
underscores; such options will be reported as `:undefined` (in strict mode)
or `:invalid` (in basic mode).
## Switches
@@ -40,30 +42,30 @@ defmodule OptionParser do
switches and their formats. The switches can be specified via two
different options:
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error;
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
* `:switches` - configure some switches. Switches that does not
exist in the switch list are still attempted to be parsed;
* `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed.
Note only `:strict` or `:switches` may be given at once.
For each switch, the following types are supported:
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
* `:string` - Returns the switch as a string;
* `:boolean` - marks the given switch as a boolean. Boolean switches
never consume the following value unless it is `true` or
`false`.
* `:integer` - parses the switch as an integer.
* `:float` - parses the switch as a float.
* `:string` - returns the switch as a string.
If a switch can't be parsed or is not specfied in the strict case,
If a switch can't be parsed or is not specified in the strict case,
the option is returned in the invalid options list (third element
of the returned tuple).
The following extra "types" are supported:
* `:keep` - Keeps duplicated items in the list instead of overriding;
* `:keep` - keeps duplicated items in the list instead of overriding
Examples:
@@ -89,14 +91,8 @@ defmodule OptionParser do
## Negation switches
All switches starting with `--no-` are considered to be booleans and never
parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{[no_op: true], ["path/to/file"], []}
However, in case the base switch exists, it sets that particular switch to
false:
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
which will set the option to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{[op: false], ["path/to/file"], []}
@@ -111,8 +107,7 @@ defmodule OptionParser do
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, true)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], true)
end
@doc """
@@ -132,37 +127,36 @@ defmodule OptionParser do
"""
@spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, false)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], false)
end
defp do_parse([], _config, opts, args, invalid) do
defp do_parse([], _config, opts, args, invalid, _all?) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} ->
# the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid)
do_parse(rest, config, new_opts, args, invalid, all?)
{:invalid, option, value, rest} ->
# the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid])
do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
{:undefined, option, _value, rest} ->
# the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid])
do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
{:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} ->
# there is no option
if all do
do_parse(rest, config, opts, [arg|args], invalid)
if all? do
do_parse(rest, config, opts, [arg|args], invalid, all?)
else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end
@@ -176,25 +170,27 @@ defmodule OptionParser do
as both functions are built on top of next. This function
may return:
* `{:ok, key, value, rest}` - the option `key` with `value` was successfully parsed
* `{:ok, key, value, rest}` - the option `key` with `value` was
successfully parsed
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
(returned when the switch type does not match the one given via the command line)
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
(returned when the switch type does not match the one given via the
command line)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned on strict cases and the switch is unknown)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned on strict cases and the switch is unknown)
* `{:error, rest}` - there are no switches at the top of the given argv
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} |
{:undefined, 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
{aliases, switches, strict, _} = compile_config(opts, true)
{aliases, switches, strict} = compile_config(opts)
next(argv, aliases, switches, strict)
end
@@ -217,14 +213,14 @@ defmodule OptionParser do
defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option)
opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases)
tagged = tag_option(option, switches, aliases)
if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest}
else
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(opt_name, value, kinds) do
case validate_option(value, kinds) do
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
:invalid -> {:invalid, opt_name_bin, value, rest}
end
@@ -235,9 +231,95 @@ defmodule OptionParser do
{:error, argv}
end
@doc """
Receives a key-value enumerable and convert it to argv.
Keys must be atoms. Keys with nil value are discarded,
boolean values are converted to `--key` or `--no-key`
and all other values are converted using `to_string/1`.
## Examples
iex> OptionParser.to_argv([foo_bar: "baz"])
["--foo-bar", "baz"]
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
["--bool", "--no-bool"]
"""
@spec to_argv(Enumerable.t) :: argv
def to_argv(enum) do
Enum.flat_map(enum, fn
{_key, nil} -> []
{key, true} -> [to_switch(key)]
{key, false} -> [to_switch(key, "--no-")]
{key, value} -> [to_switch(key), to_string(value)]
end)
end
defp to_switch(key, prefix \\ "--") when is_atom(key) do
prefix <> String.replace(Atom.to_string(key), "_", "-")
end
@doc ~S"""
Splits a string into argv chunks.
## Examples
iex> OptionParser.split("foo bar")
["foo", "bar"]
iex> OptionParser.split("foo \"bar baz\"")
["foo", "bar baz"]
"""
@spec split(String.t) :: argv
def split(string) do
do_split(strip_leading_spaces(string), "", [], nil)
end
# If we have a escaped quote, simply remove the escape
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
# If we have a quote and we were not in a quote, start one
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
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)
# If we have a 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, ?', ?"],
do: do_split(t, <<buffer::binary, h>>, acc, nil)
# If we have space and we are outside of a quote, start new segment
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
# All other characters are moved to buffer
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
do_split(t, <<buffer::binary, h>>, acc, quote)
end
# Finish the string expecting a nil marker
defp do_split(<<>>, "", acc, nil),
do: Enum.reverse(acc)
defp do_split(<<>>, buffer, acc, nil),
do: Enum.reverse([buffer|acc])
# Otherwise raise
defp do_split(<<>>, _, _acc, marker) do
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
end
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
defp strip_leading_spaces(t), do: t
## Helpers
defp compile_config(opts, all) do
defp compile_config(opts) do
aliases = opts[:aliases] || []
{switches, strict} = cond do
@@ -249,34 +331,34 @@ defmodule OptionParser do
{[], false}
end
{aliases, switches, strict, all}
{aliases, switches, strict}
end
defp validate_option(option, value, kinds) do
{invalid_opt, value} = cond do
defp validate_option(value, kinds) do
{is_invalid, value} = cond do
:invalid in kinds ->
{option, value}
{true, value}
:boolean in kinds ->
case value do
t when t in [true, "true"] -> {nil, true}
f when f in [false, "false"] -> {nil, false}
_ -> {option, value}
_ -> {true, value}
end
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
_ -> {true, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
_ -> {true, value}
end
true ->
{nil, value}
end
if invalid_opt do
if is_invalid do
:invalid
else
{:ok, value}
@@ -292,20 +374,20 @@ defmodule OptionParser do
end
end
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
get_negated(option, value, switches)
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
get_negated(option, switches)
end
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
defp tag_option(option, _switches, aliases) when is_binary(option) do
opt = get_option(option)
if alias = aliases[opt] do
{:default, alias}
else
{:unknown, opt}
:unknown
end
end
defp option_defined?({:unknown, _option}, _switches) do
defp option_defined?(:unknown, _switches) do
false
end
@@ -317,27 +399,18 @@ defmodule OptionParser do
Keyword.has_key?(switches, option)
end
defp normalize_option({:unknown, option}, value, _switches) do
{option, [:invalid], value}
defp normalize_option(:unknown, value, _switches) do
{nil, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
kinds = List.wrap(switches[option])
cond do
:boolean in kinds ->
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{reverse_negated(option), [:invalid], nil}
defp normalize_option({:negated, option}, value, switches) do
if value do
{option, [:invalid], value}
else
{option, List.wrap(switches[option]), false}
end
end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value}
end
@@ -374,29 +447,40 @@ defmodule OptionParser do
end
end
defp to_underscore(option) do
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
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 get_option(option) do
option |> to_underscore |> String.to_atom
end
defp reverse_negated(negated) do
String.to_atom("no_" <> Atom.to_string(negated))
end
defp get_negated("no-" <> rest = option, value, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
else
get_option(option)
if str = to_underscore(option) do
String.to_atom(str)
end
{:negated, option}
end
defp get_negated(rest, _value, _switches) do
{:default, get_option(rest)}
defp get_negated("no-" <> rest = original, switches) do
cond do
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
option = get_option(original) ->
{:default, option}
true ->
:unknown
end
end
defp get_negated(rest, _switches) do
if option = get_option(rest) do
{:default, option}
else
:unknown
end
end
end
+179 -81
View File
@@ -60,37 +60,70 @@ defmodule Path do
def absname(path, relative_to) do
path = IO.chardata_to_string(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
if :binary.last(path) == ?/ do
binary_part(path, 0, byte_size(path) - 1)
else
path
end
: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)
end
end
## Absolute path on current drive
# Absolute path on current drive
defp absname_vr(["/"|rest], [volume|_], _relative),
do: join([volume|rest])
do: absname_join([volume|rest])
## Relative to current directory on current drive.
# Relative to current directory on current drive.
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
do: absname(join(rest), relative)
do: absname(absname_join(rest), relative)
## Relative to current directory on another drive.
# Relative to current directory on another drive.
defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
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())
# 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:
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
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_dirsep(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_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp reverse_maybe_remove_dirsep([?/], _), do:
[?/]
defp reverse_maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp reverse_maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
@@ -103,7 +136,7 @@ defmodule Path do
"""
@spec expand(t) :: binary
def expand(path) do
normalize absname(expand_home(path), System.cwd!)
expand_dot absname(expand_home(path), System.cwd!)
end
@doc """
@@ -130,7 +163,7 @@ defmodule Path do
"""
@spec expand(t, t) :: binary
def expand(path, relative_to) do
normalize 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 """
@@ -138,6 +171,7 @@ defmodule Path do
## Unix examples
Path.type("/") #=> :absolute
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
@@ -153,10 +187,7 @@ defmodule Path do
"""
@spec type(t) :: :absolute | :relative | :volumerelative
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
pathtype(name, major_os_type) |> elem(0)
end
@doc """
@@ -178,10 +209,20 @@ defmodule Path do
"""
@spec relative(t) :: binary
def relative(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1) |> IO.chardata_to_string
relative(name, major_os_type())
end
defp relative(name, os_type) do
pathtype(name, os_type)
|> elem(1)
|> IO.chardata_to_string
end
defp pathtype(name, os_type) do
case os_type do
:win32 -> win32_pathtype(name)
_ -> unix_pathtype(name)
end
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -386,7 +427,7 @@ defmodule Path do
end
@doc """
Returns a string with one or more path components joined by the path separator.
Joins a list of strings.
This function should be used to convert a list of strings to a path.
Note that any trailing slash is removed on join.
@@ -407,11 +448,14 @@ defmodule Path do
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]), do:
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
name
@doc """
Joins two paths.
The right path will always be expanded to its relative format
and any trailing slash is removed on join.
## Examples
iex> Path.join("foo", "bar")
@@ -419,44 +463,34 @@ defmodule Path do
"""
@spec join(t, t) :: binary
def join(left, right),
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
def join(left, right) do
left = IO.chardata_to_string(left)
os_type = major_os_type()
do_join(left, right, os_type) |> remove_dirsep(os_type)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_join(rest, relativename, [char|result], os_type)
defp do_join("", right, os_type), do: relative(right, os_type)
defp do_join(left, "", _os_type), do: left
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
defp remove_dirsep("", _os_type), do: ""
defp remove_dirsep(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
bin
end
end
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
@doc ~S"""
Splits the path into a list at the path separator.
If an empty string is given, returns an empty list.
On Windows, path is split on both "\" and "/" separators
and the driver letter, if there is one, is always returned
in lowercase.
## Examples
@@ -479,6 +513,37 @@ defmodule Path do
FN.split(IO.chardata_to_string(path))
end
defmodule Wildcard do
@moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
end
# For compatibility with buggy Erlang 17.1.
def read_file_info(file) do
call({:read_link_info, file})
end
def list_dir(dir) do
case call({:list_dir, dir}) do
{:ok, files} ->
{:ok, for(file <- files, hd(file) != ?., do: file)}
other ->
other
end
end
@compile {:inline, call: 1}
defp call(tuple) do
x = :erlang.dt_spread_tag(true)
y = :gen_server.call(:file_server_2, tuple)
:erlang.dt_restore_tag(x)
y
end
end
@doc """
Traverses paths according to the given `glob` expression.
@@ -486,19 +551,26 @@ defmodule Path do
"wildcard characters" are interpreted in a special way. The
following characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
* `?` - matches one character
* `*` - matches any number of characters up to the end of the filename, the
next dot, or the next slash
* `**` - two adjacent `*`'s used as a single pattern will match all
files and zero or more directories and subdirectories
* `[char1,char2,...]` - matches any of the characters listed; two
characters separated by a hyphen will match a range of characters
* `{item1,item2,...}` - matches one of the alternatives
Other characters represent themselves. Only paths that have
exactly the same character in the same position will match. Note
that matching is case-sensitive; i.e. "a" will not match "A".
By default, the patterns `*` and `?` do not match files starting
with a dot `.` unless `match_dot: true` is given.
## Examples
Imagine you have a directory called `projects` with three Elixir projects
@@ -513,14 +585,15 @@ defmodule Path do
"""
@spec wildcard(t) :: [binary]
def wildcard(glob) do
def wildcard(glob, opts \\ []) do
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
glob
|> chardata_to_list
|> :filelib.wildcard
|> chardata_to_list()
|> :filelib.wildcard(mod)
|> Enum.map(&IO.chardata_to_string/1)
end
# Normalize the given path by expanding "..", "." and "~".
# expand_dot the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do
@@ -537,26 +610,51 @@ defmodule Path do
defp expand_home(type) do
case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest
"~" <> rest -> resolve_home(rest)
rest -> rest
end
end
defp normalize(path), do: normalize(split(path), [])
defp resolve_home(""), do: System.user_home!
defp normalize([".."|t], [_|acc]) do
normalize t, acc
defp resolve_home(rest) do
case {rest, major_os_type} do
{"\\" <> _, :win32} ->
System.user_home! <> rest
{"/" <> _, _} ->
System.user_home! <> rest
_ -> rest
end
end
defp normalize(["."|t], acc) do
normalize t, acc
defp expand_dot(<<"/../", rest::binary>>),
do: expand_dot("/" <> rest)
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/", rest::binary>>)
defp expand_dot("/.."),
do: "/"
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/">>)
defp expand_dot(path),
do: expand_dot(:binary.split(path, "/", [:global]), [])
defp expand_dot([".."|t], [_, _|acc]) do
expand_dot t, acc
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
defp expand_dot(["."|t], acc) do
expand_dot t, acc
end
defp normalize([], acc) do
join :lists.reverse(acc)
defp expand_dot([h|t], acc) do
expand_dot t, ["/", h|acc]
end
defp expand_dot([], ["/"|acc]) do
IO.iodata_to_binary(:lists.reverse(acc))
end
defp major_os_type do
:os.type |> elem(0)
end
end
+18
View File
@@ -5,6 +5,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
Inlined by the compiler.
"""
def open(name, settings) do
:erlang.open_port(name, settings)
@@ -12,6 +14,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
Inlined by the compiler.
"""
def close(port) do
:erlang.port_close(port)
@@ -19,6 +23,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
Inlined by the compiler.
"""
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
@@ -26,6 +32,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
Inlined by the compiler.
"""
def connect(port, pid) do
:erlang.port_connect(port, pid)
@@ -33,6 +41,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
Inlined by the compiler.
"""
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
@@ -40,6 +50,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
Inlined by the compiler.
"""
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
@@ -47,6 +59,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
Inlined by the compiler.
"""
def info(port) do
:erlang.port_info(port)
@@ -54,6 +68,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
Inlined by the compiler.
"""
def info(port, item) do
:erlang.port_info(port, item)
@@ -61,6 +77,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0.
Inlined by the compiler.
"""
def list do
:erlang.ports
+48 -78
View File
@@ -6,11 +6,11 @@ defmodule Process do
exposes and auto-imports some basic functionality related to processes
available through the functions:
* `Kernel.spawn/1` and `Kernel.spawn/3`
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
* `Kernel.self/0`
* `Kernel.send/2`
* `Kernel.spawn/1` and `Kernel.spawn/3`
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
* `Kernel.self/0`
* `Kernel.send/2`
"""
@@ -34,7 +34,7 @@ defmodule Process do
end
@doc """
Returns the value for the given key.
Returns the value for the given `key`.
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
@@ -64,7 +64,7 @@ defmodule Process do
end
@doc """
Deletes the given key from the dictionary.
Deletes the given `key` from the dictionary.
"""
@spec delete(term) :: term | nil
def delete(key) do
@@ -76,18 +76,18 @@ defmodule Process do
The following behaviour applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid will exit with the given reason;
1. If pid is not trapping exits, pid will exit with the given reason.
2) If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid;
2. If pid is trapping exits, the exit signal is transformed into a message
`{:EXIT, from, reason}` and delivered to the message queue of pid.
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
the exit signal is transformed into a message {:EXIT, from, :normal} and
delivered to its message queue;
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
exits, the exit signal is transformed into a message `{:EXIT, from,
:normal}` and delivered to its message queue.
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
untrappable exit signal is sent to pid which will unconditionally exit with
exit reason `:killed`.
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
an untrappable exit signal is sent to pid which will unconditionally
exit with exit reason `:killed`.
Inlined by the compiler.
@@ -129,10 +129,10 @@ defmodule Process do
end
@doc """
Sends `msg` to `dest` after `time` millisecons.
Sends `msg` to `dest` after `time` milliseconds.
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process
If `dest` is a pid, it must be the pid of a local process, dead or alive.
If `dest` is an atom, it must be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does
not refer to a process.
@@ -194,46 +194,6 @@ defmodule Process do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc false
def spawn(fun) do
:erlang.spawn(fun)
end
@doc false
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@doc false
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@doc false
def spawn_link(mod, fun, args) do
:erlang.spawn_link(mod, fun, args)
end
@doc false
def spawn_monitor(fun) do
:erlang.spawn_monitor(fun)
end
@doc false
def spawn_monitor(mod, fun, args) do
:erlang.spawn_monitor(mod, fun, args)
end
@doc false
def self() do
:erlang.self()
end
@doc false
def send(dest, msg) do
:erlang.send(dest, msg)
end
@doc """
The calling process starts monitoring the item given.
It returns the monitor reference.
@@ -248,8 +208,8 @@ defmodule Process do
end
@doc """
If monitor_ref is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off.
If `monitor_ref` is a reference which the calling process
obtained by calling `monitor/1`, this monitoring is turned off.
If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
@@ -267,7 +227,7 @@ defmodule Process do
processes currently existing on the local node.
Note that a process that is exiting, exists but is not alive, i.e.,
alive?/1 will return false for a process that is exiting,
`alive?/1` will return `false` for a process that is exiting,
but its process identifier will be part of the result returned.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
@@ -292,7 +252,7 @@ defmodule Process do
@doc """
Removes the link, if there is one, between the calling process and
the process or port referred to by `pid`. Returns true and does not
the process or port referred to by `pid`. Returns `true` and does not
fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
@@ -305,16 +265,16 @@ defmodule Process do
end
@doc """
Associates the name with a pid or a port identifier. name, which must
Associates the name with a pid or a port identifier. `name`, which must
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`).
already registered (check with `whereis/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) do
def register(pid, name) when not name in [nil, false, true] do
:erlang.register(name, pid)
end
@@ -330,7 +290,7 @@ defmodule Process do
@doc """
Returns the pid or port identifier with the registered name.
Returns nil if the name is not registered.
Returns `nil` if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@@ -349,7 +309,7 @@ defmodule Process do
@doc """
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
started from a certain shell should have a group leader other than `:init`.
"""
@spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do
@@ -357,7 +317,7 @@ defmodule Process do
end
@doc """
Returns a list of names which have been registered using register/2.
Returns a list of names which have been registered using `register/2`.
"""
@spec registered :: [atom]
def registered do
@@ -379,19 +339,19 @@ defmodule Process do
end
@doc """
Sets certain flags for the process Pid, in the same manner as flag/2.
Returns the old value of the flag. The allowed values for Flag are
only a subset of those allowed in flag/2, namely: save_calls.
Sets certain flags for the process `pid`, in the same manner as `flag/2`.
Returns the old value of the flag. The allowed values for `flag` are
only a subset of those allowed in `flag/2`, namely: `save_calls`.
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@spec flag(pid, process_flag, term) :: term
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
@doc """
Returns information about the process identified by pid or nil if the process
Returns information about the process identified by `pid` or `nil` if the process
is not alive.
Use this only for debugging information.
@@ -403,13 +363,23 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid
or nil if the process is not alive.
Returns information about the process identified by `pid`
or `nil` if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
@spec info(pid, atom) :: {atom, term} | nil
def info(pid, spec)
def info(pid, :registered_name) do
case :erlang.process_info(pid, :registered_name) do
:undefined -> nil
[] -> {:registered_name, []}
other -> other
end
end
def info(pid, spec) when is_atom(spec) do
nillify :erlang.process_info(pid, spec)
end
+591 -232
View File
@@ -1,225 +1,17 @@
defmodule Protocol do
@moduledoc false
@moduledoc """
Functions for working with protocols.
"""
# Callback for defprotocol.
@doc false
def defprotocol(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
]
@doc """
Defines a new protocol function.
# Import the new dsl that holds the new def
import Protocol.DSL, only: :macros
Protocols do not allow functions to be defined directly, instead, the
regular `Kernel.def/*` macros are replaced by this macro which
defines the protocol functions with the appropriate callbacks.
"""
defmacro def(signature)
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Invoke the user given block
unquote(block)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
defp after_defprotocol do
quote unquote: false do
# == Deprecated records handling ==
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
Kernel.def impl_for(unquote(arg)) when
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), do: unquote(impl)
# == Deprecated records handling ==
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for {guard, mod} <- Protocol.builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
end
@spec impl_for!(term) :: module | 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
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:name)
false -> nil
end
end
else
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 impl_for?(target) do
true -> target.__impl__(:name)
false -> any_impl_for
end
end
# 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}
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
# Callback for defimpl.
@doc false
def defimpl(protocol, opts) 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])
end
defp do_defimpl(protocol, [do: block, for: for]) do
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
Protocol.assert_protocol(protocol)
defmodule name do
@behaviour unquote(protocol)
@protocol unquote(protocol)
@for unquote(for)
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end
end
end
# Check if the given module is a protocol. Raises an error
# if not loaded or not a protocol.
@doc false
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{:module, ^module} -> nil
_ -> raise ArgumentError, "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, "#{inspect module} is not a protocol"
end
end
# Builtin types.
@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]
end
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def rec_impl_for(current) do
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
arg = quote do: arg
target = Module.concat(current, Tuple)
fallback = quote do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
impl_for = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(all)) and match?('Elixir.' ++ _, Atom.to_char_list(atom)) do
true ->
target = Module.concat(unquote(current), atom)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> unquote(fallback)
end
false ->
unquote(fallback)
end
end
{arg, impl_for}
end
end
defmodule Protocol.DSL do
@moduledoc false
@doc false
defmacro def({_, _, args}) when args == [] or is_atom(args) do
raise ArgumentError, "protocol functions expect at least one argument"
end
@@ -251,7 +43,7 @@ defmodule Protocol.DSL do
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
Protocol.__spec__?(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
@@ -260,21 +52,588 @@ defmodule Protocol.DSL do
raise ArgumentError, "invalid args for def inside defprotocol"
end
@doc false
def __spec__?(module, name, arity) do
case :code.ensure_loaded(Kernel.Typespec) do
{:module, Kernel.Typespec} ->
tuple = {name, arity}
specs = Module.get_attribute(module, :spec)
@doc """
Checks if the given module is loaded and is protocol.
found = for {k, v} <- specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
Returns `:ok` if so, otherwise raises ArgumentError.
"""
@spec assert_protocol!(module) :: :ok | no_return
def assert_protocol!(module) do
assert_protocol!(module, "")
end
found != []
{:error, _} ->
true
defp assert_protocol!(module, extra) do
case Code.ensure_compiled(module) do
{:module, ^module} -> :ok
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
end
:ok
end
@doc """
Checks if the given module is loaded and is an implementation
of the given protocol.
Returns `:ok` if so, otherwise raises ArgumentError.
"""
@spec assert_impl!(module, module) :: :ok | no_return
def assert_impl!(protocol, base) do
assert_impl!(protocol, base, "")
end
defp assert_impl!(protocol, base, extra) do
impl = Module.concat(protocol, base)
case Code.ensure_compiled(impl) do
{:module, ^impl} -> :ok
_ -> 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
else
^protocol ->
:ok
other ->
raise ArgumentError,
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
end
end
@doc """
Derive the `protocol` for `module` with the given options.
"""
defmacro derive(protocol, module, options \\ []) do
quote do
module = unquote(module)
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
end
end
## Consolidation
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
Does not load any of the protocols.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
Does not load any of the implementations.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = Atom.to_char_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[: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
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{:ok, files} -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
extract_from_beam(:filename.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, {module, [attributes: attributes]}} ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
end
@doc """
Returns true if the protocol was consolidated.
"""
@spec consolidated?(module) :: boolean
def consolidated?(protocol) do
protocol.__info__(:attributes)[:protocol][:consolidated]
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol.
Consolidation happens by changing the protocol `impl_for`
in the abstract format to have fast lookup rules. Usually
the list of implementations to use during consolidation
are retrieved with the help of `extract_impls/2`.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Protocol.consolidated?(Enumerable)
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
However each implementation must be available and
it will be loaded.
"""
@spec consolidate(module, [module]) ::
{:ok, binary} |
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def consolidate(protocol, types) when is_atom(protocol) do
beam_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
@docs_chunk 'ExDc'
defp beam_protocol(protocol) do
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
opts = [:allow_missing_chunks]
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
{:attributes, attributes},
{@docs_chunk, docs}]}} ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{:ok, {protocol, any, abstract_code, docs}}
_ ->
{:error, :not_a_protocol}
end
_ ->
{:error, :no_beam_info}
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
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, docs}, types) do
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, docs}}
other -> other
end
end
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
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)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{:ok, {protocol, Enum.reverse(acc)}}
else
{:error, :not_a_protocol}
end
end
defp builtin_clause_for(mod, guard, protocol, line) do
{: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
{: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(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, load_impl(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{:atom, line, value}]}
end
defp load_impl(protocol, for) do
Module.concat(protocol, for).__impl__(:target)
end
# Finally compile the module and emit its bytecode.
defp compile({{protocol, code}, docs}) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
unless docs == :missing_chunk do
binary = :elixir_module.add_beam_chunk(binary, @docs_chunk, docs)
end
{:ok, binary}
end
## Definition callbacks
@doc false
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 the new dsl that holds the new def
import Protocol, only: [def: 1]
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Invoke the user given block
unquote(block)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: atom() | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
target = Module.concat(__MODULE__, mod)
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
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
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:target)
false -> nil
end
end
else
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 impl_for?(target) do
true -> target.__impl__(:target)
false -> any_impl_for
end
end
# 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
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
@spec __protocol__(:name) :: __MODULE__
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
@doc false
def __functions_spec__([]),
do: []
def __functions_spec__([h|t]),
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
@doc false
def __impl__(protocol, opts) 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])
end
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.
__impl__ =
quote unquote: false do
@doc false
@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__(:protocol), do: unquote(protocol)
end
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
Protocol.assert_protocol!(protocol)
defmodule name do
@behaviour protocol
@protocol protocol
@for for
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
unquote(__impl__)
end
end
end
@doc false
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}"
else
for.__struct__
end
: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.flatten(derives))
:ok
end
defp derive(protocol, for, struct, opts, env) do
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra)
assert_impl!(protocol, Map, extra)
# Clean up variables from eval context
env = %{env | vars: [], export_vars: nil}
args = [for, struct, opts]
impl = Module.concat(protocol, Map)
: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__, :impl, persist: true)
@impl [protocol: unquote(protocol), for: unquote(for)]
@doc false
@spec __impl__(:target) :: unquote(impl)
@spec __impl__(:protocol) :: unquote(protocol)
@spec __impl__(:for) :: unquote(for)
def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for)
end, Macro.Env.location(env))
end
end)
end
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
found != []
end
## Helpers
defp 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]
end
end
-249
View File
@@ -1,249 +0,0 @@
defmodule Protocol.Consolidation do
@moduledoc """
Module responsible for consolidating protocols and helpers for
extracting protocols and implementations from code paths for
consolidation.
"""
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = Atom.to_char_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[: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
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{:ok, files} -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
extract_from_beam(:filename.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, {module, [attributes: attributes]}} ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol. Consolidation happens
by changing the protocol `impl_for` in the abstract
format to have fast lookup rules.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Enumerable.__info__(:attributes)[:protocol]
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{:ok, binary} |
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def apply_to(protocol, types) when is_atom(protocol) do
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
"and structs at once. Consolidation will be added back once " <>
"polymorphic records are removed"
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
attributes: attributes]}} ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{:ok, {protocol, any, abstract_code}}
_ ->
{:error, :not_a_protocol}
end
_ ->
{:error, :no_beam_info}
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> 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} <- Protocol.builtin, do: mod
structs = types -- all
change_impl_for(code, protocol, types, structs, false, [])
end
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for {guard, mod} <- Protocol.builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
clauses = [struct_clause_for(line)|clauses] ++
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{:ok, {protocol, Enum.reverse(acc)}}
else
{:error, :not_a_protocol}
end
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp struct_clause_for(line) do
{: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(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{:atom, line, value}]}
end
# Finally compile the module and emit its bytecode.
defp compile({protocol, code}) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
{:ok, binary}
end
end
+3 -2
View File
@@ -2,7 +2,7 @@ defmodule Range do
@moduledoc """
Defines a Range.
A Range are represented internally as a struct. However,
A Range is represented internally as a struct. However,
the most common form of creating and matching on ranges
is via the `../2` macro, auto-imported from Kernel:
@@ -18,7 +18,8 @@ defmodule Range do
defstruct first: nil, last: nil
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
@type t :: %Range{}
@type t(first, last) :: %Range{first: first, last: last}
@doc """
Creates a new range.
+75 -37
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom:
iex> Record.record? {User, "jose", 27}
iex> Record.is_record {User, "john", 27}
true
This module provides conveniences for working with records at
@@ -14,12 +14,26 @@ defmodule Record do
In Elixir, records are used mostly in two situations:
1. To work with short, internal data;
2. To interface with Erlang records;
1. to work with short, internal data
2. to interface with Erlang records
The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang
files.
## Types
Types can be defined for tuples with the `record/2` macro (only available
in typespecs). Like with the generated record macros it will expand to
a tuple.
defmodule MyModule do
require Record
Record.defrecord :user name: "john", age: 25
@type user :: record(:user, name: String.t, age: integer)
# expands to: `@type user :: {:user, String.t, integer}`
end
"""
@doc """
@@ -49,12 +63,12 @@ defmodule Record do
## Examples
iex> record = {User, "jose", 27}
iex> Record.record?(record, User)
iex> record = {User, "john", 27}
iex> Record.is_record(record, User)
true
"""
defmacro record?(data, kind) do
defmacro is_record(data, kind) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -77,15 +91,15 @@ defmodule Record do
## Examples
iex> record = {User, "jose", 27}
iex> Record.record?(record)
iex> record = {User, "john", 27}
iex> Record.is_record(record)
true
iex> tuple = {}
iex> Record.record?(tuple)
iex> Record.is_record(tuple)
false
"""
defmacro record?(data) do
defmacro is_record(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -101,21 +115,6 @@ defmodule Record do
end
end
@doc false
def defmacros(name, values, env, tag \\ nil) do
Record.Deprecated.defmacros(name, values, env, tag)
end
@doc false
def deftypes(values, types, env) do
Record.Deprecated.deftypes(values, types, env)
end
@doc false
def deffunctions(values, env) do
Record.Deprecated.deffunctions(values, env)
end
@doc """
Defines a set of macros to create and access a record.
@@ -126,26 +125,31 @@ defmodule Record do
## Examples
defmodule User do
Record.defrecord :user, [name: "José", age: "25"]
require Record
Record.defrecord :user, [name: "meg", age: "25"]
end
In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record:
# To create records
user() #=> {:user, "José", 25}
user(age: 26) #=> {:user, "José", 26}
record = user() #=> {:user, "meg", 25}
record = user(age: 26) #=> {:user, "meg", 26}
# To get a field from the record
user(record, :name) #=> "José"
user(record, :name) #=> "meg"
# To update the record
user(record, age: 26) #=> {:user, "José", 26}
user(record, age: 26) #=> {:user, "meg", 26}
# Convert a record to a keyword list
user(record) #=> [name: "meg", age: 26]
By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else:
defmodule User do
require Record
Record.defrecord :user, User, name: nil
end
@@ -156,7 +160,7 @@ defmodule Record do
defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecord, kv)
fields = Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -174,7 +178,7 @@ defmodule Record do
defmacro defrecordp(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecordp, kv)
fields = Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -190,9 +194,19 @@ defmodule Record do
@doc false
def __fields__(type, fields) do
:lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
{key, val} when is_atom(key) ->
try do
Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
else
val -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
end, fields)
end
@@ -205,8 +219,15 @@ defmodule Record do
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
case Macro.expand(args, caller) do
{:{}, _, [^atom|list]} when length(list) == length(fields) ->
record = List.to_tuple([atom|list])
Macro.escape(Record.__keyword__(atom, fields, record))
{^atom, arg} when length(fields) == 1 ->
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
_ ->
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
end
end
end
@@ -294,4 +315,21 @@ defmodule Record do
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if is_record(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
raise ArgumentError, msg
end
end
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)
end
-539
View File
@@ -1,539 +0,0 @@
defmodule Record.Deprecated do
@moduledoc false
def defexception(name, fields, opts) do
quote do
fields = unquote(fields)
defmodule unquote(name) do
defexception fields
unquote(Keyword.get opts, :do)
end
end
end
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
raise ArgumentError, "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{:ok, fields, types, def_type} ->
types = Macro.escape(types)
# bind_quoted isn't available when bootstrapping record
quoted = quote [unquote: false] do
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
if def_type do
type = String.to_atom(Atom.to_string(name) <> "_t")
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
end
end
quote do
def_type = unquote(def_type)
fields = unquote(fields)
types = unquote(types)
tag = unquote(tag)
name = unquote(name)
unquote(quoted)
end
:error ->
quote do
name = unquote(name)
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
end
end
end
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
recordp_split(t, [{field, default}|defaults], [type|types], true)
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
end
defp recordp_split([], defaults, types, def_type) do
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
end
defp recordp_split(_, _, _, _) do
:error
end
def deffunctions(values, env) do
values = for value <- values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
conversions(values),
record_optimizable(),
updater(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def deftypes(values, types, env) do
types = types || []
values = for value <- values do
{name, default} = convert_value(value)
{name, default, find_spec(types, name)}
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = for value <- values do
{key, value} = convert_value(value)
{key, Macro.escape(value)}
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(args) do
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
## Callbacks
defmacro __before_compile__(_) do
quote do
@spec __record__(atom) :: term
@spec __record__(atom, term) :: term
@spec __record__(atom, term, term) :: term
def __record__(:optimizable), do: @record_optimizable
end
end
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = {name, length(args)}
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
def access(atom, fields, arg, _caller) when is_atom(arg) do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = Macro.Env.in_match?(caller)
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
def dispatch(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError, "expected arguments to be a compile time atom or keywords"
end
end
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, "expected arguments to be compile time keywords"
end
if Macro.Env.in_match?(caller) do
raise ArgumentError, "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
## Function generation
defp reflection(values) do
quoted = for {k, _} <- values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
defp initializer(values) do
defaults = for {_, value} <- values, do: value
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
string_selective = for {k, v} <- values, do: initialize_lookup(Atom.to_string(k), v)
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
defp conversions(values) do
sorted = for {k, _} <- values do
index = find_index(values, k, 0)
{k, quote(do: :erlang.element(unquote(index + 2), record))}
end
quote do
@doc false
def to_keywords(record) do
unquote(sorted)
end
end
end
defp accessors([{:__exception__, _}|t], 1) do
accessors(t, 2)
end
defp accessors([{key, _default}|t], i) do
update = String.to_atom "update_" <> Atom.to_string(key)
contents = quote do
@doc false
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
atom_fields =
for {key, _default} <- values, do: updater_lookup(key, key, values)
string_fields =
for {key, _default} <- values, do: updater_lookup(Atom.to_string(key), key, values)
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
quote do
@doc false
def update([], record) do
record
end
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
index = find_index(values, key, 0)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> :erlang.element(unquote(index + 2), record)
{_, value} -> value
end
end
end
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile {unquote(__MODULE__), :__before_compile__}
@on_definition {unquote(__MODULE__), :__on_definition__}
end
end
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
defp core_specs(values) do
types = for {_, _, spec} <- values, do: spec
options = for {k, _, v} <- values, do: {k, v}
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: {__MODULE__, unquote_splicing(types)}
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, term}]
end
@spec new :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{key, _default, spec}|t], i, acc) do
update = String.to_atom "update_" <> Atom.to_string(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
defp is_keyword(_), do: false
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
defp convert_value({atom, other}) when is_atom(atom), do:
{atom, check_value(atom, other)}
defp convert_value({field, _}), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
for(i <- other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
for(i <- Tuple.to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == {:env, []} and
:erlang.fun_info(other, :type) == {:type, :external} do
raise ArgumentError, "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
defp find_index([{k, _}|_], k, i), do: i
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
defp find_spec(types, name) do
matches = for {k, v} <- types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
defmacro record_type(opts) when is_list(opts) do
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+130 -75
View File
@@ -22,53 +22,63 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
strings to be given on match
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
Use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to
anycrlf; the new line setting can be overridden by setting `(*CR)` or
`(*LF)` or `(*CRLF)` or `(*ANY)` according to re documentation
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of
each line; use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped
and allow `#` to delimit comments
* `firstline` (f) - forces the unanchored pattern to match before or at the
first newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
The options not available are:
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or
`(*ANYCRLF)` or `(*ANY)` at the beginning of the regexp according to the
re documentation
## Captures
Many functions in this module allows what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string.
This is the default;
* `:all` - all captured subpatterns including the complete matching string
(this is the default)
* `:first` - only the first captured subpattern, which is always the complete
matching part of the string. All explicitly captured subpatterns are
discarded;
* `:first` - only the first captured subpattern, which is always the
complete matching part of the string; all explicitly captured subpatterns
are discarded
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
captured subpatterns, but not the complete matching part of
the string;
* `:all_but_first`- all but the first matching subpattern, i.e. all
explicitly captured subpatterns, but not the complete matching part of
the string
* `:none` - do not return matching subpatterns at all;
* `:none` - do not return matching subpatterns at all
* `:all_names` - captures all names in the Regex;
* `:all_names` - captures all names in the Regex
* `list(binary)` - a list of named captures to capture;
* `list(binary)` - a list of named captures to capture
"""
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
defstruct re_pattern: nil, source: "", opts: ""
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do
defexception message: "regex could not be compiled"
@@ -97,7 +107,7 @@ defmodule Regex do
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
case translate_options(options, []) do
{:error, rest} ->
{:error, {:invalid_option, rest}}
@@ -123,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
@@ -142,6 +153,7 @@ defmodule Regex do
false
"""
@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
@@ -158,6 +170,8 @@ defmodule Regex do
false
"""
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -167,9 +181,9 @@ defmodule Regex do
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
to see the possible capture values.
## Examples
@@ -183,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}]
"""
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -213,6 +228,7 @@ defmodule Regex do
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)
@@ -223,6 +239,7 @@ defmodule Regex do
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -236,6 +253,7 @@ defmodule Regex do
"foo"
"""
@spec source(t) :: String.t
def source(%Regex{source: source}) do
source
end
@@ -249,6 +267,7 @@ defmodule Regex do
"m"
"""
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do
opts
end
@@ -262,6 +281,7 @@ defmodule Regex do
["foo"]
"""
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
@@ -275,9 +295,9 @@ defmodule Regex do
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
to see the possible capture values.
## Examples
@@ -291,6 +311,7 @@ defmodule Regex do
[]
"""
@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
@@ -310,13 +331,17 @@ defmodule Regex do
## Options
* `:parts` - when specified, splits the string into the
given number of parts. If not specified, `:parts`
is defaulted to `:infinity`, which will split the
string into the maximum number of parts possible
based on the given pattern.
* `:parts` - when specified, splits the string into the given number of
parts. If not specified, `:parts` defaults to `:infinity`, which will
split the string into the maximum number of parts possible based on the
given pattern.
* `:trim` - when true, remove blank strings from the result;
* `:trim` - when true, remove blank strings from the result.
* `:on` - specifies which captures and order to split the string
on. Check the moduledoc for `Regex` to see the possible capture
values. Defaults to `:first` which means captures inside the
Regex does not affect the split result.
## Examples
@@ -332,27 +357,61 @@ defmodule Regex do
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
iex> Regex.split(~r/a(?<second>b)c/, "abc")
["", ""]
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
["a", "c"]
"""
@spec split(t, String.t, [term]) :: [String.t]
def split(regex, string, options \\ [])
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
def split(%Regex{}, "", _opts), do: [""]
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
else
splits
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
on = Keyword.get(opts, :on, :first)
case :re.run(string, compiled, [:global, capture: on]) do
{:match, matches} ->
do_split(matches, string, 0,
parts_to_index(Keyword.get(opts, :parts, :infinity)),
Keyword.get(opts, :trim, false))
:match ->
[string]
:nomatch ->
[string]
end
end
defp zero_to_infinity(0), do: :infinity
defp zero_to_infinity(n), do: n
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) when byte_size(string) <= offset,
do: []
defp do_split(_, string, offset, 1, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([], string, offset, _counter, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
do: do_split([h|t], string, offset, counter, trim)
defp do_split([[]|t], string, offset, counter, trim),
do: do_split(t, string, offset, counter, trim)
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
new_offset = pos + length
keep = pos - offset
if keep == 0 and (length == 0 or trim) do
do_split([h|t], string, new_offset, counter, trim)
else
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
end
end
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
@@ -370,8 +429,8 @@ defmodule Regex do
## Options
* `:global` - when `false`, replaces only the first occurrence
(defaults to true)
* `:global` - when `false`, replaces only the first occurrence
(defaults to true)
## Examples
@@ -391,6 +450,7 @@ defmodule Regex do
"[b][d]"
"""
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
@@ -465,12 +525,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos
<<untouched :: [size(length), binary], rest :: binary>> = string
<<untouched :: binary-size(length), rest :: binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)]
end
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
<<_ :: [size(length), binary], rest :: binary>> = string
<<_ :: size(length)-binary, rest :: binary>> = string
new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end
@@ -503,7 +563,7 @@ defmodule Regex do
end
defp get_index(string, {pos, len}) do
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
res
end
@@ -553,18 +613,13 @@ defmodule Regex do
# Private Helpers
defp translate_options(<<?g, t :: binary>>) do
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: {:error, rest}
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
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(<<?r, 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(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<>>, acc), do: acc
defp translate_options(rest, _acc), do: {:error, rest}
end
+1 -1
View File
@@ -198,7 +198,7 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
target1.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
+180 -60
View File
@@ -76,8 +76,8 @@ defmodule Stream do
There are many functions in Elixir's standard library that return
streams, some examples are:
* `IO.stream/2` - Streams input lines, one by one;
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
* `IO.stream/2` - streams input lines, one by one
* `URI.query_decoder/1` - decodes a query string, pair by pair
This module also provides many convenience functions for creating streams,
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
@@ -89,12 +89,14 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases.
"""
@doc false
defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type index :: non_neg_integer
@type default :: any
@type t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
@@ -154,18 +156,22 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
fn(f1) -> &do_chunk(&1, n, pad, f1) end
if is_nil(pad) do
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
else
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
&do_chunk(&1, n, pad, &2)
end
end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if nil?(pad) || count == 0 do
{:cont, acc}
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
{:cont, acc}
end
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
@doc """
@@ -184,7 +190,7 @@ defmodule Stream do
def chunk_by(enum, fun) do
lazy enum, nil,
fn(f1) -> R.chunk_by(fun, f1) end,
fn(f1) -> &do_chunk_by(&1, f1) end
&do_chunk_by(&1, &2)
end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
@@ -336,6 +342,25 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
end
@doc """
Creates a stream that emits a value every `n` milliseconds.
The values emitted are an increasing counter starting at `0`.
## Examples
iex> Stream.interval(10) |> Enum.take(10)
[0,1,2,3,4,5,6,7,8,9]
"""
@spec interval(non_neg_integer) :: Enumerable.t
def interval(n) do
unfold 0, fn (count) ->
:timer.sleep(n)
{count, count + 1}
end
end
@doc """
Injects the stream values into the given collectable as a side-effect.
@@ -420,13 +445,14 @@ defmodule Stream do
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> Stream.into(File.stream!("new"))
|> Stream.run
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok
end
@@ -560,6 +586,22 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Creates a stream that emits a single value after `n` milliseconds.
The value emitted is `0`.
## Examples
iex> Stream.timer(10) |> Enum.to_list
[0]
"""
@spec timer(non_neg_integer) :: Enumerable.t
def timer(n) do
take(interval(n), 1)
end
@doc """
Transforms an existing stream.
@@ -574,7 +616,7 @@ defmodule Stream do
## Examples
`Stream.transform/3` is a useful as it can be used as basis to implement
`Stream.transform/3` is useful as it can be used as the basis to implement
many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows:
@@ -615,12 +657,14 @@ defmodule Stream do
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{list, user_acc} when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.List.reduce(list, &1, fun))
{:halt, _user_acc} ->
next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)}
{other, user_acc} ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.reduce(other, &1, inner))
end
{reason, _} ->
{reason, elem(inner_acc, 1)}
@@ -632,8 +676,9 @@ defmodule Stream do
reduce.(inner_acc)
catch
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -645,29 +690,31 @@ defmodule Stream do
end
end
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
try do
reduce.(inner_acc)
reduce.({op, [:outer|inner_acc]})
catch
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace)
else
{_, acc} ->
{:halted, [:outer|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
{:halted, [:inner|acc]} ->
next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end
end
defp do_transform_each(x, acc, f) do
defp do_transform_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, h} -> throw({:stream_transform, h})
{_, _} = o -> o
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
@@ -839,6 +886,15 @@ defmodule Stream do
"""
@spec cycle(Enumerable.t) :: Enumerable.t
def cycle(enumerable)
def cycle(enumerable) when is_list(enumerable) do
unfold {enumerable, enumerable}, fn
{source, [h | t]} -> {h, {source, t}}
{source = [h | t], []} -> {h, {source, t}}
end
end
def cycle(enumerable) do
fn acc, fun ->
inner = &do_cycle_each(&1, &2, fun)
@@ -926,14 +982,15 @@ defmodule Stream do
@doc """
Emits a sequence of values for the given resource.
Similar to `unfold/2` but the initial value is computed lazily via
`start_fun` and executes an `after_fun` at the end of enumeration
(both in cases of success and failure).
Similar to `transform/2` but the initial accumulated value is
computed lazily via `start_fun` and executes an `after_fun` at
the end of enumeration (both in cases of success and failure).
Successive values are generated by calling `next_fun` with the
previous accumulator (the initial value being the result returned
by `start_fun`) and it must return a tuple with the current and
next accumulator. The enumeration finishes if it returns `nil`.
by `start_fun`) and it must return a tuple containing a list
of items to be emitted and the next accumulator. The enumeration
finishes if it returns `{:halt, acc}`.
As the name says, this function is useful to stream values from
resources.
@@ -943,8 +1000,8 @@ defmodule Stream do
Stream.resource(fn -> File.open!("sample") end,
fn file ->
case IO.read(file, :line) do
data when is_binary(data) -> {data, file}
_ -> nil
data when is_binary(data) -> {[data], file}
_ -> {:halt, file}
end
end,
fn file -> File.close(file) end)
@@ -966,9 +1023,11 @@ defmodule Stream do
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do
# Optimize the most common cases
case next_fun.(next_acc) do
nil -> nil
{v, next_acc} -> {fun.(v, acc), next_acc}
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
{_, _} = other -> other
end
catch
kind, reason ->
@@ -976,11 +1035,62 @@ defmodule Stream do
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
nil ->
after_fun.(next_acc)
{:done, acc}
{acc, next_acc} ->
{:opt, acc, next_acc} ->
do_resource(next_acc, next_fun, acc, fun, after_fun)
{:halt, next_acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{list, next_acc} when is_list(list) ->
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.List.reduce(list, &1, fun))
{enum, next_acc} ->
inner = &do_resource_each(&1, &2, fun)
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.reduce(enum, &1, inner))
end
end
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
try do
reduce.(acc)
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:suspended, acc, c} ->
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
end
end
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
try do
reduce.({op, [:outer|acc]})
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:halted, [:outer|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, [:inner|acc]} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:done, [_|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
end
end
defp do_resource_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
@@ -988,7 +1098,7 @@ defmodule Stream do
Emits a sequence of values for the given accumulator.
Successive values are generated by calling `next_fun` with the previous
accumulator and it must return a tuple with the current and next
accumulator and it must return a tuple with the current value and next
accumulator. The enumeration finishes if it returns `nil`.
## Examples
@@ -1021,12 +1131,12 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(%Stream{funs: funs} = lazy, fun),
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: %Stream{enum: enum, funs: [fun]}
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun),
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
@@ -1065,19 +1175,29 @@ defimpl Enumerable, for: Stream do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
{:halted, accs} ->
do_done {:halted, accs}, done
{:done, accs} ->
do_done {:done, accs}, done
end
end
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
defp do_done({reason, [acc|t]}, {done, fun}) do
[h|_] = Enum.reverse(t)
case done.([acc, h], fun) do
{:cont, [acc|_]} -> {reason, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
defimpl Inspect, for: Stream do
import Inspect.Algebra
def inspect(%{enum: enum, funs: funs}, opts) do
inner = [enum: enum, funs: Enum.reverse(funs)]
concat ["#Stream<", to_doc(inner, opts), ">"]
end
end
+279 -169
View File
@@ -4,28 +4,12 @@ defmodule String do
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.3.0. For example,
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
In addition to this module, Elixir provides more low-level
operations that work directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
## Codepoints and graphemes
As per the Unicode Standard, a codepoint is an Unicode
Character, which may be represented by one or more bytes.
For example, the character "é" is represented with two
The functions in this module act according to the Unicode
Standard, version 6.3.0.As per the standard, a codepoint is
an Unicode Character, which may be represented by one or more
bytes. For example, the character "é" is represented with two
bytes:
iex> byte_size("é")
@@ -66,34 +50,110 @@ defmodule String do
This current Elixir version implements Extended Grapheme Cluster
algorithm.
## String and binary operations
To act accordingly to the Unicode Standard, many functions
in this module runs in linear time, as it needs to traverse
the whole string considering the proper Unicode codepoints.
For example, `String.length/1` is going to take longer as
the input grows. On the other hand, `byte_size/1` always runs
in constant time (i.e. regardless of the input size).
This means often there are performance costs in using the
functions in this module, compared to the more low-level
operations that work directly with binaries:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
There are many situations where using the `String` module can
be avoided in favor of binary functions or pattern matching.
For example, imagine you have a string `prefix` and you want to
remove this prefix from another string named `full`.
One may be tempted to write:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base, String.length(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Although the function above works, it performs poorly. To
calculate the length of the string, we need to traverse it
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempting at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base..-1)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
While this is much better (we don't traverse `full` twice),
it could still be improved. In this case, since we want to
extract a substring from a string, we can use `byte_size/1`
and `binary_part/3` as there is no chance we will slice in
the middle of a codepoint made of more than one byte:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> binary_part(full, base, byte_size(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Or simply used pattern matching:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
...> rest
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
On the other hand, if you want to dynamically slice a string
based on an integer value, then using `String.slice/3` is the
best option as it guarantees we won't incorrectly split a valid
codepoint in multiple bytes.
## Integer codepoints
Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example:
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("olá")
["o", "l", "á"]
There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` special macro:
codepoint. One may use the `?` construct:
iex> ?j
106
iex> ?o
111
iex> ?é
233
iex> ?á
225
Or also via pattern matching:
iex> << eacute :: utf8 >> = "é"
iex> << eacute :: utf8 >> = "á"
iex> eacute
233
225
As we have seen above, codepoints can be inserted into
a string by their hexadecimal code:
"jos\x{0065}\x{0301}" #=>
"josé"
"ol\x{0061}\x{0301}" #=>
"olá"
## Self-synchronization
@@ -129,7 +189,7 @@ defmodule String do
@spec printable?(t) :: boolean
def printable?(<< h :: utf8, t :: binary >>)
when h in ?\040..?\176
when h in 0x20..0x7E
when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD
when h in 0x10000..0x10FFFF do
@@ -227,40 +287,53 @@ defmodule String do
"""
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options \\ [])
def split(string, pattern, options \\ [])
def split("", _pattern, _options), do: [""]
def split(string, "", options) do
parts = Keyword.get(options, :parts, :infinity)
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
end
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, pattern, options) do
def split(string, pattern, options) do
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
Regex.split(pattern, string, options)
else
splits =
case Keyword.get(options, :parts, :infinity) do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
_ ->
:binary.split(binary, pattern, [:global])
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
parts = Keyword.get(options, :parts, :infinity)
trim = Keyword.get(options, :trim, false)
if parts == :infinity and trim == false do
:binary.split(string, pattern, [:global])
else
splits
split_parts(string, pattern, parts_to_index(parts), trim)
end
end
end
defp split_parts("", _pattern, _num), do: [""]
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
defp split_parts(binary, pattern, num, parts) do
case :binary.split(binary, pattern) do
[head] -> Enum.reverse([head|parts])
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
defp parts_to_index(:infinity), do: 0
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp split_codepoints(binary, 1, _trim), do: [binary]
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<h, t :: binary>>, count, trim),
do: [<<h>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<>>, _, true), do: []
defp split_codepoints(<<>>, _, false), do: [""]
defp split_parts("", _pattern, _num, true), do: []
defp split_parts("", _pattern, _num, _trim), do: [""]
defp split_parts(string, _pattern, 1, _trim), do: [string]
defp split_parts(string, pattern, num, trim) do
case :binary.split(string, pattern) do
[""] when trim ->
[]
[head] ->
[head]
[head, tail] ->
if trim and head == "" do
split_parts(tail, pattern, num, trim)
else
[head|split_parts(tail, pattern, num-1, trim)]
end
end
end
@@ -322,8 +395,8 @@ defmodule String do
iex> String.upcase("ab 123 xpto")
"AB 123 XPTO"
iex> String.upcase("josé")
"JOSÉ"
iex> String.upcase("olá")
"OLÁ"
"""
@spec upcase(t) :: t
@@ -340,8 +413,8 @@ defmodule String do
iex> String.downcase("AB 123 XPTO")
"ab 123 xpto"
iex> String.downcase("JOSÉ")
"josé"
iex> String.downcase("OLÁ")
"olá"
"""
@spec downcase(t) :: t
@@ -364,8 +437,8 @@ defmodule String do
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("josé")
"José"
iex> String.capitalize("olá")
"Olá"
"""
@spec capitalize(t) :: t
@@ -405,7 +478,7 @@ defmodule String do
# does not traverse the whole binary).
def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do
do_rstrip(string, "", char)
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
else
string
end
@@ -599,8 +672,8 @@ defmodule String do
"a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
@spec replace(t, t | Regex.t, t) :: t
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do
@@ -672,8 +745,8 @@ defmodule String do
## Examples
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("olá")
["o", "l", "á"]
iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
@@ -699,8 +772,8 @@ defmodule String do
## Examples
iex> String.next_codepoint("josé")
{"j", "osé"}
iex> String.next_codepoint("olá")
{"o", "lá"}
"""
@compile {:inline, next_codepoint: 1}
@@ -767,6 +840,65 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc ~S"""
Splits the string into chunks of characters that share a common trait.
The trait can be one of two options:
* `:valid` – the string is split into chunks of valid and invalid character
sequences
* `:printable` – the string is split into chunks of printable and
non-printable character sequences
Returns a list of binaries each of which contains only one kind of
characters.
If the given string is empty, an empty list is returned.
## Examples
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
["abc\0"]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
["abc\0", <<0x0ffff::utf8>>]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
["abc", <<0, 0x0ffff::utf8>>]
"""
@spec chunk(t, :valid | :printable) :: [t]
def chunk(string, trait)
def chunk("", _), do: []
def chunk(str, trait) when trait in [:valid, :printable] do
{cp, _} = next_codepoint(str)
pred_fn = make_chunk_pred(trait)
do_chunk(str, pred_fn.(cp), pred_fn)
end
defp do_chunk(str, flag, pred_fn), do: do_chunk(str, [], <<>>, flag, pred_fn)
defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc)
defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk])
defp do_chunk(str, acc, chunk, flag, pred_fn) do
{cp, rest} = next_codepoint(str)
if pred_fn.(cp) != flag do
do_chunk(rest, [chunk|acc], cp, not flag, pred_fn)
else
do_chunk(rest, acc, chunk <> cp, flag, pred_fn)
end
end
defp make_chunk_pred(:valid), do: &valid?/1
defp make_chunk_pred(:printable), do: &printable?/1
@doc """
Returns unicode graphemes in the string as per Extended Grapheme
Cluster algorithm outlined in the [Unicode Standard Annex #29,
@@ -790,8 +922,8 @@ defmodule String do
## Examples
iex> String.next_grapheme("josé")
{"j", "osé"}
iex> String.next_grapheme("olá")
{"o", "lá"}
"""
@compile {:inline, next_grapheme: 1}
@@ -868,7 +1000,7 @@ defmodule String do
@doc """
Returns the grapheme in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, than it returns `nil`.
If `position` is greater than `string` length, then it returns `nil`.
## Examples
@@ -915,7 +1047,12 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, than it returns `nil`.
If the offset is greater than string length, then it returns `""`.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
@@ -926,13 +1063,13 @@ defmodule String do
"lixir"
iex> String.slice("elixir", 10, 3)
nil
""
iex> String.slice("elixir", -4, 4)
"ixir"
iex> String.slice("elixir", -10, 3)
nil
""
iex> String.slice("a", 0, 1500)
"a"
@@ -941,36 +1078,53 @@ defmodule String do
""
iex> String.slice("a", 2, 1500)
nil
""
"""
@spec slice(t, integer, integer) :: grapheme | nil
@spec slice(t, integer, integer) :: grapheme
def slice(string, start, 0) do
case abs(start) <= length(string) do
true -> ""
false -> nil
end
def slice(_, _, 0) do
""
end
def slice(string, start, len) when start >= 0 and len >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
case do_count_bytes(next_grapheme(string), start, 0) do
{nil, _} -> ""
{next, start_bytes} ->
{_, len_bytes} = do_count_bytes(next, len, 0)
binary_part(string, start_bytes, len_bytes)
end
end
def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = length(string) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
false -> nil
start = length(string) + start
case start >= 0 do
true -> slice(string, start, len)
false -> ""
end
end
defp do_count_bytes(next, 0, acc), do: {next, acc}
defp do_count_bytes(nil, _, acc), do: {nil, acc}
defp do_count_bytes({char, rest}, counter, acc) do
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
end
@doc """
Returns a substring from the offset given by the start of the
range to the offset given by the end of the range.
If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `nil`.
string or if the range is in reverse order, returns `""`.
If the start or end of the range are negative, the whole string
is traversed first in order to convert the negative indexes into
positive ones.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
@@ -981,7 +1135,7 @@ defmodule String do
"lixir"
iex> String.slice("elixir", 10..3)
nil
""
iex> String.slice("elixir", -4..-1)
"ixir"
@@ -993,10 +1147,10 @@ defmodule String do
"ixir"
iex> String.slice("elixir", -1..-4)
nil
""
iex> String.slice("elixir", -10..-7)
nil
""
iex> String.slice("a", 0..1500)
"a"
@@ -1004,59 +1158,51 @@ defmodule String do
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
nil
"""
@spec slice(t, Range.t) :: t | nil
@spec slice(t, Range.t) :: t
def slice(string, range)
def slice("", _.._), do: ""
def slice(string, first..-1) when first >= 0 do
case do_count_bytes(next_grapheme(string), first, 0) do
{nil, _} -> ""
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
end
end
def slice(string, first..last) when first >= 0 and last >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
if last >= first do
slice(string, first, last - first + 1)
else
""
end
end
def slice(string, first..last) do
total = length(string)
{bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
if first < 0 do
first = total + first
end
if first < 0, do: first = length + first
if last < 0, do: last = length + last
if last < 0 do
last = total + last
end
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
if first < 0 or first > last or first > length do
""
else
last = min(last + 1, length)
bytes = Enum.drop(bytes, length - last)
first = last - first
{length_bytes, start_bytes} = Enum.split(bytes, first)
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
end
end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
nil
defp do_acc_bytes({char, rest}, bytes, length) do
do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
end
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
end
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
end
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
acc <> char
end
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
acc
end
defp do_slice(nil, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
end
defp do_acc_bytes(nil, bytes, length) do
{bytes, length}
end
@doc """
@@ -1124,8 +1270,8 @@ defmodule String do
end
defp do_ends_with(string, suffix) when is_binary(suffix) do
string_size = size(string)
suffix_size = size(suffix)
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]))
end
@@ -1277,7 +1423,7 @@ defmodule String do
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
@spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
@@ -1297,40 +1443,4 @@ defmodule String do
def to_float(string) do
:erlang.binary_to_float(string)
end
@doc false
def from_char_data(binary) when is_binary(binary) do
binary
end
def from_char_data(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
{:ok, result}
{:error, _, _} = error ->
error
{:incomplete, _, _} = incomplete ->
incomplete
end
end
@doc false
def from_char_data!(binary) when is_binary(binary) do
binary
end
def from_char_data!(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
end
+5 -5
View File
@@ -36,7 +36,7 @@ defmodule StringIO do
"""
@spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, {string, options}, [])
GenServer.start_link(__MODULE__, {string, options}, [])
end
@doc """
@@ -52,7 +52,7 @@ defmodule StringIO do
"""
@spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
GenServer.call(pid, :contents)
end
@doc """
@@ -68,7 +68,7 @@ defmodule StringIO do
"""
@spec close(pid) :: {:ok, {binary, binary}}
def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close)
GenServer.call(pid, :close)
end
## callbacks
@@ -199,7 +199,7 @@ defmodule StringIO do
end
defp do_get_chars(input, :latin1, n) do
<< chars :: [ binary, size(n) ], rest :: binary >> = input
<<chars :: binary-size(n), rest :: binary>> = input
{chars, rest}
end
@@ -209,7 +209,7 @@ defmodule StringIO 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 :: binary-size(split_pos), rest :: binary>> = input
{chars, rest}
end
catch
+90 -68
View File
@@ -1,11 +1,11 @@
defmodule Supervisor do
@moduledoc """
A behaviour module for implementing supevision functionality.
A behaviour module for implementing supervision functionality.
A supervisor is a process which supervises other processes called
child processes. Supervisors are used to build an hierarchical process
structure called a supervision tree, a nice way to structure a fault
tolerant applications.
structure called a supervision tree, a nice way to structure fault-tolerant
applications.
A supervisor implemented using this module will have a standard set
of interface functions and include functionality for tracing and error
@@ -28,7 +28,7 @@ defmodule Supervisor do
{:reply, h, t}
end
def handle_cast({:push, h}, _from, t) do
def handle_cast({:push, h}, t) do
{:noreply, [h|t]}
end
end
@@ -44,7 +44,7 @@ defmodule Supervisor do
worker(Stack, [[:hello]])
]
# Start the supervisor with our one children
# Start the supervisor with our one child
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
Notice that when starting the GenServer, we have registered it
@@ -73,12 +73,12 @@ defmodule Supervisor do
GenServer.call(:sup_stack, :pop) == :hello
Supervisors support different strategies, in the example above, we
Supervisors support different strategies; in the example above, we
have chosen `:one_for_one`. Furthermore, each supervisor can have many
workers and supervisors as children, each of them with their specific
configuration, shutdown values and restart strategies.
configuration, shutdown values, and restart strategies.
Continue reading this module to learn more about supervision strategies
Continue reading this moduledoc to learn more about supervision strategies
and then follow to the `Supervisor.Spec` module documentation to learn
about the specification for workers and supervisors.
@@ -86,7 +86,7 @@ defmodule Supervisor do
In the example above, a supervisor was dynamically created by passing
the supervision structure to `start_link/2`. However, supervisors
can also be created by explicitly defining a supevision module:
can also be created by explicitly defining a supervision module:
defmodule MyApp.Supervisor do
use Supervisor
@@ -96,51 +96,49 @@ defmodule Supervisor do
end
def init([]) do
import Supervisor.Spec
children = [
worker(Stack, [[:hello]])
]
supervisor(children, strategy: :one_for_one)
supervise(children, strategy: :one_for_one)
end
end
You may want to use a module-based supervisor if:
* You need to do some particular action on supervisor
initialization, like setting up a ETS table;
* You need to do some particular action on supervisor
initialization, like setting up a ETS table.
* You want to perform partial hot-code swapping of the
tree. For example, if you add or remove a children,
the module-based supervision will add and remove the
new children directly, while the dynamic supervision
requires the whole tree to be restarted in order to
perform such swaps;
* You want to perform partial hot-code swapping of the
tree. For example, if you add or remove a children,
the module-based supervision will add and remove the
new children directly, while the dynamic supervision
requires the whole tree to be restarted in order to
perform such swaps.
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_one` - if a child process terminates, only that
process is restarted.
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:one_for_all` - if a child process terminates, all other child
processes are terminated and then all child processes (including
the terminated one) are restarted.
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:rest_for_one` - if a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
one in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted.
* `: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 children. Many functions
in this module behave 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.
## Name registering
## Name Registration
A supervisor is bound to the same name registering rules as a `GenServer`.
A supervisor is bound to the same name registration rules as a `GenServer`.
Read more about it in the `GenServer` docs.
"""
@@ -181,22 +179,26 @@ defmodule Supervisor do
the `:max_restarts` and `:max_seconds` value can be configured
as described in `Supervisor.Spec.supervise/2` docs.
The options can also be used to register a supervisor name,
the supported values are described under the `Name Registering`
The options 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 and its child processes are successfully created
(i.e. if all child process start functions return `{:ok, child}`,
(i.e. if the start function of all child processes returns `{:ok, child}`,
`{:ok, child, info}`, or `:ignore`) the function returns
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
already exists a process with the specified name the function returns
already exists a process with the specified name, the function returns
`{:error, {:already_started, pid}}`, where pid is the pid of that
process.
If any child process start function fails or returns an error tuple or
If any of the child process start functions fail or return an error tuple or
an erroneous value, the supervisor will first terminate all already
started child processes with reason `:shutdown` and then terminate
itself and return `{:error, {:shutdown, reason}}`.
Note that the `Supervisor` is linked to the parent process
and will exit not only on crashes but also if the parent process
exits with `:normal` reason.
"""
@spec start_link([tuple], options) :: on_start
def start_link(children, options) when is_list(children) do
@@ -215,11 +217,11 @@ defmodule Supervisor do
If the `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, 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 under the `Name Registering`
name, the supported values are described under the `Name Registration`
section in the `GenServer` module docs.
Other failure conditions are specified in `start_link/2` docs.
@@ -243,7 +245,7 @@ defmodule Supervisor do
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 a `:simple_one_for_one`, the child specification defined in
In the case of `:simple_one_for_one`, the child specification defined in
the supervisor will be 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.
@@ -262,33 +264,37 @@ defmodule Supervisor do
If the child process start function returns an error tuple or an erroneous value,
or if it fails, the child specification is discarded and the function returns
`{:error, error}` where error is a term containing information about the error
`{:error, error}` where `error` is a term containing information about the error
and child specification.
"""
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
def start_child(supervisor, child_spec_or_args) do
call(supervisor, {:start_child, child_spec_or_args})
end
@doc """
Terminates the given pid or child id.
If the supervisor is not a `simple_one_for_one`, the child id is expected
and the process, if there is one, is terminated and, the child specification is
and the process, if there is one, is terminated; the child specification is
kept unless the child is temporary.
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
specification identifier is given instead instead of a `pid`, the function will
specification identifier is given instead of a `pid`, the function will
return `{:error, :simple_one_for_one}`.
Non-temporary child process may later be restarted by the supervisor. The child
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, the function returns ok. If there is no child specification or
If successful, the function returns `:ok`. If there is no child specification or
pid, the function returns `{:error, :not_found}`.
"""
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
def terminate_child(supervisor, pid_or_child_id) do
call(supervisor, {:terminate_child, pid_or_child_id})
end
@doc """
Deletes the child specification identified by `child_id`.
@@ -304,7 +310,9 @@ defmodule Supervisor do
"""
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting
defdelegate delete_child(supervisor, child_id), to: :supervisor
def delete_child(supervisor, child_id) do
call(supervisor, {:delete_child, child_id})
end
@doc """
Restarts a child process identified by `child_id`.
@@ -333,7 +341,9 @@ defmodule Supervisor do
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} | {:ok, child, term} | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting | term
defdelegate restart_child(supervisor, child_id), to: :supervisor
def restart_child(supervisor, child_id) do
call(supervisor, {:restart_child, child_id})
end
@doc """
Returns a list with information about all children.
@@ -343,41 +353,53 @@ defmodule Supervisor do
This function returns a list of tuples containing:
* the `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor;
* `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor
* the `child` - the pid of the corresponding child process, the atom `:restarting`
if the process is about to be restarted or `:undefined` if there is no such process;
* `child` - the pid of the corresponding child process, the atom
`:restarting` if the process is about to be restarted, or `:undefined` if
there is no such process
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
* `type` - `:worker` or `:supervisor` as defined in the child specification
* the `modules` as defined in the child specification;
* `modules` – as defined in the child specification
"""
@spec which_children(supervisor) ::
[{Supervisor.Spec.child_id | :undefined,
child | :restarting,
Supervisor.Spec.worker,
Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor
def which_children(supervisor) do
call(supervisor, :which_children)
end
@doc """
Returns a map containing count value for the supervisor.
Returns a map containing count values for the supervisor.
The map contains the following keys:
* `:specs` - the total count of children, dead or alive;
* `:specs` - the total count of children, dead or alive
* `:active` - the count of all actively running child processes managed by this supervisor;
* `: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;
* `: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;
* `:workers` - the count of all workers, whether or not the child process
is still alive
"""
@spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer]
%{specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer}
def count_children(supervisor) do
:supervisor.count_children(supervisor) |> :maps.from_list
call(supervisor, :count_children) |> :maps.from_list
end
@compile {:inline, call: 2}
defp call(supervisor, req) do
GenServer.call(supervisor, req, :infinity)
end
end
-156
View File
@@ -1,156 +0,0 @@
defmodule Supervisor.Behaviour do
@moduledoc false
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import unquote(__MODULE__)
end
end
@doc """
Receives a list of children (worker or supervisors) to
supervise and a set of options. Returns a tuple containing
the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`;
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5;
* `:max_seconds` - the time frame in which max_restarts applies.
Defaults to 5;
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds.
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
when dynamically attaching children;
"""
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, "expected :strategy option to be given to supervise"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
{:ok, {{strategy, maxR, maxS}, children}}
end
@child_doc """
## Options
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the module name;
* `:function` - the function to invoke on the child to start it.
Defaults to `:start_link`;
* `:restart` - defines when the child process should restart.
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000` for a worker and `:infinity` for a supervisor;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, `gen_server` or `gen_fsm`. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
## Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`;
## Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `exit(child, :shutdown)` and
then waits for an exit signal back. If no exit signal is received within
the specified time (in miliseconds), the child process is unconditionally
terminated using `exit(child, :kill)`;
"""
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def supervisor(module, args, options \\ []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, [module])
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{id, {module, function, args},
restart, shutdown, type, modules}
end
end
+46 -45
View File
@@ -40,64 +40,65 @@ defmodule Supervisor.Spec do
Explicit supervisors as above are required when there is a need to:
1. partialy change the supervision tree during hot-code swaps;
1. Partialy change the supervision tree during hot-code swaps.
2. define supervisors inside other supervisors;
2. Define supervisors inside other supervisors.
3. perform actions inside the supervision `init/1` callback.
For example, you may want to start an ETS table that is linked to
the supervisor (i.e. if the supervision tree needs to be restarted,
the ETS table must be restarted too);
3. Perform actions inside the supervision `init/1` callback.
For example, you may want to start an ETS table that is linked to
the supervisor (i.e. if the supervision tree needs to be restarted,
the ETS table must be restarted too).
## Supervisor and worker options
In the example above, we have defined workers and supervisors
and each accepts the following options:
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the given module
name;
* `:id` - a name used to identify the child specification
internally by the supervisor; defaults to the given module
name
* `:function` - the function to invoke on the child to start it;
* `:function` - the function to invoke on the child to start it
* `:restart` - defines when the child process should restart;
* `:restart` - defines when the child process should restart
* `:shutdown` - defines how a child process should be terminated;
* `:shutdown` - defines how a child process should be terminated
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a `Supervisor` or `GenServer` If the child
process is a `GenEvent`, modules should be `:dynamic`;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a `Supervisor` or `GenServer`; if the child
process is a `GenEvent`, modules should be `:dynamic`
### Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:permanent` - the child process is always restarted
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`;
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`
### Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`.
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care.
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `Process.exit(child, :shutdown)`
and then waits for an exit signal back. If no exit signal is received
within the specified time (in miliseconds), the child process is
unconditionally terminated using `Process.exit(child, :kill)`;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `Process.exit(child, :shutdown)`
and then waits for an exit signal back. If no exit signal is received
within the specified time (in miliseconds), the child process is
unconditionally terminated using `Process.exit(child, :kill)`.
"""
@typedoc "Supported strategies"
@@ -138,19 +139,19 @@ defmodule Supervisor.Spec do
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`. You can learn more about strategies
in the `Supervisor` module;
* `:strategy` - the restart strategy option. It can be either
`: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 amount of restarts allowed in
a time frame. Defaults to 5;
* `: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.
Defaults to 5;
* `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5.
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds. Please check the `Supervisor` module for
The `:strategy` option is required and by default maximum 3 restarts
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@spec supervise([spec], strategy: strategy,
@@ -161,7 +162,7 @@ defmodule Supervisor.Spec do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxR = Keyword.get(options, :max_restarts, 3)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
@@ -188,7 +189,7 @@ defmodule Supervisor.Spec do
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
@@ -212,7 +213,7 @@ defmodule Supervisor.Spec do
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
+153 -51
View File
@@ -60,9 +60,9 @@ defmodule System do
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: Keyword.t
@spec build_info() :: map
def build_info do
[version: version, tag: get_describe, date: get_date]
%{version: version, tag: get_describe, date: get_date}
end
@doc """
@@ -94,11 +94,20 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> IO.chardata_to_string(base)
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
_ -> nil
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
Current working directory, exception on error.
@@ -153,11 +162,11 @@ defmodule System do
Returns a writable temporary directory.
Searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
3. The directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
1. the directory named by the TMPDIR environment variable
2. the directory named by the TEMP environment variable
3. the directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. as a last resort, the current working directory
Returns `nil` if none of the above are writable.
"""
@@ -202,59 +211,32 @@ defmodule System do
end
@doc """
Register a program exit handler function.
Registers a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook in "script" mode.
Registers a function that will be invoked at the end of program execution.
Useful for invoking a hook in "script" mode.
The function must receive the exit status code
as an argument.
The handler always executes in a different process from the one it was
registered in. As a consequence, any resources managed by the calling process
(ETS tables, open files, etc.) won't be available by the time the handler
function is invoked.
The function must receive the exit status code as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast {:at_exit, fun}
end
@doc """
Execute a system command.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Otherwise a string, correctly encoded in UTF-8, is expected.
"""
@spec cmd(String.t) :: String.t
@spec cmd(char_list) :: char_list
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) when is_binary(command) do
List.to_string :os.cmd(String.to_char_list(command))
end
@doc """
Locate an executable on the system.
Locates an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) when is_binary(program) do
case :os.find_executable(String.to_char_list(program)) do
false -> nil
@@ -355,14 +337,14 @@ defmodule System do
Halts the Erlang runtime system where the argument `status` must be a
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the operating system;
* If an integer, the runtime system exits with the integer value which
is returned to the operating system.
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system.
* If a string, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
* If a string, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1.
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
@@ -387,4 +369,124 @@ defmodule System do
def halt(status) when is_binary(status) do
:erlang.halt(String.to_char_list(status))
end
@doc """
Executes the given `command` with `args`.
`command` is expected to be an executable available in PATH
unless an absolute path is given.
`args` must be a list of strings which are not expanded
in any way. For example, this means wildcard expansion will
not happen unless `Path.wildcard/2` is used. On Windows though,
wildcard expansion is up to the program.
A set of options are also supported and described below.
## Options
* `:into` - injects the result into the given collectable, defaults to `""`
* `:cd` - the directory to run the command in
* `:env` - an enumerable of tuples containing environment key-value as binary
* `:arg0` - set the command arg0
* `:stderr_to_stdout` - redirects stderr to stdout when true
* `:parallelism` - when true, the VM will schedule port tasks to improve
parallelism in the system. If set to false, the VM will try to perform
commands immediately, improving latency at the expense of parallelism.
The default can be set on system startup by passing the "+spp" argument
to `--erl`.
## Error reasons
If invalid arguments are given, `ArgumentError` is raised by
`System.cmd/3`. `System.cmd/3` also expects a strict set of
options and will raise if unknown or invalid options are given.
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
detailed below:
* `:system_limit` - all available ports in the Erlang emulator are in use
* `:enomem` - there was not enough memory to create the port
* `:eagain` - there are no more available operating system processes
* `:enametoolong` - the external command given was too long
* `:emfile` - there are no more available file descriptors
(for the operating system process that the Erlang emulator runs in)
* `:enfile` - the file table is full (for the entire operating system)
* `:eacces` - the command does not point to an executable file
* `:enoent` - the command does not point to an existing file
## Shell commands
If you desire to execute a trusted command inside a shell, with pipes,
redirecting and so on, please check
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
"""
@spec cmd(binary, [binary], Keyword.t) ::
{Collectable.t, exit_status :: non_neg_integer}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
cmd = String.to_char_list(command)
cmd =
if Path.type(cmd) == :absolute do
cmd
else
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
end
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
receive do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{fun.(acc, :done), status}
end
end
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)
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:arg0, bin}|opts], into)
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([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
defp cmd_opts([{:env, enum}|t], opts, into),
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}")
defp cmd_opts([], opts, into),
do: {into, opts}
defp validate_env(enum) do
Enum.map enum, fn
{k, v} ->
{String.to_char_list(k), String.to_char_list(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect other}"
end
end
end
+83 -56
View File
@@ -2,42 +2,35 @@ defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting for tasks.
Tasks are processes that meant to execute one particular
action throughout their life-cycle, often with little
explicit communication with other processes. The most common
use case for tasks is to compute a value asynchronously:
Tasks are processes meant to execute one particular
action throughout their life-cycle, often with little or no
communication with other processes. The most common use case
for tasks is to compute a value asynchronously:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
Tasks spawned with async can be awaited on by its caller
Tasks spawned with `async` can be awaited on by its caller
process (and only its caller) as shown in the example above.
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides `async/1` and `await/1`, tasks can also be used as part
of supervision trees and dynamically spawned in remote nodes.
We will explore all three scenarios next.
Besides `async/1` and `await/2`, tasks can also be
started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next.
## async and await
The most common way to spawn a task is with `Task.async/1`. A new
process will be created and this process is linked and monitored
by the caller. However, the processes are unlinked right before
the task finishes, allowing the proper error to be triggered only
on `await/1`.
process will be created, linked and monitored by the caller. Once
the task action finishes, a message will be sent to the caller
with the result.
This implies three things:
1) In case the caller crashes, the task will be killed and its
computation will abort;
2) In case the task crashes due to an error, the parent will
crash only on `await/1`;
3) In case the task crashes because a linked process caused
it to crash, the parent will crash immediately;
`Task.await/2` is used to read the message sent by the task. On
`await`, Elixir will also setup a monitor to verify if the process
exited for any abnormal reason (or in case exits are being
trapped by the caller).
## Supervised tasks
@@ -46,7 +39,7 @@ defmodule Task do
Task.start_link(fn -> IO.puts "ok" end)
Such can be mounted in your supervision tree as:
Such tasks can be mounted in your supervision tree as:
import Supervisor.Spec
@@ -54,15 +47,10 @@ defmodule Task do
worker(Task, [fn -> IO.puts "ok" end])
]
Since such tasks are supervised and not directly linked to
the caller, they cannot be awaited on. For such reason,
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
(which is the result expected by supervision trees).
Such tasks are useful as workers that run during your application
life-cycle and rarely communicate with other workers. For example,
a worker that pushes data to another server or a worker that consumes
events from an event manager and writes it to a log file.
Since these tasks are supervised and not directly linked to
the caller, they cannot be awaited on. Note `start_link/1`,
unlike `async/1`, returns `{:ok, pid}` (which is
the result expected by supervision trees).
## Supervision trees
@@ -70,7 +58,7 @@ defmodule Task do
that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, fn -> do_work() end)
Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally:
@@ -78,8 +66,8 @@ defmodule Task do
# In the remote node
Task.Supervisor.start_link(name: :tasks_sup)
# On the client
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
# In the client
Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` is more often started in your supervision tree as:
@@ -89,7 +77,15 @@ defmodule Task do
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Check `Task.Supervisor` for other operations supported by the Task supervisor.
Note that, when working with distributed tasks, one should use the `async/3` API,
that expects explicit module, function and arguments, instead of `async/1` that
works with anonymous functions. That's because the anonymous function API expects
the same module version to exist on all involved nodes. Check the `Agent` module
documentation for more information on distributed processes, as the limitations
described in the agents documentation apply to the whole ecosystem.
Finally, check `Task.Supervisor` for other operations supported by the Task
supervisor.
"""
@doc """
@@ -97,14 +93,16 @@ defmodule Task do
It contains two fields:
* `:pid` - the proces reference of the task process. It may be a pid
or a tuple containing the process and node names;
* `:pid` - the process reference of the task process; it may be a pid
or a tuple containing the process and node names
* `:ref` - the task monitor reference;
* `:ref` - the task monitor reference
"""
defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@doc """
Starts a task as part of a supervision tree.
"""
@@ -118,20 +116,44 @@ defmodule Task do
"""
@spec start_link(module, atom, [term]) :: {:ok, pid}
def start_link(mod, fun, args) do
Task.Supervised.start_link(:undefined, {mod, fun, args})
Task.Supervised.start_link(get_info(self), {mod, fun, args})
end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(fun) :: {:ok, pid}
def start(fun) do
start(:erlang, :apply, [fun, []])
end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(module, atom, [term]) :: {:ok, pid}
def start(mod, fun, args) do
Task.Supervised.start(get_info(self), {mod, fun, args})
end
@doc """
Starts a task that can be awaited on.
This function spawns a process that is linked and monitored
to the caller process. A `Task` struct is returned containing
This function spawns a process that is linked to and monitored
by the caller process. A `Task` struct is returned containing
the relevant information.
## Task's message format
The reply sent by the task will be in the format `{ref, msg}`,
where `ref` is the monitoring reference hold by the task.
where `ref` is the monitoring reference held by the task.
"""
@spec async(fun) :: t
def async(fun) do
@@ -147,16 +169,24 @@ defmodule Task do
@spec async(module, atom, [term]) :: t
def async(mod, fun, args) do
mfa = {mod, fun, args}
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self(), mfa])
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
ref = Process.monitor(pid)
send(pid, {self(), ref})
%Task{pid: pid, ref: ref}
end
@doc """
Awaits for a task reply.
defp get_info(self) do
{node(),
case Process.info(self, :registered_name) do
{:registered_name, []} -> self()
{:registered_name, name} -> name
end}
end
A timeout, in miliseconds, can be given with default value
@doc """
Awaits a task reply.
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.
"""
@@ -168,7 +198,7 @@ defmodule Task do
reply
{:DOWN, ^ref, _, _, :noconnection} ->
mfa = {__MODULE__, :await, [task, timeout]}
exit({{:nodedown, get_node(task.pid)}, mfa})
exit({{:nodedown, node(task.pid)}, mfa})
{:DOWN, ^ref, _, _, reason} ->
exit({reason, {__MODULE__, :await, [task, timeout]}})
after
@@ -185,13 +215,13 @@ defmodule Task do
This function returns a tuple with the task and the
returned value in case the message matches a task that
exited with success, it raises in case the found task
failed or nil if no task was found.
failed or `nil` if no task was found.
This function is useful in situations where multiple
tasks are spawned and their results are collected just
later on. For example, a GenServer can spawn tasks,
tasks are spawned and their results are collected
later on. For example, a `GenServer` can spawn tasks,
store the tasks in a list and later use `Task.find/2`
to see if upcoming messages are from any of the tasks.
to see if incoming messages are from any of the tasks.
"""
@spec find([t], any) :: {term, t} | nil | no_return
def find(tasks, msg)
@@ -210,7 +240,7 @@ defmodule Task do
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
case Enum.find(tasks, find) do
%Task{pid: pid} when reason == :noconnection ->
exit({{:nodedown, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
%Task{} ->
exit({reason, {__MODULE__, :find, [tasks, msg]}})
nil ->
@@ -221,7 +251,4 @@ defmodule Task do
def find(_tasks, _msg) do
nil
end
defp get_node({_, n}) when is_atom(n), do: n
defp get_node(pid) when is_pid(pid), do: pid
end
+70 -27
View File
@@ -1,15 +1,29 @@
defmodule Task.Supervised do
@moduledoc false
def start_link(:undefined, fun) do
:proc_lib.start_link(__MODULE__, :noreply, [fun])
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def async(caller, {module, fun, args}) do
def start_link(caller, info, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
end
def async(caller, info, mfa) do
initial_call(mfa)
ref = receive do: ({^caller, ref} -> ref)
send caller, {ref, do_apply(info, mfa)}
end
def reply(caller, info, mfa) do
initial_call(mfa)
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
ref =
# There is a race condition on this operation when working accross
# node that manifests if a `Task.Supervisor.async/1` call is made
@@ -33,36 +47,65 @@ defmodule Task.Supervised do
5000 -> exit(:timeout)
end
try do
apply(module, fun, args)
else
result ->
send caller, {ref, result}
catch
:error, reason ->
exit({reason, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
after
:erlang.unlink(caller)
end
send caller, {ref, do_apply(info, mfa)}
end
def reply(caller, mfa) do
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
async(caller, mfa)
def noreply(info, mfa) do
initial_call(mfa)
do_apply(info, mfa)
end
def noreply({module, fun, args}) do
:proc_lib.init_ack({:ok, self()})
defp initial_call(mfa) do
Process.put(:"$initial_call", get_initial_call(mfa))
end
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
{:module, module} = :erlang.fun_info(fun, :module)
{:name, name} = :erlang.fun_info(fun, :name)
{module, name, 0}
end
defp get_initial_call({mod, fun, args}) do
{mod, fun, length(args)}
end
defp do_apply(info, {module, fun, args} = mfa) do
try do
apply(module, fun, args)
catch
:error, reason ->
exit({reason, System.stacktrace()})
:error, value ->
exit(info, mfa, {value, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
exit(info, mfa, {{:nocatch, value}, System.stacktrace()})
:exit, value ->
exit(info, mfa, value)
end
end
defp exit(_info, _mfa, reason)
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown do
exit(reason)
end
defp exit(info, mfa, reason) do
{fun, args} = get_running(mfa)
:error_logger.format(
'** Task ~p terminating~n' ++
'** Started from ~p~n' ++
'** When function == ~p~n' ++
'** arguments == ~p~n' ++
'** Reason for termination == ~n' ++
'** ~p~n', [self, get_from(info), fun, args, reason])
exit(reason)
end
defp get_from({node, pid_or_name}) when node == node(), do: pid_or_name
defp get_from(other), do: other
defp get_running({:erlang, :apply, [fun, []]}) when is_function(fun, 0), do: {fun, []}
defp get_running({mod, fun, args}), do: {:erlang.make_fun(mod, fun, length(args)), args}
end
+29 -11
View File
@@ -7,12 +7,12 @@ defmodule Task.Supervisor do
`:simple_one_for_one` supervisor where the workers are temporary
(i.e. they are not restarted after they die).
The functions in this module allow tasks can be spawned and awaited
The functions in this module allow tasks to be spawned and awaited
from a supervisor, similar to the functions defined in the `Task` module.
## Name registering
## Name Registration
A `Task.Supervisor` is bound to the same name registering rules as a
A `Task.Supervisor` is bound to the same name registration rules as a
`GenServer`. Read more about it in the `GenServer` docs.
"""
@@ -22,17 +22,26 @@ defmodule Task.Supervisor do
The supported options are:
* `:name` - used to register a supervisor name, the supported values are
described under the `Name Registering` section in the `GenServer` module
described under the `Name Registration` section in the `GenServer` module
docs;
* `:restart` - the restart strategy, may be `:temporary` (the default),
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
Defaults to temporary as most tasks can't be effectively restarted after
a crash;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 miliseconds;
or an integer indicating the timeout value, defaults to 5000 milliseconds;
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
@@ -55,14 +64,15 @@ defmodule Task.Supervisor do
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
args = [self, get_info(self), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
ref = Process.monitor(pid)
send pid, {self(), ref}
%Task{pid: pid, ref: ref}
end
@doc """
Terminates the given child at pid.
Terminates the child with the given `pid`.
"""
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
def terminate_child(supervisor, pid) when is_pid(pid) do
@@ -80,9 +90,9 @@ defmodule Task.Supervisor do
@doc """
Starts a task as child of the given `supervisor`.
Note the spawned process is not linked to the caller but
Note that the spawned process is not linked to the caller, but
only to the supervisor. This command is useful in case the
task needs to emit side-effects (like I/O) and does not need
task needs to perform side-effects (like I/O) and does not need
to report back to the caller.
"""
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
@@ -98,6 +108,14 @@ defmodule Task.Supervisor do
"""
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
def start_child(supervisor, module, fun, args) do
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}])
end
defp get_info(self) do
{node(),
case Process.info(self, :registered_name) do
{:registered_name, []} -> self()
{:registered_name, name} -> name
end}
end
end
+117 -40
View File
@@ -7,6 +7,8 @@ defmodule URI do
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil
@type t :: %__MODULE__{}
import Bitwise
@ports %{
@@ -66,7 +68,7 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`.
keys and values are URL encoded as per `encode/2`.
Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden.
@@ -126,8 +128,10 @@ defmodule URI do
current =
case :binary.split(first, "=") do
[ key, value ] -> {decode(key), decode(value)}
[ key ] -> {decode(key), nil}
[key, value] ->
{decode_www_form(key), decode_www_form(value)}
[key] ->
{decode_www_form(key), nil}
end
{current, next}
@@ -142,33 +146,80 @@ defmodule URI do
end
defp pair({k, v}) do
encode(to_string(k)) <> "=" <> encode(to_string(v))
encode_www_form(to_string(k)) <>
"=" <> encode_www_form(to_string(v))
end
@doc """
Checks if the character is a "reserved" character in a URI.
Reserved characters are specified in RFC3986, section 2.2.
"""
def char_reserved?(c) do
c in ':/?#[]@!$&\'()*+,;='
end
@doc """
Checks if the character is a "unreserved" character in a URI.
Unreserved characters are specified in RFC3986, section 2.3.
"""
def char_unreserved?(c) do
c in ?0..?9 or
c in ?a..?z or
c in ?A..?Z or
c in '~_-.'
end
@doc """
Checks if the character is allowed unescaped in a URI.
This is the default used by `URI.encode/2` where both
reserved and unreserved characters are kept unescaped.
"""
def char_unescaped?(c) do
char_reserved?(c) or char_unreserved?(c)
end
@doc """
Percent-escape a URI.
Accepts `predicate` function as an argument to specify if char can be left as is.
## Example
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
"ftp://s-ite.tld/?value=put%20it+%D0%B9"
"""
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
defp percent(?_), do: <<?_>>
defp percent(?.), do: <<?.>>
defp percent(c)
when c >= ?0 and c <= ?9
when c >= ?a and c <= ?z
when c >= ?A and c <= ?Z do
<<c>>
def encode(str, predicate \\ &char_unescaped?/1) when is_binary(str) do
for <<c <- str>>, into: "", do: percent(c, predicate)
end
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
@doc """
Encode a string as "x-www-urlencoded".
## Example
iex> URI.encode_www_form("put: it+й")
"put%3A+it%2B%D0%B9"
"""
def encode_www_form(str) when is_binary(str) do
for <<c <- str>>, into: "" do
case percent(c, &char_unreserved?/1) do
"%20" -> "+"
pct -> pct
end
end
end
defp percent(c, predicate) do
if predicate.(c) do
<<c>>
else
"%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
end
end
defp hex(n) when n <= 9, do: <<n + ?0>>
defp hex(n), do: <<n + ?A - 10>>
@@ -183,28 +234,44 @@ defmodule URI do
"""
def decode(uri) do
decode(uri, uri)
unpercent(uri)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
end
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
@doc """
Decode a string as "x-www-urlencoded".
## Examples
iex> URI.decode_www_form("%3Call+in%2F")
"<all in/"
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
def decode(<<head, tail :: binary >>, uri) do
<<check_plus(head)>> <> decode(tail, uri)
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
end
def decode(<<>>, _uri), do: <<>>
defp unpercent(<<>>), do: <<>>
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
defp check_plus(c), do: c
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
@@ -222,6 +289,8 @@ defmodule URI do
host: "elixir-lang.org", port: 80}
"""
def parse(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
@@ -240,7 +309,7 @@ defmodule URI do
scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do
if is_nil(port) and not is_nil(scheme) do
port = default_port(scheme)
end
@@ -267,7 +336,7 @@ defmodule URI do
# to replace those with nil for consistency.
defp nillify(l) do
for s <- l do
if size(s) > 0, do: s, else: nil
if byte_size(s) > 0, do: s, else: nil
end
end
end
@@ -280,12 +349,20 @@ defimpl String.Chars, for: URI do
if uri.port == port, do: uri = %{uri | port: nil}
end
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
if uri.host do
authority = uri.host
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
else
authority = uri.authority
end
result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.scheme, do: result = result <> uri.scheme <> ":"
if authority, do: result = result <> "//" <> authority
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
+28 -9
View File
@@ -58,18 +58,35 @@ defmodule Version do
@type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t]}
@type major :: String.t | non_neg_integer
@type minor :: non_neg_integer | nil
@type patch :: non_neg_integer | nil
@type pre :: [String.t]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
patch :: patch,
pre :: pre}
@type t :: %__MODULE__{
major: major,
minor: minor,
patch: patch,
pre: pre,
build: build}
defmodule Requirement do
@moduledoc false
defstruct [:source, :matchspec]
@type t :: %__MODULE__{}
end
defexception InvalidRequirementError, [:message]
defexception InvalidVersionError, [:message]
defmodule InvalidRequirementError do
defexception [:message]
end
defmodule InvalidVersionError do
defexception [:message]
end
@doc """
Check if the given version matches the specification.
@@ -152,7 +169,8 @@ defmodule Version do
## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1)
iex> {:ok, version} = Version.parse("2.0.1-alpha1")
iex> version
#Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
@@ -176,7 +194,8 @@ defmodule Version do
## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
iex> req
#Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1")
@@ -305,7 +324,7 @@ defmodule Version do
patch = nillify(patch)
pre = nillify(pre)
if nil?(minor) or (nil?(patch) and not approximate?) do
if is_nil(minor) or (is_nil(patch) and not approximate?) do
:error
else
major = String.to_integer(major)
+1 -4
View File
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
def project do
[app: :elixir,
version: System.version,
build_per_environment: false,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n"]
build_per_environment: false]
end
end
+53 -34
View File
@@ -2,7 +2,7 @@
%% private to the Elixir compiler and reserved to be used by Elixir only.
-module(elixir).
-behaviour(application).
-export([main/1, start_cli/0,
-export([start_cli/0,
string_to_quoted/4, 'string_to_quoted!'/4,
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
@@ -18,21 +18,30 @@
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [binary,{encoding,utf8}]),
io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
"warning: the VM is running with native name encoding of latin1 which may cause "
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
"(which can be verified by running \"locale\" in your shell)~n", []);
_ ->
ok
end
end),
%% In case there is a shell, we can't really change its
%% encoding, so we just set binary to true. Otherwise
%% we must set the encoding as the user with no shell
%% has encoding set to latin1.
Opts =
case init:get_argument(noshell) of
{ok, _} -> [binary,{encoding,utf8}];
error -> [binary]
end,
ok = io:setopts(standard_io, Opts),
%% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
"warning: the VM is running with native name encoding of latin1 which may cause "
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
"(which can be verified by running \"locale\" in your shell)~n", []);
_ ->
ok
end,
elixir_sup:start_link().
stop(_S) ->
@@ -41,16 +50,21 @@ stop(_S) ->
config_change(_Changed, _New, _Remove) ->
ok.
%% escript entry point
main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
%% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
application:start(?MODULE),
{ok, _} = application:ensure_all_started(?MODULE),
%% We start the Logger so tools that depend on Elixir
%% always have the Logger directly accessible. However
%% Logger is not a dependency of the Elixir application,
%% which means releases that want to use Logger must
%% always list it as part of its applications.
_ = case code:ensure_loaded('Elixir.Logger') of
{module, _} -> application:start(logger);
{error, _} -> ok
end,
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
@@ -65,37 +79,37 @@ env_for_eval(Opts) ->
env_for_eval(Env, Opts) ->
Line = case lists:keyfind(line, 1, Opts) of
{line, RawLine} when is_integer(RawLine) -> RawLine;
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
false -> ?m(Env, line)
end,
File = case lists:keyfind(file, 1, Opts) of
{file, RawFile} when is_binary(RawFile) -> RawFile;
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
false -> ?m(Env, file)
end,
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{delegate_locals_to, LocalOpt} -> LocalOpt;
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
false -> ?m(Env, local)
end,
Aliases = case lists:keyfind(aliases, 1, Opts) of
{aliases, AliasesOpt} -> AliasesOpt;
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
false -> ?m(Env, aliases)
end,
Requires = case lists:keyfind(requires, 1, Opts) of
{requires, List} -> ordsets:from_list(List);
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
false -> ?m(Env, requires)
end,
Functions = case lists:keyfind(functions, 1, Opts) of
{functions, FunctionsOpt} -> FunctionsOpt;
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
false -> ?m(Env, functions)
end,
Macros = case lists:keyfind(macros, 1, Opts) of
{macros, MacrosOpt} -> MacrosOpt;
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
false -> ?m(Env, macros)
end,
@@ -108,7 +122,7 @@ env_for_eval(Env, Opts) ->
file := File, local := Local, module := Module,
macros := Macros, functions := Functions,
requires := Requires, aliases := Aliases, line := Line
}.
}.
%% String evaluation
@@ -146,14 +160,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
{atom, _, Atom} ->
{Atom, Binding, NewEnv, NewScope};
_ ->
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
end.
erl_eval(Erl, ParsedBinding) ->
erl_eval(Erl, ParsedBinding, E) ->
case erl_eval:check_command([Erl], ParsedBinding) of
ok -> ok;
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
end,
% Below must be all one line for locations to be the same when the stacktrace
% needs to be extended to the full stacktrace.
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
get_stacktrace() ->
Stacktrace = erlang:get_stacktrace(),
@@ -21,7 +21,7 @@
-record(elixir_quote, {
line=false,
keep=false,
file=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -41,28 +41,29 @@
%% Used in tokenization and interpolation
%% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
-define(is_bin(S), (S >= $0 andalso S =< $1)).
-define(is_octal(S), (S >= $0 andalso S =< $7)).
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Atoms
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
-define(is_quote(S), (S == $" orelse S == $')).
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
-define(is_invalid_space(S), (S == 16#A0)).
+1 -1
View File
@@ -5,7 +5,7 @@
inspect(Atom) when is_atom(Atom) ->
case elixir_compiler:get_opt(internal) of
true -> atom_to_binary(Atom, utf8);
true -> atom_to_binary(Atom, utf8);
false -> 'Elixir.Inspect.Atom':inspect(Atom)
end.
+93 -67
View File
@@ -35,70 +35,99 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info
expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, default, [], E);
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E).
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, ListArgs) of
type ->
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
size ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
case Size of
default ->
{[EArg], EE} = elixir_exp:expand_args(Args, E),
case EArg of
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
ok;
_ when is_integer(EArg) ->
ok;
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"size in bitstring expects an integer or a variable as argument, got: ~ts",
['Elixir.Macro':to_string(EArg)])
end,
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"duplicated size definition in bitstring")
end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
case expand_bit_type(Expr, Args) of
type ->
{EArgs, EE} = elixir_exp:expand_args(Args, E),
validate_bit_type_args(Meta, Expr, EArgs, EE),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
end;
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) ->
case Size of
expand_bit_info(Meta, [], Size, Types, _) ->
[H|T] = case Size of
default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)]
end.
_ -> lists:reverse(Types, [Size])
end,
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
expand_bit_type_or_size(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type;
expand_bit_type_or_size(little, []) -> type;
expand_bit_type_or_size(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size;
expand_bit_type_or_size(_, _) -> none.
expand_bit_type(binary, []) -> type;
expand_bit_type(integer, []) -> type;
expand_bit_type(float, []) -> type;
expand_bit_type(bitstring, []) -> type;
expand_bit_type(bytes, []) -> type;
expand_bit_type(bits, []) -> type;
expand_bit_type(utf8, []) -> type;
expand_bit_type(utf16, []) -> type;
expand_bit_type(utf32, []) -> type;
expand_bit_type(signed, []) -> type;
expand_bit_type(unsigned, []) -> type;
expand_bit_type(big, []) -> type;
expand_bit_type(little, []) -> type;
expand_bit_type(native, []) -> type;
expand_bit_type(unit, [_]) -> type;
expand_bit_type(_, _) -> none.
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unit in bitstring expects an integer as argument, got: ~ts",
['Elixir.Macro':to_string(Unit)]);
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
ok.
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
Expr ->
elixir_errors:compile_error(ExprMeta, ?m(E, file),
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other ->
List = case is_list(Other) of true -> Other; false -> [Other] end,
expand_bit_info(Meta, List ++ T, Size, Types, E)
Info ->
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
end.
unpack_bit_info({'-', _, [H, T]}, Acc) ->
unpack_bit_info(H, unpack_bit_info(T, Acc));
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
[{unit, [], [Unit]}|Acc];
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
unpack_bit_info(Size, Acc) when is_integer(Size) ->
[{size, [], [Size]}|Acc];
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
[{Expr, Meta, ListArgs}|Acc];
unpack_bit_info(Other, Acc) ->
[Other|Acc].
%% Translation
has_size({bin, _, Elements}) ->
@@ -124,7 +153,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S};
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
{Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
@@ -201,23 +230,20 @@ unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
case elixir_translator:translate(Arg, S) of
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
{Size, extract_bit_type(Meta, T, S)};
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{default, extract_bit_type(Meta, T, S)}.
extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
{extract_bit_size(Size, S), extract_bit_type(L, [])};
extract_bit_info({size, _, [Size]}, S) ->
{extract_bit_size(Size, S), []};
extract_bit_info(L, _S) ->
{default, extract_bit_type(L, [])}.
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
[Other|extract_bit_type(Meta, T, S)];
extract_bit_type(_Meta, [], _S) ->
[].
extract_bit_size(Size, S) ->
{TSize, _} = elixir_translator:translate(Size, S),
TSize.
extract_bit_type({'-', _, [L, R]}, Acc) ->
extract_bit_type(L, extract_bit_type(R, Acc));
extract_bit_type({unit, _, [Arg]}, Acc) ->
[{unit, Arg}|Acc];
extract_bit_type({Other, _, []}, Acc) ->
[Other|Acc].
+24 -17
View File
@@ -1,18 +1,18 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
%% Get pairs from a clause.
get_pairs(Key, Clauses) ->
get_pairs(Key, Clauses, false).
get_pairs(Key, Clauses, AllowNil) ->
get_pairs(Key, Clauses, As) ->
get_pairs(Key, Clauses, As, false).
get_pairs(Key, Clauses, As, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{Key, Pairs} when is_list(Pairs) ->
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
{Key, nil} when AllowNil ->
[];
false ->
@@ -79,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {SAcc, VAcc}) ->
{TX, TS} = each_clause(X, Return, SAcc),
{TX,
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
{TX, TS} = each_clause(Meta, X, Return, SAcc),
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
end,
{TClauses, {TS, ReverseCV}} =
@@ -140,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
% Handle each key/value clause pair and translate them accordingly.
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
{TCondition, SC} = elixir_translator:translate(Condition, S),
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
wrap_export_fun(Meta, Fun) ->
case lists:keyfind(export_head, 1, Meta) of
{export_head, true} ->
Fun;
_ ->
fun(Args, S) ->
{TArgs, TS} = Fun(Args, S),
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
end
end.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
% existing assignments instead of creating new ones every time.
@@ -181,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
export_vars=orddict:store(Key, Value, ClauseVars)
},
},
Expr = case orddict:find(Key, Vars) of
{ok, {PreValue, _}} -> {var, 0, PreValue};
+17 -5
View File
@@ -11,6 +11,7 @@
loaded=[],
at_exit=[],
pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
}).
@@ -27,9 +28,13 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% We attempt to load those modules here so throughout
%% the codebase we can use a code:is_loaded/1 check on
%% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ok, #elixir_code_server{}}.
handle_call({acquire, Path}, From, Config) ->
@@ -64,7 +69,8 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
{reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
@@ -83,6 +89,9 @@ handle_call(erl_compiler_options, _From, Config) ->
{reply, Opts, Config}
end;
handle_call(paths, _From, Config) ->
{reply, Config#elixir_code_server.paths, Config};
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
@@ -115,7 +124,7 @@ handle_cast({loaded, Path}, Config) ->
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
@@ -131,6 +140,9 @@ handle_cast({unload_files, Files}, Config) ->
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.

Some files were not shown because too many files have changed in this diff Show More