Compare commits

...
142 Commits
Author SHA1 Message Date
José Valim a613445f78 Release v0.9.2 2013-06-13 15:54:52 +02:00
José Valim e4ea0a6e36 Update CHANGELOG 2013-06-13 15:51:08 +02:00
José Valim a03639fe1a Deprecate Regex.index/2 2013-06-13 15:23:03 +02:00
José Valim e7b1242e98 Fix a bug on Path.split/1 2013-06-13 15:13:56 +02:00
José Valim d3d83b17f4 Merge pull request #1199 from alco/1148-match-tilde-op
Reimplement =~ to use Regex.match? or String.contains?
2013-06-13 06:13:32 -07:00
José Valim 78eb14edae Merge pull request #1187 from alco/pipeline-op
Pipeline op fix + additional assertion
2013-06-13 06:12:25 -07:00
José Valim a7d931ce21 Merge pull request #1237 from edgurgel/fix-typo-on-list-doc
Fix typo on List documentation
2013-06-12 20:43:58 -07:00
Eduardo Gurgel b1cd1ee7e9 Fix typo on List documentation 2013-06-12 22:30:12 -03:00
José Valim 1513ca7c14 Merge pull request #1235 from mururu/update-docs
Update docs for `defoverridable` and `super`
2013-06-12 11:11:37 -07:00
Yuki Ito 016480bdcc Update docs for defoverridable and super 2013-06-13 02:35:13 +09:00
José Valim 857af06aff Merge pull request #1234 from mururu/deprecate-implicit-args
Implicit super arguments is deprecated
2013-06-12 08:56:55 -07:00
Yuki Ito 961099746e Implicit super arguments is deprecated 2013-06-13 00:42:34 +09:00
José Valim 8247382967 Merge pull request #1233 from alco/1226-doctest-perf
Optimize doctest generation. Closes #1226
2013-06-12 06:10:00 -07:00
Alexei Sholik 9b0d972571 Optimize doctest generation. Closes #1226 2013-06-12 15:55:06 +03:00
Alexei Sholik b19c389cee Replace @export_type with @typep in the docs
Elixir automatically generates corresponding @export_type attributes for
public types
2013-06-12 12:58:40 +03:00
Alexei Sholik 2243235e08 Merge pull request #1229 from alco/1132-doc-module-attr
Provide reference documentation for module attributes
2013-06-11 10:30:07 -07:00
Alexei Sholik a72457112f Add support for h AnyModule.__info__. Closes #1230 2013-06-11 20:27:01 +03:00
Alexei Sholik 3f3e86d7f4 Document __info__/1 2013-06-11 20:14:14 +03:00
Alexei Sholik 15bf0fb16e Provide reference documentation for module attributes 2013-06-11 16:46:55 +03:00
Alexei Sholik 5184edd432 Fix code escaping in ExUnit.Callbacks docstring 2013-06-11 14:05:19 +03:00
José Valim fc9506e28e Add missing rm -rf ebin back to docs task 2013-06-10 21:24:16 +08:00
José Valim 5c01be91ea Fix Makefile dependencies 2013-06-10 15:10:42 +08:00
José Valim fed406a2e6 Rename test/doctest.exs file to test/doc_test.exs to follow file name convention 2013-06-10 14:27:38 +08:00
José Valim 26904197d5 Merge pull request #1222 from jjh42/doc-error
Added more informative error to Makefile when ex_doc is not available.
2013-06-09 18:29:14 -07:00
José Valim f552d7620f Merge pull request #1221 from alexrp/mix-local-install
Add a --force option to `mix local.install`.
2013-06-09 18:25:45 -07:00
Jonathan J Hunt ce55936b45 Added more informative error to Makefile when ex_doc is not available.
Added more information to README about building docs.

fixes #1151
2013-06-09 16:04:48 -07:00
alexrp 3167d78711 Add a --force option to mix local.install.
Useful for makefiles.
2013-06-09 23:34:54 +02:00
José Valim 05eb42612b Merge pull request #1217 from ericmj/mix-outofdatedepserror
Improve error message for diverged deps
2013-06-09 07:58:40 -07:00
Eric Meadows-Jonsson 6720522707 Improve error message for diverged deps 2013-06-09 16:02:33 +02:00
José Valim 0520b138f8 Merge pull request #1218 from zambal/remove-bash-dependencies
remove dependency on bash in shell scripts
2013-06-09 06:54:39 -07:00
zambal 0ddb31992b removed use of local variables in shell scripts in order to remove dependency on bash 2013-06-09 14:51:29 +02:00
Alexei Sholik 4a1dfccbec Use =~ with strings where it makes sense 2013-06-08 15:58:34 +03:00
Alexei Sholik c62a7e1001 Reimplement =~ to use Regex.match? or String.contains? 2013-06-08 15:00:09 +03:00
Alexei Sholik 2b8854c63e Add a few sanity check tests for upcase & downcase 2013-06-08 14:33:04 +03:00
José Valim b535f0746e No need for accumulator on downcase/upcase 2013-06-08 16:22:44 +08:00
José Valim 4b795a042b Optimize downcase, upcase and rstrip in Unicode 2013-06-08 15:24:51 +08:00
José Valim c80e52ee39 Merge pull request #1198 from ericmj/patch-1194
Dont run deps tasks recursively when they are started non-recursively
2013-06-07 18:14:44 -07:00
Eric Meadows-Jonsson 520415b013 Dont run any tasks recursively from another task
The only time a task may run recursively is after a new project has been pushed to the stack.
2013-06-07 18:10:34 +02:00
José Valim 27682320d4 Merge pull request #1211 from mururu/to-binary-uri-path
to_binary for URI.Info do not ignore the path
2013-06-07 05:50:32 -07:00
Eric Meadows-Jonsson 98ade3b8fb Convert project stack to records 2013-06-07 14:45:26 +02:00
José Valim 263c76f5e5 Merge pull request #1209 from mururu/rm-case
Remove unneeded `case`
2013-06-07 05:39:17 -07:00
Yuki Ito bd984a545d to_binary for URI.Info do not ignore the path 2013-06-07 20:28:56 +09:00
Yuki Ito 115e022025 Remove unneeded case
`:os.type/0` always returns a tuple in R16B
2013-06-07 18:45:34 +09:00
José Valim dc5fe48639 Merge pull request #1208 from mururu/fix-spec
Fix Enum.uniq/2 spec
2013-06-07 02:02:42 -07:00
Yuki Ito 155b937327 Fix Enum.uniq/2 spec 2013-06-07 17:59:09 +09:00
José Valim 6ef10d4b0d Add tests for compile_string and compile_quoted, closes #1206 2013-06-07 14:48:19 +08:00
José Valim 4f2c2ceb95 Wrap rebar command on windows inside escript.exe 2013-06-07 14:35:41 +08:00
José Valim 078522d014 Fix iex -S mix on Windows 2013-06-07 13:50:04 +08:00
José Valim c765a95b91 Ensure default calls are connected in dispatch tracker 2013-06-07 13:34:41 +08:00
José Valim 8836405bc5 Merge pull request #1201 from alco/exunit-docs
Extend ExUnit docs
2013-06-06 16:21:18 -07:00
Alexei Sholik dd2e0f6064 Mention mix and testing conventions in ExUnit's doc 2013-06-06 19:44:10 +03:00
Alexei Sholik 23982f1744 Extend the documentation for ExUnit.Callbacks. Closes #1142 2013-06-06 19:44:02 +03:00
Alexei Sholik a6f04a2224 Fix Record's moduledoc 2013-06-06 13:03:14 +03:00
Alexei Sholik 7291abb26f Move the description of records to Record's moduledoc. Closes #1166 2013-06-06 12:52:22 +03:00
Alexei Sholik 55cb8ab436 Merge pull request #1195 from alco/string-fixes
String fixes
2013-06-05 09:20:54 -07:00
Alexei Sholik 32cddf2738 Reorganize tests for starts_with? and co. 2013-06-05 18:58:29 +03:00
Alexei Sholik 7893852f2f Fix edge case for String.contains? 2013-06-05 18:58:16 +03:00
Alexei Sholik f1b625ccd2 Don't support nested lists in starts_with? and co. 2013-06-05 18:44:22 +03:00
Alexei Sholik caa7d41e96 Refactor String.ends_with?
Using :binary.match should save us an allocation. Plus, the function
now looks more similar to starts_with?
2013-06-05 18:22:48 +03:00
Alexei Sholik b96a714240 Fix edge cases for starts_with?, ends_with? and contains? 2013-06-05 18:14:38 +03:00
Yurii Rashkovskii d39a52ae79 Fix minor compile-time warnings 2013-06-05 07:55:10 -07:00
José Valim 3e66197318 Merge pull request #1193 from ericmj/docs-overridable
Document `super` and add more docs to `defoverridable`
2013-06-05 07:44:23 -07:00
Eric Meadows-Jonsson 065b7f3f8f Document super and add more docs to defoverridable 2013-06-05 16:05:11 +02:00
José Valim 6004a3614f Update CHANGELOG 2013-06-05 20:11:12 +08:00
José Valim 22627c44df Merge pull request #1188 from josephwilk/string-contains
Adding String.contains?
2013-06-05 05:04:44 -07:00
José Valim 6e959733dd Merge pull request #1186 from ericmj/mix-umbrella-deps
Add support for direct dependencies of umbrella project
2013-06-05 05:03:04 -07:00
Eric Meadows-Jonsson 7d2d352236 Add deps to 'mix new --umbrella' 2013-06-05 13:24:16 +02:00
Eric Meadows-Jonsson 9d61e5886c Add support for direct dependencies of umbrella 2013-06-05 13:20:54 +02:00
Alexei Sholik 37c8867d9f Implement assert_compile_fail
The format_rescue function has been found useful in multiple instances,
so it made sense to generalize it into its own assertion.
2013-06-05 13:14:29 +03:00
Alexei Sholik da7e830bf7 Disallow &-partial calls on the right side of |> 2013-06-05 13:08:16 +03:00
José Valim c78d3b3717 Clean up DocTestTest 2013-06-05 18:00:26 +08:00
José Valim cafbd2c2d4 Ensure --cookie is set before remsh starts, closes #1190 2013-06-05 17:55:48 +08:00
josephwilk ae8def380a :binary.match supports a list of matches 2013-06-05 11:27:08 +02:00
José Valim 37cb1801b5 Merge pull request #1181 from jjh42/add-version-option
Added -v --version option to all elixir commands.
2013-06-04 23:07:50 -07:00
Jonathan J Hunt dd43e799c8 Added -v --version option to all elixir commands.
Fixes #1178
2013-06-04 23:01:29 -07:00
José Valim e93d4197ef Merge pull request #1183 from alco/system-bootstrap
Bring system.ex back to bootstrap
2013-06-04 17:02:15 -07:00
Alexei Sholik d55a1703bd Bring system.ex back to bootstrap. Closes #1155 2013-06-05 02:50:50 +03:00
José Valim 87268e9c78 Merge pull request #1185 from meh/uri-to_binary
Implement Binary.Chars for URI.Info
2013-06-04 16:41:02 -07:00
Joseph Wilk f0ecfbfac9 Adding String.contains?
Detect whether a string contains another string.
2013-06-04 19:20:11 +02:00
meh 854cab51bc Implement Binary.Chars for URI.Info 2013-06-04 16:12:55 +02:00
José Valim ffdf9e9a35 Merge pull request #1184 from ericmj/mix-skip-deps
Make rebar skip deps since mix compiles them
2013-06-04 02:55:11 -07:00
Eric Meadows-Jonsson 6d74511e92 Make rebar skip deps since mix compiles them 2013-06-04 11:45:58 +02:00
Alexei Sholik 75a1373bbb Merge pull request #1137 from alco/919-iex-test-fmwk
Test framework for IEx
2013-06-03 05:13:13 -07:00
Alexei Sholik d26e4b3dbc Minor comment cleanup 2013-06-03 15:07:47 +03:00
Alexei Sholik d636dc0ff0 Proof-read the doc for capture_io 2013-06-03 15:07:47 +03:00
Alexei Sholik ed0b161992 Add prompt test 2013-06-03 15:07:47 +03:00
Alexei Sholik 5e640336e0 Update the docs for Code.load_file and Code.require_file 2013-06-03 15:07:47 +03:00
Alexei Sholik 99c7d84f9c Update the r helper 2013-06-03 15:07:47 +03:00
Alexei Sholik 893a52a95a Clean up test code for module helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik a8d2e45d2f Add docs for IEx test framework 2013-06-03 15:07:47 +03:00
Alexei Sholik ecd3c19c64 Add more tests for shell coloring 2013-06-03 15:07:47 +03:00
Alexei Sholik 7f9a2ed17b Don't process ANSI escapes in ExUnit output 2013-06-03 15:07:47 +03:00
Alexei Sholik 7951f73925 Update the tests for l and r helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik a79d965248 Rewrite tests for helpers to use capture_iex and capture_io sparingly 2013-06-03 15:07:47 +03:00
Alexei Sholik 958550c5cd Write tests for IEx helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik 997a798994 Implementation of a basic test framework for IEx. Closes #919 2013-06-03 15:07:47 +03:00
José Valim c0547a15ce Ensure Enum.to_list and Dict.to_list return exactly the same result 2013-06-03 20:35:18 +09:00
José Valim 3918b889f1 Merge pull request #1177 from fishcakez/hashdict-unordered
Fix HashDict so that key comparison uses exact match
2013-06-03 04:29:34 -07:00
James Fish d1fd5a3c2a Fix HashDict so that key comparison uses exact match
This change means that HashDict buckets are nolonger strictly ordered.
Previously keys which compared equal (==) but not exactly equal (===)
would be considered different for large sizes but equivalent for small
sizes. This ambiguity occured because hashes are only equal when terms
are exactly equal (===), so both now use exact (===) key comparisons.
Small HashDicts are still a single ordered list and large HashDicts
contain ordered buckets. Both of these may contain keys that compare
equal (==) but are not exactly equal (===), should these keys be present
in the same list or bucket the ordering of these keys (relative to each
other) is undefined.

See github issue #1171 for examples.

Note: If assumptions were made about the form of a HashDict they may
nolonger be valid. Particularly that HashDicts can nolonger be reliably
pattern matched on for equality.
2013-06-03 02:13:33 +01:00
José Valim a3aa1e8de5 Ensure noname scope does not leak after assignment 2013-06-03 00:24:14 +09:00
José Valim 7c379aa50a Merge pull request #1174 from mururu/capture_prompt_option
Option that enable to decide whether `capture_io` captures a prompt or not
2013-06-02 05:07:42 -07:00
Yuki Ito 1fa29be448 Option that enable to decide whether capture_io captures a prompt or not 2013-06-02 19:15:06 +09:00
José Valim b9a60b2668 Merge pull request #1170 from carlosmn/load-path-make
Set load paths for dependencies with Makefile
2013-06-01 20:02:34 -07:00
José Valim ab9735533e Merge pull request #1172 from carlosmn/iex-r
Fix IEx's module source lookup
2013-06-01 20:00:36 -07:00
Carlos Martín Nieto 6b2b928867 Fix IEx's module source lookup
Module.module_info(:compile)[:source] returns the value, not a tuple
as the code was expecting, making r/1 think that no module has a
source. Fix this expectation.
2013-06-02 00:33:31 +02:00
Carlos Martín Nieto 4b1ea18625 Set load paths for dependencies with Makefile
A recent update (036d016 2013-06-02; Rebar dep with no config is given
rebar as manager) introduced :make as a value for a dependency's
manager, but Mix.Deps.load_path has no function clause to handle this.

Add :make to the nil manager clause, as it seems the closest
equivalent.
2013-06-01 22:35:24 +02:00
José Valim 7cd98e149c Speed up escriptize tests 2013-06-02 00:36:02 +09:00
José Valim 036d016053 Rebar dep with no config is given rebar as manager 2013-06-02 00:35:30 +09:00
José Valim be2aedff98 Fix typo on |> docs, closes #1168 2013-06-01 23:57:58 +09:00
José Valim e19d666a14 Merge pull request #1167 from mururu/prompt
capture_io captures a prompt when capturing of stdio
2013-06-01 03:15:38 -07:00
Yuki Ito 9840ac266b capture_io captures a prompt when capturing of stdio 2013-06-01 17:07:28 +09:00
José Valim bcfc927455 Improve docs for IEx helper l/1 2013-06-01 10:45:34 +09:00
José Valim 014a2ec7a8 By default, start the application on escript 2013-06-01 08:12:47 +09:00
José Valim 4846d42101 Merge pull request #1160 from jjh42/alias-nil-correctly
Don't ignore nil during aliasing. Attempt to fix #1159
2013-05-31 16:10:48 -07:00
Jonathan J Hunt 636da42f2a Don't ignore nil during aliasing. Attempt to fix #1159 2013-05-31 15:58:15 -07:00
José Valim 7c4516f1ec Do not access another task directly, use Mix.Rebar module instead 2013-06-01 07:46:54 +09:00
Eric Meadows-Jonsson 65cc78034d Correctly add load_paths for rebar dependencies
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-06-01 07:41:06 +09:00
José Valim f97a384b82 Move dependencies callbacks back into Mix.Deps.Converger
The converger is the one that knows if a dependency exists or
not and, as such, is the best one to know if a dependency callback
should be invoked or not.

This commit also normalizes some of the Retriever internal
functions, for example, now we have mix_children and rebar_children.
The goal is to extract those functions (and the ones in Mix.Deps)
one by one into a Mix.DepsManager behaviour which will be implemented
by Mix.DepsManager.Mix and Mix.DepsManager.Rebar, removing all
the conditional calls existing in the code.
2013-06-01 07:19:23 +09:00
Alexei Sholik 7e856e1af0 Don't process ANSI escapes in strings in IEx 2013-05-31 21:48:31 +03:00
José Valim 893e333204 Update changelog and move version to 0.9.2.dev 2013-06-01 03:44:58 +09:00
José Valim 5e94a69693 Merge pull request #1163 from meh/regex-escape
Support extended syntax in Regex.escape
2013-05-31 11:13:16 -07:00
José Valim 5775d49c08 Merge pull request #1164 from sasa1977/document_record_features
documentation for record generated functions
2013-05-31 11:12:33 -07:00
José Valim 6397cedec6 Do not interpret ansi escape codes in IEx results 2013-06-01 03:10:11 +09:00
meh 3dd445bcce Add documentation to Regex.escape 2013-05-31 19:04:26 +02:00
meh 5e803487ef Improve Regex.escape tests 2013-05-31 19:04:26 +02:00
meh caeea8973c Support extended syntax in Regex.escape 2013-05-31 19:04:26 +02:00
Yurii Rashkovskii 6656f7c843 Minor indentation fix 2013-05-31 09:54:28 -07:00
Yurii Rashkovskii e7a872c975 Mix.Shell: Fix incorrect Windows' COMSPEC handling 2013-05-31 09:49:52 -07:00
sasa1977 ea797815a0 document update and to_keywords 2013-05-31 15:46:05 +02:00
José Valim 453d8835bd Merge pull request #1150 from alco/to_binary-error
Better error message for Binary.Chars.List.to_binary. Closes #1144
2013-05-31 02:21:16 -07:00
Alexei Sholik 2272dd1c46 Better error message for Binary.Chars.List.to_binary. Closes #1144 2013-05-31 12:14:51 +03:00
José Valim 41b5f4c826 Merge pull request #1122 from ericmj/mix-rebar
Handle rebar dependencies better
2013-05-31 02:14:05 -07:00
José Valim ac0ac7f024 Merge pull request #1153 from meh/regex-escape
Implement Regex.escape
2013-05-30 19:38:51 -07:00
meh 5ff3b760d9 Implement Regex.escape 2013-05-31 04:26:43 +02:00
José Valim fe97c4886a Merge pull request #1152 from ericmj/mix-sh
Use the shell to run Mix.Shell.cmd commands
2013-05-30 17:06:01 -07:00
Eric Meadows-Jonsson 0b45b61de8 Use the shell to run Mix.Shell.cmd commands 2013-05-31 00:20:52 +02:00
Eric Meadows-Jonsson 406edcb07d Use rebar from project root directory 2013-05-30 15:22:05 +02:00
Eric Meadows-Jonsson 6c813a140d Change project/rebar to manager/source in Mix.Dep 2013-05-29 19:00:07 +02:00
Eric Meadows-Jonsson dfc40703f0 Use Mix.Rebar as module for project pushing 2013-05-29 17:02:27 +02:00
Eric Meadows-Jonsson f06f15184c Clean up code, add tests, fix bugs found in tests 2013-05-28 19:39:55 +02:00
Eric Meadows-Jonsson 0cbf50c53a Handle raw option in rebar deps 2013-05-26 23:30:15 +02:00
Eric Meadows-Jonsson 1d87a778ef Handle rebar dependencies better
Download dependencies specified in rebar.config and go through
Converger to handle conflicts.
Handle `sub_dirs` in rebar.config when getting dependencies and
by the sub_dirs to the code load path.
2013-05-26 23:24:42 +02:00
118 changed files with 3153 additions and 1031 deletions
+1
View File
@@ -14,3 +14,4 @@ erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.*.swp
docs
+25
View File
@@ -1,3 +1,28 @@
# v0.9.2 (2013-06-13)
* enhancements
* [ExUnit] `capture_io` now captures prompt by default
* [Mix] Automatically import git dependencies from Rebar
* [Mix] Support for dependencies directly from the umbrella application
* [Regex] Add `Regex.escape`
* [String] Add `String.contains?`
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
* bug fix
* [HashDict] Ensure HashDict uses exact match throughout its implementation
* [IEx] Do not interpret ANSI codes in IEx results
* [IEx] Ensure --cookie is set before accessing remote shell
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
* [Mix] Start the application by default on escripts
* deprecations
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
* [Kernel] `super` no longer supports implicit arguments
* backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
* enhancements
+8 -4
View File
@@ -96,13 +96,17 @@ clean:
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
docs: compile
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)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
rm -rf ebin
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@ false
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
@@ -127,11 +131,11 @@ test_erlang: compile
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
test_elixir: test_kernel test_ex_unit test_doctest test_mix test_eex test_iex
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
test_doctest: compile
test_doc_test: compile
@ echo "==> doctest (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
+9
View File
@@ -20,6 +20,15 @@ However, if tests fail, it is likely you have an outdated Erlang version (Elixir
If you have the correct version and tests still fail, feel free to [open an issue][2].
## Building documentation
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
# After cloning and compiling Elixir
$ git clone git://github.com/elixir-lang/ex_doc.git
$ cd ../ex_doc && ../elixir/bin/mix compile
$ cd ../elixir && make docs
## Contributing
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
+1 -1
View File
@@ -1 +1 @@
0.9.1
0.9.2
+7 -2
View File
@@ -26,7 +26,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
@@ -43,12 +43,17 @@ while [ $I -le $# ]; do
case "$PEEK" in
-v|--compile|--no-halt|+iex|+compile)
;;
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
-e|-r|-pr|-pa|-pz|--remsh|-S)
S=2
;;
--detached|--hidden)
ERL="$ERL `echo $PEEK | cut -c 2-`"
;;
--cookie)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL -setcookie "$VAL""
;;
--sname|--name)
I=$(expr $I + 1)
eval "VAL=\${$I}"
+1 -1
View File
@@ -14,7 +14,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
+1 -1
View File
@@ -25,7 +25,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
+3 -3
View File
@@ -76,8 +76,8 @@ defmodule EEx.AssignsEngine do
quote(do: Keyword.get var!(assigns), unquote(name))
end
defp transform(_) do
super
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
@@ -94,4 +94,4 @@ defmodule EEx.SmartEngine do
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
end
+9 -1
View File
@@ -58,7 +58,15 @@ defimpl Binary.Chars, for: List do
"""
def to_binary(thing) do
iolist_to_binary(thing)
try do
iolist_to_binary(thing)
rescue
ArgumentError ->
raise Protocol.UndefinedError,
protocol: __MODULE__,
structure: thing,
extra: "Only iolists are supported"
end
end
end
+6 -6
View File
@@ -190,10 +190,10 @@ defmodule Code do
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
Loads the given `file`. Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples { ModuleName, <<byte_code>> }, one tuple for each
module defined in the file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
@@ -211,8 +211,8 @@ defmodule Code do
@doc """
Requires the given `file`. Accepts `relative_to` as an argument to tell where
the file is located. It returns all the modules defined in the file. If the
file was already required/loaded, doesn't do anything and returns nil.
the file is located. The return value is the same as that of `load_file`. If
the file was already required/loaded, doesn't do anything and returns nil.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
+1 -1
View File
@@ -946,7 +946,7 @@ defmodule Enum do
"""
@spec uniq(t) :: list
@spec uniq(t, (element -> term)) :: boolean
@spec uniq(t, (element -> term)) :: list
def uniq(collection, fun // fn x -> x end)
def uniq(collection, fun) when is_list(collection) do
+7 -2
View File
@@ -67,9 +67,14 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
if exception.extra do
msg <> ". " <> exception.extra
else
msg
end
end
end
+63 -41
View File
@@ -209,24 +209,14 @@ defmodule HashDict do
ordered()
end
def equal?(ordered(bucket: a, size: size), ordered(bucket: b, size: ^size)) do
a == b
end
def equal?(trie(size: size) = a, trie(size: ^size) = b) do
a == b
end
def equal?(ordered() = a, trie() = b) do
equal?(b, a)
end
def equal?(trie(size: size) = a, ordered(bucket: b, size: ^size)) do
:lists.keysort(1, to_list(a)) == :lists.keysort(1, b)
end
def equal?(_, _) do
false
def equal?(dict1, dict2) do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict1)
_ ->
false
end
end
@doc """
@@ -335,11 +325,11 @@ defmodule HashDict do
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
:lists.keyfind(key, 1, bucket)
bucket_get(bucket, key)
end
defp dict_get(trie(root: root, depth: depth), key) do
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
defp dict_fold(ordered(bucket: bucket), acc, fun) do
@@ -402,28 +392,60 @@ defmodule HashDict do
end
end
defp dict_equal?(dict1, dict2) do
try do
reduce(dict1, true, fn({ key, value }, acc) ->
case fetch(dict2, key) do
{ _ok, ^value } ->
acc
_ ->
throw(:error)
end
end)
catch
:error -> false
end
end
## Bucket helpers
# Get value from the bucket
defp bucket_get([{k,_}|_bucket], key) when k > key do
false
end
defp bucket_get([{key,_}=e|_bucket], key) do
e
end
defp bucket_get([_e|bucket], key) do
bucket_get(bucket, key)
end
defp bucket_get([], _key) do
false
end
# Puts a value in the bucket
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
{ [{key,value},e|bucket], 1 }
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
{ [{key, value}|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
{ [{key,initial},e|bucket], 1 }
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [{key, initial}|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
{ [{key,value}|bucket], 0 }
end
defp bucket_put([{_,value}|bucket], key, { :update, _initial, fun }) do
{ [{key,fun.(value)}|bucket], 0 }
defp bucket_put([{key,value}|bucket], key, { :update, _initial, fun }) do
{ [{key, fun.(value)}|bucket], 0 }
end
defp bucket_put([e|bucket], key, value) do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([], key, { :put, value }) do
@@ -436,23 +458,23 @@ defmodule HashDict do
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
defp bucket_put!([{k,_}|_]=bucket, key, value) when k > key, do: [{key,value}|bucket]
defp bucket_put!([{key,_}|bucket], key, value), do: [{key,value}|bucket]
defp bucket_put!([{_,_}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [{key,value}]
# Deletes a key from the bucket
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
defp bucket_delete([{k,_}|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
defp bucket_delete([{key,value}|bucket], key) do
{ bucket, value, -1 }
end
defp bucket_delete([{_,value}|bucket], _key) do
{ bucket, value, -1 }
defp bucket_delete([e|bucket], key) do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
defp bucket_delete([], _key) do
+100 -164
View File
@@ -1362,148 +1362,16 @@ defmodule Kernel do
@doc %B"""
Defines a record.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a module
that generates accessors to manipulate the record at both
compilation and runtime.
This macro defines a module that generates accessors to manipulate the record
at both compilation and runtime.
See the `Record` module's documentation for a detailed description of records
in Elixir.
## Examples
defrecord FileInfo, atime: nil, accesses: 0
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record:
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_accesses(fn(old) -> old + 1 end)
Which can be also written as:
file_info.update_accesses(&1 + 1)
## Access syntax
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
The clause above will expand to:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
end
Is translated to:
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
end
Which provides faster get and set times for record operations.
## Runtime introspection
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
prints out
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
where the last line is a quoted representation of
[bar: nil | integer]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
"""
defmacro defrecord(name, fields, do_block // [])
@@ -1515,18 +1383,18 @@ defmodule Kernel do
end
end
@doc """
@doc %B"""
Defines a record with a set of private macros to manipulate it.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a set of
macros private to the current module to manipulate the record
exclusively at compilation time.
This macro defines a set of macros private to the current module to
manipulate the record exclusively at compilation time.
`defrecordp` must be used instead of `defrecord` when there is
no interest in exposing the record as a whole. In many ways,
it is similar to Erlang records, since it is only available at
compilation time.
`defrecordp` must be used instead of `defrecord` when there is no interest in
exposing the record outside of the module it's defined in. In many ways, it
is similar to an Erlang record, since it is only available at compilation time.
See the `Record` module's documentation for a detailed description of records
in Elixir.
## Examples
@@ -1571,8 +1439,8 @@ defmodule Kernel do
1) Differently from records, exceptions are documented by default;
2) Exceptions **must** implement `message/1` as API that return a
binary as result;
2) Exceptions **must** implement `message/1` -- a function that returns a
string;
"""
defmacro defexception(name, fields, opts // [], do_block // []) do
@@ -1828,9 +1696,33 @@ defmodule Kernel do
end
@doc """
Makes the given functions in the current module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
Makes the given functions in the current module overridable. An overridable
function is lazily defined, allowing a developer to customize it.
## Example
defmodule DefaultMod do
defmacro __using__(_opts) do
quote do
def test(x, y) do
x + y
end
defoverridable [test: 2]
end
end
end
defmodule InheritMod do
use DefaultMod
def test(x, y) do
x * y + super(x, y)
end
end
As seen as in the example `super` can be used to call the default
implementation.
"""
defmacro defoverridable(tuples) do
quote do
@@ -2921,29 +2813,59 @@ defmodule Kernel do
defmacro left in right
@doc """
Matches the term on the left against the regular expression
on the right. It returns nil if not match happened or the
first match otherwise.
Matches the term on the left against the regular expression or string on the
right. Returns true if `left` matches `right` (if it's a regular expression)
or contains `right` (if it's a string).
## Examples
iex> "abcd" =~ %r/c(d)/
2
true
iex> "abcd" =~ %r/e/
nil
false
iex> "abcd" =~ "bc"
true
iex> "abcd" =~ "ad"
false
"""
# fast path for literal binaries
defmacro left =~ right when is_binary(right) do
quote do
String.contains?(unquote(left), unquote(right))
end
end
# fast path for literal binaries
defmacro left =~ ({:<<>>, _, [_bin]} = right) do
quote do
String.contains?(unquote(left), unquote(right))
end
end
# slow path for everything else
defmacro left =~ right do
quote do
Regex.index(unquote(right), unquote(left))
str = unquote(left)
case unquote(right) do
bin when is_binary(bin) ->
String.contains?(str, bin)
re when is_regex(re) ->
Regex.match?(re, str)
other ->
raise ArgumentError, message: "bad argument on the right side of =~: #{inspect other}"
end
end
end
@doc """
`|>` is called the pipeline operator as it is useful
to write pipeline style expressions. This operator
tntroduces the expression on the left as the first
argument to the expression on the right.
introduces the expression on the left as the first
argument to the function call on the right.
## Examples
@@ -2954,8 +2876,8 @@ defmodule Kernel do
Enum.map(List.flatten([1,[2],3]), &1 * 2)
Please be aware of operator precendence, when using
this operator. For example, the following expression:
Be aware of operator precendence when using this operator.
For example, the following expression:
String.graphemes "Hello" |> Enum.reverse
@@ -2965,7 +2887,7 @@ defmodule Kernel do
Which will result in an error as Enumerable protocol
is not defined for binaries. Adding explicit parenthesis
is recommended:
resolves the ambiguity:
String.graphemes("Hello") |> Enum.reverse
@@ -2982,7 +2904,11 @@ defmodule Kernel do
{ call, line, [left] }
end
defp pipeline_op(left, { call, line, args }) when is_list(args) do
defp pipeline_op(left, { call, line, args }=right) when is_list(args) do
case validate_pipeline_args(args) do
:error -> pipeline_error(right)
_ -> nil
end
{ call, line, [left|args] }
end
@@ -2991,7 +2917,17 @@ defmodule Kernel do
end
defp pipeline_op(_, other) do
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{inspect other}"
pipeline_error(other)
end
defp validate_pipeline_args([]), do: nil
defp validate_pipeline_args([ {:&,_,_ } | _ ]), do: :error
defp validate_pipeline_args([_|t]) do
validate_pipeline_args(t)
end
defp pipeline_error(arg) do
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{Macro.to_binary arg}"
end
@doc """
+19 -8
View File
@@ -102,9 +102,9 @@ defmodule Kernel.CLI do
# Process shared options
defp process_shared(["-v"|t], config) do
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
process_shared t, config
System.halt 0
end
defp process_shared(["-pa",h|t], config) do
@@ -137,11 +137,7 @@ defmodule Kernel.CLI do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
defp process_shared(["--cookie",h|t], config) do
process_shared t, config.update_commands [{:cookie,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name"] do
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
@@ -275,7 +271,7 @@ defmodule Kernel.CLI do
end
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = System.find_executable(file) do
if exec = find_elixir_executable(file) do
Code.require_file(exec)
:ok
else
@@ -334,4 +330,19 @@ defmodule Kernel.CLI do
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
# a .bat file that must be in the same directory as
# the actual Elixir executable.
case :os.type() do
{ :win32, _ } ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
_ ->
exec
end
end
end
end
+8 -2
View File
@@ -24,7 +24,7 @@ defmodule Kernel.SpecialForms do
## Examples
:{}.(1,2,3)
iex> { 1, 2, 3 }
{ 1, 2, 3 }
"""
@@ -35,7 +35,7 @@ defmodule Kernel.SpecialForms do
## Examples
:[].(1,2,3)
iex> [ 1, 2, 3 ]
[ 1, 2, 3 ]
"""
@@ -810,4 +810,10 @@ defmodule Kernel.SpecialForms do
"""
defmacro __aliases__(args)
@doc """
Calls the overriden function when overriding it with `defoverridable`.
See `Kernel.defoverridable` for more information and documentation.
"""
defmacro super(args)
end
+2 -2
View File
@@ -165,7 +165,7 @@ defmodule List do
@doc """
Receives a list of tuples and returns true if there is
a tuple where the item at position `posistion` matches
a tuple where the item at position `position` matches
with the given `item`.
## Examples
@@ -215,7 +215,7 @@ defmodule List do
@doc """
Receives a list of tuples and deletes the first tuple
where the item at position `posistion` matches with the
where the item at position `position` matches with the
given `item`. Returns the new tuple.
## Examples
+284 -3
View File
@@ -7,16 +7,296 @@ defmodule Module do
end
end
@moduledoc """
@moduledoc %B'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After the module is compiled, using many of the functions in
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
## Module attributes
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take two arguments: the module environment and its bytecode.
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
**Example**
defmodule M do
@after_compile __MODULE__
def __after_compile__(env, _bytecode) do
IO.inspect env
end
end
* `@before_compile`
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
**Example**
defmodule M do
@before_compile __MODULE__
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
end
end
end
* `@behaviour` (notice the british spelling)
Specify an OTP or user-defined behaviour.
**Example**
defmodule M do
@behaviour gen_event
# ...
end
* `@compile`
Define options for module compilation that are passed to the Erlang
compiler.
Accepts an atom, a tuple, or a list of atoms and tuples.
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
**Example**
defmodule M do
@compile { :inline, myfun: 1 }
def myfun(arg) do
to_binary(arg)
end
end
* `@doc`
Provide documentation for the function or macro that follows the
attribute.
Accepts a string (often a heredoc) or `false` where `@doc false` will
make the function/macro invisible to the documentation extraction tools
like ExDoc.
Can be invoked more than once.
**Example**
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sum.
"""
def sum(a, b) do
a + b
end
end
* `@file`
Change the filename used in stacktraces for the function or macro that
follows the attribute.
Accepts a string. Can be used more than once.
**Example**
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
* `@moduledoc`
Provide documentation for the current module.
Accepts a string (which is often a heredoc) or `false` where
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
**Example**
defmodule M do
@moduledoc """
A very useful module
"""
end
* `@on_definition`
A hook that will be invoked after each function or macro in the current
module is defined. This makes it easy to annotate and customize
functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take 6 arguments:
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time.
**Example**
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_binary(body)
end
end
defmodule M do
@on_definition { H, :on_def }
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_binary(arg)
end
def hello(_) do
:ok
end
end
* `@on_load`
A hook that will be invoked whenever the module is loaded.
Accepts a function atom of a function in the current module. The function
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
**Example**
defmodule M do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
end
end
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
**Example**
defmodule M do
@vsn "1.0"
end
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 behavior 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
`@` and followed by a valid Elixir value:
defmodule M do
@custom_attr [some: "stuff"]
end
For more advanced options available when defining custom attributes, see
`register_attribute/3`.
## Runtime information about a module
It is possible to query a module at runtime to find out which functions and
macros it defines, extract its docstrings, etc. See `__info__/1`.
'''
@doc """
Provides runtime information about functions and macros defined by the
module, enables docstring extraction, etc.
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
* `: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)`)
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
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
more information.
"""
def __info__(kind)
@doc """
Check if a module is open, i.e. it is currently being defined
@@ -362,7 +642,8 @@ defmodule Module do
@doc """
Makes the given functions in the given module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
developer to customize it. See `Kernel.defoverridable` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
+31 -10
View File
@@ -180,6 +180,12 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_definition, kind, tuple })
end
# Adds and tracks defaults for a definition into the tracker.
@doc false
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
end
# Adds a local dispatch to the given target.
def add_local(pid, to) when is_tuple(to) do
:gen_server.cast(pid, { :add_local, :local, to })
@@ -327,18 +333,17 @@ defmodule Module.DispatchTracker do
end
def handle_cast({ :add_local, from, to }, d) do
:digraph.add_vertex(d, to)
replace_edge!(d, from, to)
handle_add_local(d, from, to)
{ :noreply, d }
end
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
record_import_or_remote(d, :remote, function, module, name, arity)
handle_import_or_remote(d, :remote, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
record_import_or_remote(d, :import, function, module, name, arity)
handle_import_or_remote(d, :import, function, module, name, arity)
{ :noreply, d }
end
@@ -355,14 +360,16 @@ defmodule Module.DispatchTracker do
{ :noreply, d }
end
def handle_cast({ :add_definition, public, tuple }, d) when public in [:def, :defmacro] do
:digraph.add_vertex(d, tuple)
replace_edge!(d, :local, tuple)
def handle_cast({ :add_definition, kind, tuple }, d) do
handle_add_definition(d, kind, tuple)
{ :noreply, d }
end
def handle_cast({ :add_definition, private, tuple }, d) when private in [:defp, :defmacrop] do
:digraph.add_vertex(d, tuple)
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
end
{ :noreply, d }
end
@@ -388,7 +395,7 @@ defmodule Module.DispatchTracker do
{ :ok, d }
end
defp record_import_or_remote(d, kind, function, module, name, arity) do
defp handle_import_or_remote(d, kind, function, module, name, arity) do
:digraph.add_vertex(d, module)
replace_edge!(d, kind, module)
@@ -401,6 +408,20 @@ defmodule Module.DispatchTracker do
end
end
defp handle_add_local(d, from, to) do
:digraph.add_vertex(d, to)
replace_edge!(d, from, to)
end
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
:digraph.add_vertex(d, tuple)
replace_edge!(d, :local, tuple)
end
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
:digraph.add_vertex(d, tuple)
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
+5 -5
View File
@@ -385,10 +385,7 @@ defmodule Path do
join(left, atom_to_binary(right))
defp major_os_type do
case :os.type do
{ maj, _ } -> maj
maj -> maj
end
:os.type |> elem(0)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
@@ -426,13 +423,16 @@ defmodule Path do
## Examples
iex> Path.split("")
["/"]
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
"""
# Work around a bug in Erlang on UNIX
def split(""), do: []
def split(path) do
FN.split(path)
end
+150 -1
View File
@@ -1,6 +1,155 @@
defmodule Record do
@moduledoc """
@moduledoc %B"""
Functions to define Elixir records
A record is a tagged tuple which contains one or more elements and the first
element is a module. One creates a record by calling `defrecord` or
`defrecordp` which are documented in `Kernel`.
## Examples
defrecord FileInfo, atime: nil, accesses: 0
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record:
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
# Update multiple attributes at once:
file_info.update(atime: now(), accesses: 1)
# Obtain the keywords representation of a record:
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_accesses(fn(old) -> old + 1 end)
Which can be also written as:
file_info.update_accesses(&1 + 1)
## Access syntax
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
The clause above will expand to:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
end
Is translated to:
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
end
Which provides faster get and set times for record operations.
## Runtime introspection
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
prints out
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
where the last line is a quoted representation of
[bar: nil | integer]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
"""
@doc """
+26 -13
View File
@@ -79,20 +79,12 @@ defmodule Regex do
end
end
@doc """
Runs the regular expression against the given string
and returns the index (zero indexes) where the first
match occurs, nil otherwise.
## Examples
iex> Regex.index(%r/c(d)/, "abcd")
2
iex> Regex.index(%r/e/, "abcd")
nil
"""
@doc false
def index(regex(re_pattern: compiled), string) do
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
"Regex.run(re, string, return: :index) instead."
Exception.print_stacktrace
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
@@ -125,6 +117,8 @@ defmodule Regex do
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
@@ -291,6 +285,25 @@ defmodule Regex do
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %B"""
Escapes a string to be literally matched in a regex.
## Examples
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
end
# Helpers
@doc false
+72 -12
View File
@@ -811,12 +811,24 @@ defmodule String do
"""
@spec starts_with?(t, t | [t]) :: boolean
def starts_with?(string, prefixes) do
case :binary.match(string, prefixes) do
:nomatch -> false
{0, _} -> true
_ -> false
end
def starts_with?(string, prefixes) when is_list(prefixes) do
Enum.any?(prefixes, do_starts_with(string, &1))
end
def starts_with?(string, prefix) do
do_starts_with(string, prefix)
end
defp do_starts_with(_, "") do
true
end
defp do_starts_with(string, prefix) when is_binary(prefix) do
match?({0,_}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
raise ArgumentError
end
@doc """
@@ -836,13 +848,61 @@ defmodule String do
@spec ends_with?(t, t | [t]) :: boolean
def ends_with?(string, suffixes) when is_list(suffixes) do
string_size = size(string)
Enum.any? suffixes, fn suffix ->
suffix_size = size(suffix)
(suffix_size <= string_size) and suffix == :binary.part(string, {string_size, -suffix_size})
end
Enum.any?(suffixes, do_ends_with(string, &1))
end
def ends_with?(string, suffix), do: ends_with?(string, [ suffix ])
def ends_with?(string, suffix) do
do_ends_with(string, suffix)
end
defp do_ends_with(_, "") do
true
end
defp do_ends_with(string, suffix) when is_binary(suffix) do
string_size = size(string)
suffix_size = size(suffix)
scope = {string_size - suffix_size, suffix_size}
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
end
defp do_ends_with(_, _) do
raise ArgumentError
end
@doc """
Returns true if `string` contains match, otherwise false.
`matches` can be either a single string or a list of strings.
## Examples
iex> String.contains? "elixir of life", "of"
true
iex> String.contains? "elixir of life", ["life", "death"]
true
iex> String.contains? "elixir of life", ["death", "mercury"]
false
"""
@spec contains?(t, t | [t]) :: boolean
def contains?(string, matches) when is_list(matches) do
Enum.any?(matches, do_contains(string, &1))
end
def contains?(string, match) do
do_contains(string, match)
end
defp do_contains(_, "") do
true
end
defp do_contains(string, match) when is_binary(match) do
:nomatch != :binary.match(string, match)
end
defp do_contains(_, _) do
raise ArgumentError
end
end
+50 -21
View File
@@ -14,20 +14,40 @@ defmodule System do
with the VM or the host system.
"""
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
defp strip_re(iodata, pattern) do
:re.replace(iodata, pattern, "", [return: :binary])
end
# Tries to run `git describe --always --tags`. In case of success
# returns the most recent tag, otherwise returns an empty string.
defp read_stripped(path) do
case :file.read_file(path) do
{ :ok, binary } ->
strip_re(binary, "^\s+|\s+$")
_ -> ""
end
end
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped("VERSION") do
"" -> raise CompileError, message: "could not read the version number from VERSION"
data -> data
end
end
# Tries to run `git describe --always --tags`. In the case of success returns
# the most recent tag. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_describe do
dotgit = Path.join(File.cwd!, ".git")
if :os.find_executable('git') && File.exists?(dotgit) do
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
""
dirpath = ".git"
case :file.read_file_info(dirpath) do
{ :ok, _ } ->
if :os.find_executable('git') do
data = :os.cmd('git describe --always --tags')
strip_re(data, "\n")
else
read_stripped(:filename.join(".git", "HEAD"))
end
_ -> ""
end
end
@@ -133,15 +153,22 @@ defmodule System do
end
defp write_env_tmp_dir(env) do
case System.get_env(env) do
case get_env(env) do
nil -> nil
tmp -> write_tmp_dir tmp
end
end
defp write_tmp_dir(dir) do
case File.stat(dir) do
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__index__ :type
access_index = File.Stat.__index__ :access
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
dir
_ -> nil
end
{ :error, _ } -> nil
end
end
@@ -177,21 +204,23 @@ defmodule System do
end
@doc """
This functions looks up an executable program given
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows.
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 `command` is a char list, a char list is returned.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(command) do
case :os.find_executable(to_char_list(command)) do
def find_executable(program) do
case :os.find_executable(to_char_list(program)) do
false -> nil
other -> list_to_binary(other)
end
+29 -4
View File
@@ -150,7 +150,8 @@ defmodule URI do
scheme_specific(scheme, info)
end
defp scheme_specific(scheme, info) do
@doc false
def scheme_module(scheme) do
if scheme do
module =
try do
@@ -160,10 +161,14 @@ defmodule URI do
end
if module && Code.ensure_loaded?(module) do
module.parse(default_port(info, module))
else
info
module
end
end
end
defp scheme_specific(scheme, info) do
if module = scheme_module(scheme) do
module.parse(default_port(info, module))
else
info
end
@@ -202,3 +207,23 @@ defmodule URI do
:ok
end
end
defimpl Binary.Chars, for: URI.Info do
def to_binary(uri) do
result = ""
if module = URI.scheme_module(uri.scheme) do
if module.default_port == uri.port, do: uri = uri.port(nil)
end
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_binary(uri.port)
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
result
end
end
+26 -18
View File
@@ -57,32 +57,36 @@ defmodule String.Unicode do
# Downcase
def downcase(""), do: ""
def downcase(string), do: do_downcase(string) |> list_to_binary
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
def downcase(unquote(codepoint) <> rest) do
unquote(lower) <> downcase(rest)
defp do_downcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(lower)) ++ downcase(rest)
end
end
def downcase(<< char, rest :: binary >>) do
<< char >> <> downcase(rest)
defp do_downcase(<< char, rest :: binary >>) do
[char|do_downcase(rest)]
end
defp do_downcase(""), do: []
# Upcase
def upcase(""), do: ""
def upcase(string), do: do_upcase(string) |> list_to_binary
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
def upcase(unquote(codepoint) <> rest) do
unquote(upper) <> upcase(rest)
defp do_upcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(upper)) ++ do_upcase(rest)
end
end
def upcase(<< char, rest :: binary >>) do
<< char >> <> upcase(rest)
defp do_upcase(<< char, rest :: binary >>) do
[char|do_upcase(rest)]
end
defp do_upcase(""), do: []
# Titlecase once
def titlecase_once(""), do: { "", "" }
@@ -109,23 +113,27 @@ defmodule String.Unicode do
def lstrip(other) when is_binary(other), do: other
def rstrip(""), do: ""
def rstrip(string) when is_binary(string) do
do_rstrip(string, "")
do_rstrip(string, [], [])
end
lc codepoint inlist whitespace do
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
do_rstrip(rest, unquote(codepoint) <> buffer)
c = binary_to_list(codepoint) |> :lists.reverse
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
end
end
defp do_rstrip(<< char, rest :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
do_rstrip(rest, nil, [char|acc2])
end
defp do_rstrip(<<>>, _), do: <<>>
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
do_rstrip(rest, nil, [char|acc1])
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
# Split
+1 -1
View File
@@ -137,7 +137,7 @@ raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat(Args) -> do_concat(Args).
do_concat(Args) ->
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
Aliases = [to_partial(Arg) || Arg <- Args],
"Elixir" ++ lists:concat(Aliases).
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
+3 -2
View File
@@ -1,5 +1,5 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4, format_error/1]).
-include("elixir.hrl").
@@ -222,6 +222,7 @@ core_main() ->
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/path.ex",
"lib/elixir/lib/system.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex",
@@ -246,4 +247,4 @@ format_errors(File, Errors) ->
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
end, Warnings).
+4 -1
View File
@@ -124,13 +124,16 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
DefaultsLength = length(Defaults),
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = TS#elixir_scope.file,
Table = table(Module),
CTable = clauses_table(Module),
compile_super(Module, TS),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, length(Defaults), Function),
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
+4 -4
View File
@@ -147,12 +147,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
%% Try
translate({'try', Meta, [Clauses]}, RawS) ->
S = RawS#elixir_scope{noname=true},
translate({'try', Meta, [Clauses]}, S) ->
assert_no_match_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate_each(Do, S),
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
@@ -163,7 +162,8 @@ translate({'try', Meta, [Clauses]}, RawS) ->
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
%% Receive
+9
View File
@@ -6,6 +6,7 @@
record_local/2, record_local/3,
record_import/4, record_remote/4,
record_warn/4, record_definition/3,
record_defaults/4,
ensure_no_import_conflict/4, ensure_all_imports_used/3,
warn_unused_local/3, format_error/1
]).
@@ -70,6 +71,14 @@ record_definition(Tuple, Kind, Module) ->
true
end).
record_defaults(_Tuple, _Kind, _Module, 0) ->
true;
record_defaults(Tuple, Kind, Module, Defaults) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
true
end).
%% HELPERS
if_tracker(Module, Callback) ->
+5 -2
View File
@@ -331,9 +331,12 @@ translate_each({ super, Meta, Args } = Original, S) ->
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) ->
is_atom(Args) andalso Arity == 0 ->
elixir_def_overridable:retrieve_args(Meta, Arity, S);
length(Args) == Arity ->
is_atom(Args) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
elixir_def_overridable:retrieve_args(Meta, Arity, S);
not is_atom(Args) andalso length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
@@ -16,7 +16,6 @@ defmodule KernelTest do
doctest Code
doctest Dict
doctest Enum
doctest ExUnit.CaptureIO, import: true
doctest IO.ANSI
doctest Regex
doctest String
@@ -71,3 +71,89 @@ defmodule Binary.Chars.ListTest do
assert to_binary([]) == ""
end
end
defmodule Binary.Chars.ErrorsTest do
use ExUnit.Case, async: true
test :list do
assert to_binary([0,1, 2, 3,255]) == <<0,1,2,3,255>>
assert to_binary([0,[1, "hello"], 2, [["bye"]]]) == <<0,1,104,101,108,108,111,2,98,121,101>>
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
to_binary([256])
end
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
to_binary([1001, 10001, 100001])
end
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
to_binary([:atom, 13, "hello"])
end
end
test :tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
to_binary({1,2,3})
end
end
test :nested_in_tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
to_binary({1, [2], :atom})
end
end
test :nested_tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
to_binary([1, {[2], :atom}])
end
end
test :nested_pid do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
to_binary([self(), :atom])
end
end
test :nested_fun do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
to_binary([:atom, fn -> end])
end
end
test :record do
# FIXME: is this expected error message?
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.ArgumentError.to_binary/1", fn ->
to_binary(ArgumentError[])
end
# FIXME: is this expected error message?
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.File.Stat.to_binary/1", fn ->
to_binary(File.Stat[])
end
end
test :pid do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #PID<.+?>$", fn ->
to_binary(self())
end
end
test :ref do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Reference<.+?>$", fn ->
to_binary(make_ref()) == ""
end
end
test :function do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Function<.+?>$", fn ->
to_binary(fn -> end)
end
end
test :port do
[port|_] = Port.list
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Port<.+?>$", fn ->
to_binary(port)
end
end
end
+19
View File
@@ -19,6 +19,11 @@ defmodule CodeTest do
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
end
test :eval_with_unnamed_scopes do
assert { RuntimeError[], [a: RuntimeError[]] } =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
end
test :eval_with_scope do
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
end
@@ -91,6 +96,20 @@ defmodule CodeTest do
assert Keyword.get(compile, :source) != nil
end
test :compile_string do
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
after
:code.purge CompileSimpleSample
:code.delete CompileSimpleSample
end
test :compile_quoted do
assert [{ CompileQuotedSample, _ }] = Code.compile_string("defmodule CompileQuotedSample, do: :ok")
after
:code.purge CompileQuotedSample
:code.delete CompileQuotedSample
end
test :ensure_loaded? do
assert Code.ensure_loaded?(__MODULE__)
refute Code.ensure_loaded?(Unknown.Module)
+2 -2
View File
@@ -34,8 +34,8 @@ defmodule Kernel.ExceptionTest do
end
test :format_stacktrace_entry_with_fun do
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
end
test :format_module_function_arity do
@@ -16,8 +16,8 @@ defmodule GenServer.BehaviourTest do
{ :stop, :normal, :ok, config }
end
def handle_call(_request, _from, _config) do
super
def handle_call(request, from, config) do
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
+17 -1
View File
@@ -62,6 +62,11 @@ defmodule HashDictTest do
dict = HashDict.put_new(dict, 11, 13)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
dict = HashDict.put_new(dict, 11.0, 15)
assert HashDict.get(dict, 11.0) == 15
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test :update do
@@ -82,6 +87,17 @@ defmodule HashDictTest do
dict = HashDict.update(dict, 11, 13, &1 * 2)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
assert_raise KeyError, fn->
HashDict.update(dict, 11.0, &1 * 2)
end
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
assert HashDict.get(dict, 11.0) == 15
assert HashDict.size(dict) == 10
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
assert HashDict.get(dict, 11.0) == 30
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test :to_list do
@@ -149,7 +165,7 @@ defmodule HashDictTest do
end
test :inspect do
assert inspect(filled_dict(8)) =~ %r"#HashDict<"
assert inspect(filled_dict(8)) =~ "#HashDict<"
end
test :small_range_merge do
+116 -114
View File
@@ -2,6 +2,7 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule Kernel.ErrorsTest do
use ExUnit.Case, async: true
import CompileAssertion
defmodule UnproperMacro do
defmacro unproper(args), do: args
@@ -11,263 +12,275 @@ defmodule Kernel.ErrorsTest do
defrecord Config, integer: 0
test :invalid_token do
assert "nofile:1: invalid token: \end" == format_rescue '\end\nlol\nbarbecue'
assert_compile_fail SyntaxError, "nofile:1: invalid token: \end", '\end\nlol\nbarbecue'
end
test :sigil_terminator do
assert "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)" == format_rescue '%r"foo\n\n'
assert "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)" == format_rescue '%r{foo\n\n'
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)", '%r"foo\n\n'
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)", '%r{foo\n\n'
end
test :dot_terminator do
assert "nofile:1: missing terminator: \" (for function name starting at line 1)" == format_rescue 'foo."bar'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for function name starting at line 1)", 'foo."bar'
end
test :string_terminator do
assert "nofile:1: missing terminator: \" (for string starting at line 1)" == format_rescue '"bar'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for string starting at line 1)", '"bar'
end
test :heredoc_terminator do
assert "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)" == format_rescue '"""\nbar'
assert_compile_fail TokenMissingError, "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)", '"""\nbar'
end
test :invalid_partial do
assert "nofile:1: partial variable &2 cannot be defined without &1" == format_rescue '&2 + 3'
assert_compile_fail SyntaxError, "nofile:1: partial variable &2 cannot be defined without &1", '&2 + 3'
end
test :unexpected_end do
assert "nofile:1: unexpected token: end" == format_rescue '1 end'
assert_compile_fail SyntaxError, "nofile:1: unexpected token: end", '1 end'
end
test :syntax_error do
assert "nofile:1: syntax error before: '.'" == format_rescue '+.foo'
assert_compile_fail SyntaxError, "nofile:1: syntax error before: '.'", '+.foo'
end
test :syntax_error_on_parens_call do
assert "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
assert_compile_fail SyntaxError, "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
"do not insert spaces in between the function name and the opening parentheses. " <>
"Syntax error before: )" == format_rescue 'foo (hello, world)'
"Syntax error before: )", 'foo (hello, world)'
end
test :syntax_error_with_no_token do
assert "nofile:1: missing terminator: ) (for \"(\" starting at line 1)" == format_rescue 'case 1 ('
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: ) (for \"(\" starting at line 1)", 'case 1 ('
end
test :bad_form do
assert "nofile:2: function bar/0 undefined" == format_rescue 'defmodule Foo do\ndef foo, do: bar\nend'
assert_compile_fail CompileError, "nofile:2: function bar/0 undefined", 'defmodule Foo do\ndef foo, do: bar\nend'
end
test :unbound_var do
assert "nofile:1: unbound variable ^x" == format_rescue('^x = 1')
assert_compile_fail SyntaxError, "nofile:1: unbound variable ^x", '^x = 1'
end
test :unbound_not_match do
assert "nofile:1: cannot use ^x outside of match clauses" == format_rescue('^x')
assert_compile_fail SyntaxError, "nofile:1: cannot use ^x outside of match clauses", '^x'
end
test :unbound_expr do
assert "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)" == format_rescue('^x(1)')
assert_compile_fail SyntaxError, "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)", '^x(1)'
end
test :name_for_defmodule do
assert "nofile:1: invalid module name: 3" == format_rescue 'defmodule 1 + 2, do: 3'
assert_compile_fail CompileError, "nofile:1: invalid module name: 3", 'defmodule 1 + 2, do: 3'
end
test :invalid_scope_for_function do
assert "nofile:1: cannot invoke def outside module" == format_rescue 'def Foo, do: 2'
assert "nofile:3: cannot invoke defmacro outside module" == format_rescue '\n\ndefmacro Foo, do: 2'
assert_compile_fail SyntaxError, "nofile:1: cannot invoke def outside module", 'def Foo, do: 2'
assert_compile_fail SyntaxError, "nofile:3: cannot invoke defmacro outside module", '\n\ndefmacro Foo, do: 2'
end
test :invalid_unquote do
assert "nofile:1: unquote called outside quote" == format_rescue 'unquote 1'
assert_compile_fail SyntaxError, "nofile:1: unquote called outside quote", 'unquote 1'
end
test :invalid_quote_args do
assert "nofile:1: invalid args for quote" == format_rescue 'quote 1'
assert_compile_fail SyntaxError, "nofile:1: invalid args for quote", 'quote 1'
end
test :invalid_parens do
assert "nofile:1: unexpected parenthesis after foo(1)" == format_rescue 'foo(1)(2)'
assert_compile_fail SyntaxError, "nofile:1: unexpected parenthesis after foo(1)", 'foo(1)(2)'
end
test :invalid_fn_args do
assert "nofile:1: missing terminator: end (for \"fn\" starting at line 1)" == format_rescue 'fn 1'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"fn\" starting at line 1)", 'fn 1'
end
test :macro_conflict do
assert "nofile:1: imported Kernel.defrecord/2 conflicts with local function" ==
format_rescue 'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
assert_compile_fail CompileError, "nofile:1: imported Kernel.defrecord/2 conflicts with local function",
'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
end
test :macro_with_undefined_local do
assert "undefined function: Foo.unknown/1" ==
format_rescue 'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.unknown/1",
'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
end
test :private_macro do
assert "undefined function: Foo.foo/0" ==
format_rescue 'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.foo/0",
'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
end
test :function_definition_with_alias do
assert "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar" ==
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
assert_compile_fail SyntaxError, "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar",
'defmodule Foo do def Bar do :baz end\nend\n'
end
test :function_import_conflict do
assert "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous" ==
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
assert_compile_fail CompileError, "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
end
test :import_invalid_macro do
assert "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist" ==
format_rescue 'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
assert_compile_fail CompileError, "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist",
'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
end
test :unrequired_macro do
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record" ==
format_rescue 'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
assert_compile_fail CompileError, "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record",
'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
end
test :def_defmacro_clause_change do
assert "nofile:3: defmacro foo/1 already defined as def" ==
format_rescue 'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
assert_compile_fail CompileError, "nofile:3: defmacro foo/1 already defined as def",
'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
end
test :internal_function_overridden do
assert "nofile:1: function __info__/1 is internal and should not be overridden" ==
format_rescue 'defmodule Foo do\ndef __info__(_), do: []\nend'
assert_compile_fail CompileError, "nofile:1: function __info__/1 is internal and should not be overridden",
'defmodule Foo do\ndef __info__(_), do: []\nend'
end
test :no_macros do
assert "nofile:2: could not load macros from module :lists" ==
format_rescue 'defmodule Foo do\nimport :macros, :lists\nend'
assert_compile_fail CompileError, "nofile:2: could not load macros from module :lists",
'defmodule Foo do\nimport :macros, :lists\nend'
end
test :unloaded_module do
assert "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found" ==
format_rescue 'import Certainly.Doesnt.Exist'
assert_compile_fail CompileError, "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found",
'import Certainly.Doesnt.Exist'
end
test :scheduled_module do
assert "nofile:1: module Foo.Hygiene is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it." ==
format_rescue 'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
assert_compile_fail CompileError, "nofile:1: module Foo.Hygiene is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
end
test :already_compiled_module do
assert "could not call eval_quoted on module Record because it was already compiled" ==
format_rescue 'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
assert_compile_fail ArgumentError, "could not call eval_quoted on module Record because it was already compiled",
'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
end
test :interpolation_error do
assert "nofile:1: missing terminator: end (for \"do\" starting at line 1)" == format_rescue '"foo\#{case 1 do )}bar"'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"do\" starting at line 1)", '"foo\#{case 1 do )}bar"'
end
test :cant_define_local_due_to_in_erlang_macros_conflict do
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms" ==
format_rescue 'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
assert_compile_fail CompileError, "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms",
'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
end
test :in_definition_module do
assert "nofile:1: cannot define module Foo because it is currently being defined" ==
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
assert_compile_fail CompileError, "nofile:1: cannot define module Foo because it is currently being defined",
'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
end
test :invalid_definition do
assert "nofile:1: invalid syntax in def 1.(hello)" ==
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
assert_compile_fail SyntaxError, "nofile:1: invalid syntax in def 1.(hello)",
'defmodule Foo, do: (def 1.(hello), do: true)'
end
test :duplicated_bitstring_size do
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
assert_compile_fail SyntaxError, "nofile:1: duplicated size definition for bitstring", '<<1 :: [size(12), size(13)]>>'
end
test :invalid_bitstring_specified do
assert "nofile:1: unknown bitstring specifier :atom" == format_rescue '<<1 :: :atom>>'
assert "nofile:1: unknown bitstring specifier unknown" == format_rescue '<<1 :: unknown>>'
assert "nofile:1: unknown bitstring specifier another(12)" == format_rescue '<<1 :: another(12)>>'
assert "nofile:1: size in bitstring expects an integer or a variable as argument" == format_rescue '<<1 :: size(:a)>>'
assert "nofile:1: unit in bitstring expects an integer as argument" == format_rescue '<<1 :: unit(x)>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier :atom", '<<1 :: :atom>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier unknown", '<<1 :: unknown>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier another(12)", '<<1 :: another(12)>>'
assert_compile_fail SyntaxError, "nofile:1: size in bitstring expects an integer or a variable as argument", '<<1 :: size(:a)>>'
assert_compile_fail SyntaxError, "nofile:1: unit in bitstring expects an integer as argument", '<<1 :: unit(x)>>'
end
test :invalid_var! do
assert "nofile:1: expected var!(y) to expand to an existing variable or be a part of a match",
format_rescue 'var!(x)'
assert_compile_fail SyntaxError, "nofile:1: expected var!(x) to expand to an existing variable or be a part of a match", 'var!(x)'
end
test :invalid_alias do
assert "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists" ==
format_rescue 'alias :lists, as: Sample.Lists'
assert_compile_fail SyntaxError, "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists",
'alias :lists, as: Sample.Lists'
end
test :invalid_import_option do
assert "nofile:1: unsupported option ops given to import" ==
format_rescue 'import :lists, [ops: 1]'
assert_compile_fail SyntaxError, "nofile:1: unsupported option ops given to import",
'import :lists, [ops: 1]'
end
test :invalid_access_protocol_not_alias do
assert "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)" ==
format_rescue 'defmodule Foo do\ndef sample(config[integer: 0]), do: true\nend'
assert_raise RuntimeError, "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)", fn ->
defmodule Foo do
def sample(config[integer: 0]), do: true
end
end
end
test :invalid_access_protocol_not_available do
assert "nofile:2: module Unknown is not loaded and could not be found" ==
format_rescue 'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
assert_compile_fail CompileError, "nofile:2: module Unknown is not loaded and could not be found",
'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
end
test :invalid_access_protocol_not_record do
assert "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest[integer: 0]), do: true\nend'
assert_raise RuntimeError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest[integer: 0]), do: true
end
end
end
test :invalid_access_protocol_not_keywords do
assert "expected contents inside brackets to be a Keyword" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[0]), do: true\nend'
assert_raise RuntimeError, "expected contents inside brackets to be a Keyword", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest.Config[0]), do: true
end
end
end
test :invalid_access_protocol_invalid_keywords do
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true\nend'
assert_raise RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true
end
end
end
test :invalid_access_protocol_invalid_keywords_outside_match do
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
# FIXME: why RuntimeError?
assert_compile_fail RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]",
'Kernel.ErrorsTest.Config[foo: :bar]'
end
test :invalid_rescue_clause do
assert "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format" ==
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
assert_compile_fail SyntaxError, "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format",
'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
end
test :invalid_bc_return do
assert "nofile:1: a bit comprehension expects a bit string << >> to be returned" ==
format_rescue 'bc x inlist [1,2,3], do: x'
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be returned",
'bc x inlist [1,2,3], do: x'
end
test :invalid_bc_inbits_gen do
assert "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators" ==
format_rescue 'bc x inbits "123", do: <<x>>'
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators",
'bc x inbits "123", do: <<x>>'
end
test :unbound_cond do
assert "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->" ==
format_rescue 'cond do _ -> true end'
assert_compile_fail ArgumentError, "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->",
'cond do _ -> true end'
end
test :fun_different_arities do
assert "nofile:1: cannot mix clauses with different arities in function definition" ==
format_rescue 'fn x -> x; x, y -> x + y end'
assert_compile_fail SyntaxError, "nofile:1: cannot mix clauses with different arities in function definition",
'fn x -> x; x, y -> x + y end'
end
test :new_line_error do
assert "nofile:3: syntax error before: newline" ==
format_rescue 'if true do\n foo = [],\n baz\nend'
assert_compile_fail SyntaxError, "nofile:3: syntax error before: newline",
'if true do\n foo = [],\n baz\nend'
end
test :invalid_var_or_function_on_guard do
assert "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard" =
format_rescue('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard",
('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
end
test :bodyless_function do
@@ -276,23 +289,23 @@ defmodule Kernel.ErrorsTest do
end
test :bodyless_function_with_guard do
assert "nofile:2: missing keyword do in def" =
format_rescue("defmodule Foo do\ndef foo(n) when is_number(n)\nend")
assert_compile_fail SyntaxError, "nofile:2: missing keyword do in def",
("defmodule Foo do\ndef foo(n) when is_number(n)\nend")
end
test :invalid_function_on_match do
assert "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match" =
format_rescue('case [] do; something_that_does_not_exist() -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
('case [] do; something_that_does_not_exist() -> :ok; end')
end
test :invalid_remote_on_match do
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match" =
format_rescue('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match",
('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
end
test :invalid_remote_on_guard do
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard" =
format_rescue('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard",
('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
end
test :macros_error_stacktrace do
@@ -323,17 +336,6 @@ defmodule Kernel.ErrorsTest do
## Helpers
defp format_rescue(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
nil
rescue
error -> error.message
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
end
defp rescue_stacktrace(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
@@ -342,6 +344,6 @@ defmodule Kernel.ErrorsTest do
_ -> System.stacktrace
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
result || raise(ExUnit.AssertionError, message: "Expected function given to rescue_stacktrace to fail")
end
end
@@ -47,10 +47,6 @@ defmodule Kernel.Overridable do
x + y
end
def super_with_implicit_args(x, y) do
x + y
end
def many_clauses(0) do
11
end
@@ -59,7 +55,7 @@ defmodule Kernel.Overridable do
13
end
defoverridable [implicit_nested_super: 0, super_with_implicit_args: 2,
defoverridable [implicit_nested_super: 0,
super_with_explicit_args: 2, many_clauses: 1]
def without_super do
@@ -82,14 +78,6 @@ defmodule Kernel.Overridable do
super x, y * 2
end
def super_with_implicit_args(x, y) do
if true do
x + y + super
else
raise :NEVER
end
end
def many_clauses(2) do
17
end
@@ -98,8 +86,8 @@ defmodule Kernel.Overridable do
super(0) + super(1)
end
def many_clauses(_) do
super
def many_clauses(x) do
super(x)
end
end
@@ -131,10 +119,6 @@ defmodule Kernel.OverridableTest do
assert Overridable.super_with_explicit_args(1, 2) == 5
end
test "calling super with implicit args" do
assert Overridable.super_with_implicit_args(1, 2) == 6
end
test "function without overridable returns false for super?" do
assert Overridable.no_overridable == { :no_overridable, false }
end
@@ -160,4 +144,4 @@ defmodule Kernel.OverridableTest do
"Overridable functions available are: bar/0")
end
end
end
end
+14 -14
View File
@@ -12,7 +12,7 @@ defmodule Kernel.WarningTest do
def hello(arg), do: nil
end
"""
end) =~ %r"variable arg is unused"
end) =~ "variable arg is unused"
after
purge Sample
end
@@ -24,7 +24,7 @@ defmodule Kernel.WarningTest do
defp hello, do: nil
end
"""
end) =~ %r"function hello/0 is unused"
end) =~ "function hello/0 is unused"
assert capture_io(fn ->
Code.eval_string """
@@ -33,7 +33,7 @@ defmodule Kernel.WarningTest do
defp hello(1), do: :ok
end
"""
end) =~ %r"function hello/1 is unused"
end) =~ "function hello/1 is unused"
assert capture_io(fn ->
Code.eval_string """
@@ -44,7 +44,7 @@ defmodule Kernel.WarningTest do
defp d(x), do: x
end
"""
end) =~ %r"function c/2 is unused"
end) =~ "function c/2 is unused"
after
purge [Sample1, Sample2, Sample3]
@@ -58,7 +58,7 @@ defmodule Kernel.WarningTest do
defp b, do: a
end
"""
end) =~ %r"function a/0 is unused"
end) =~ "function a/0 is unused"
after
purge Sample
end
@@ -70,7 +70,7 @@ defmodule Kernel.WarningTest do
defmacrop hello, do: nil
end
"""
end) =~ %r"macro hello/0 is unused"
end) =~ "macro hello/0 is unused"
after
purge Sample
end
@@ -83,7 +83,7 @@ defmodule Kernel.WarningTest do
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ %r"default arguments in b/3 are never used"
end) =~ "default arguments in b/3 are never used"
assert capture_io(fn ->
Code.eval_string """
@@ -92,7 +92,7 @@ defmodule Kernel.WarningTest do
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ %r"the first 2 default arguments in b/3 are never used"
end) =~ "the first 2 default arguments in b/3 are never used"
assert capture_io(fn ->
Code.eval_string """
@@ -101,7 +101,7 @@ defmodule Kernel.WarningTest do
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ %r"the first default argument in b/3 is never used"
end) =~ "the first default argument in b/3 is never used"
assert capture_io(fn ->
Code.eval_string """
@@ -127,7 +127,7 @@ defmodule Kernel.WarningTest do
def a, do: nil
end
"""
end) =~ %r"unused import Sample1"
end) =~ "unused import Sample1"
after
purge [Sample1, Sample2]
end
@@ -140,7 +140,7 @@ defmodule Kernel.WarningTest do
def hello, do: nil
end
"""
end) =~ %r"this clause cannot match because a previous clause at line 2 always matches"
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge Sample
end
@@ -153,7 +153,7 @@ defmodule Kernel.WarningTest do
def hello(arg // 0), do: nil
end
"""
end) =~ %r"clause with defaults should be the first clause in def hello/1"
end) =~ "clause with defaults should be the first clause in def hello/1"
after
purge Sample
end
@@ -166,7 +166,7 @@ defmodule Kernel.WarningTest do
def hello(arg // 1), do: nil
end
"""
end) =~ %r"def hello/1 has default values and multiple clauses, use a separate clause for declaring defaults"
end) =~ "def hello/1 has default values and multiple clauses, use a separate clause for declaring defaults"
after
purge Sample
end
@@ -181,7 +181,7 @@ defmodule Kernel.WarningTest do
def hello, do: :ok
end
"""
end) =~ %r"function world/0 is unused"
end) =~ "function world/0 is unused"
after
purge Sample
end
+30 -2
View File
@@ -4,8 +4,24 @@ defmodule KernelTest do
use ExUnit.Case, async: true
test :match do
assert "abcd" =~ %r/c(d)/
refute "abcd" =~ %r/e/
assert ("abcd" =~ %r/c(d)/) == true
assert ("abcd" =~ %r/e/) == false
string = "^ab+cd*$"
assert (string =~ "ab+") == true
assert (string =~ "bb") == false
assert_raise ArgumentError, "bad argument on the right side of =~: [\"^a\",\"*$\"]", fn ->
string =~ ["^a", "*$"]
end
assert_raise ArgumentError, "argument error", fn ->
1234 =~ "hello"
end
assert_raise ArgumentError, "argument error", fn ->
1234 =~ %r"hello"
end
end
test :nil? do
@@ -227,6 +243,18 @@ defmodule KernelTest do
assert __MODULE__ |> :constant == 13
end
test "non-call" do
assert 1 |> (&1*2).() == 2
assert [1] |> hd(&1).() == 1
import CompileAssertion
# FIXME: this mustn't work, but it doesn't call pipeline_op at all
#assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1", "1 |> &1"
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1 * 2", "1 |> &1*2"
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: hd(&1)", "[1] |> hd(&1)"
end
def constant, do: 13
defp twice(a), do: a * 2
+1 -1
View File
@@ -25,7 +25,7 @@ defmodule KeywordTest do
list = [{:b,2},{:a,1},{:c,3}]
dict = HashDict.new list
assert Keyword.from_enum(list) == [b: 2, a: 1, c: 3]
assert Keyword.from_enum(dict) == [a: 1, b: 2, c: 3]
assert Keyword.equal?(Keyword.from_enum(dict), [a: 1, b: 2, c: 3])
end
test :keyword? do
@@ -103,6 +103,36 @@ defmodule Module.DispatchTrackerTest do
assert unused == []
end
## Defaults
test "can add defaults", config do
D.add_definition(config[:pid], :def, { :foo, 4 })
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
end
test "defaults are reachable if public", config do
D.add_definition(config[:pid], :def, { :foo, 4 })
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
assert { :foo, 2 } in D.reachable(config[:pid])
assert { :foo, 3 } in D.reachable(config[:pid])
end
test "defaults are not reachable if private", config do
D.add_definition(config[:pid], :defp, { :foo, 4 })
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
refute { :foo, 2 } in D.reachable(config[:pid])
refute { :foo, 3 } in D.reachable(config[:pid])
end
test "defaults are connected", config do
D.add_definition(config[:pid], :defp, { :foo, 4 })
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
D.add_local(config[:pid], { :foo, 2 })
assert { :foo, 2 } in D.reachable(config[:pid])
assert { :foo, 3 } in D.reachable(config[:pid])
assert { :foo, 4 } in D.reachable(config[:pid])
end
## Imports
test "can add import", config do
+1
View File
@@ -161,6 +161,7 @@ defmodule ModuleTest do
assert Module.concat(Foo, 'Bar') == Foo.Bar
assert Module.concat(Foo, Bar.Baz) == Foo.Bar.Baz
assert Module.concat(Foo, "Bar.Baz") == Foo.Bar.Baz
assert Module.concat(Bar, :nil) == :"Elixir.Bar.nil"
end
test :safe_concat do
+5 -5
View File
@@ -64,15 +64,15 @@ defmodule PathTest do
assert Path.type("C:\\usr\\local\\bin") == :absolute
assert Path.type("C:usr\\local\\bin") == :volumerelative
assert Path.type("/usr/local/bin") == :absolute
assert Path.type("/usr/local/bin") == :volumerelative
assert Path.type("usr/local/bin") == :relative
assert Path.type("../usr/local/bin") == :relative
assert Path.type('/usr/local/bin') == :absolute
assert Path.type('/usr/local/bin') == :volumerelative
assert Path.type('usr/local/bin') == :relative
assert Path.type('../usr/local/bin') == :relative
assert Path.type(['/usr/', 'local/bin']) == :absolute
assert Path.type(['/usr/', 'local/bin']) == :volumerelative
assert Path.type(['usr/', 'local/bin']) == :relative
assert Path.type(['../usr', '/local/bin']) == :relative
end
@@ -238,13 +238,13 @@ defmodule PathTest do
end
test :split_with_binary do
assert Path.split("") == ["/"]
assert Path.split("") == []
assert Path.split("foo") == ["foo"]
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
end
test :split_with_list do
assert Path.split('') == ''
assert Path.split('') == []
assert Path.split('foo') == ['foo']
assert Path.split('/foo/bar') == ['/', 'foo', 'bar']
end
+6
View File
@@ -104,6 +104,12 @@ defmodule ProtocolTest do
false = ProtocolTest.WithAll.blank(ProtocolTest.Foo.new(a: 1))
end
test :protocol_with_nil_record do
assert_raise UndefinedFunctionError, fn ->
ProtocolTest.WithOnly.blank({:nil})
end
end
test :protocol_for do
assert_protocol_for(ProtocolTest.WithAll, Atom, :foo)
assert_protocol_for(ProtocolTest.WithAll, Function, fn(x) -> x end)
+33 -11
View File
@@ -40,7 +40,7 @@ defmodule Regex.BinaryTest do
end
test :unicode do
assert ("josé" =~ %r"\p{Latin}$"u) == 3
assert ("josé" =~ %r"\p{Latin}$"u)
end
test :groups do
@@ -93,11 +93,6 @@ defmodule Regex.BinaryTest do
assert Regex.run(%r"e", "abcd", return: :index) == nil
end
test :index do
assert Regex.index(%r"c(d)", "abcd") == 2
assert Regex.index(%r"e", "abcd") == nil
end
test :scan do
assert Regex.scan(%r"c(d|e)", "abcd abce") == [["d"], ["e"]]
assert Regex.scan(%r"c(?:d|e)", "abcd abce") == ["cd", "ce"]
@@ -127,6 +122,38 @@ defmodule Regex.BinaryTest do
assert Regex.replace(%r(b), "abcbe", "[\\&]") == "a[&]c[&]e"
assert Regex.replace(%r[(b)], "abcbe", "[\\1]") == "a[b]c[b]e"
end
test :escape do
assert matches_escaped?(".")
refute matches_escaped?(".", "x")
assert matches_escaped?("[\w]")
refute matches_escaped?("[\w]", "x")
assert matches_escaped?("\\")
assert matches_escaped?("\\xff", "\\xff")
refute matches_escaped?("\\xff", "\xff")
assert matches_escaped?("(")
assert matches_escaped?("()")
assert matches_escaped?("(?:foo)")
assert matches_escaped?("\\A \\z")
assert matches_escaped?(" x ")
assert matches_escaped?("  x    x ") # unicode spaces here
assert matches_escaped?("# lol")
assert matches_escaped?("\\A.^$*+?()[{\\| \t\n\xff\\z #hello\x{202F}\x{205F}")
end
defp matches_escaped?(string) do
matches_escaped?(string, string)
end
defp matches_escaped?(string, match) do
Regex.match? %r/#{Regex.escape(string)}/usimx, match
end
end
defmodule Regex.ListTest do
@@ -151,11 +178,6 @@ defmodule Regex.ListTest do
assert Regex.run(%r"c(d)", "abcd", return: :binary) == ["cd", "d"]
end
test :index do
assert Regex.index(%r'c(d)', 'abcd') == 2
assert Regex.index(%r'e', 'abcd') == nil
end
test :indexes do
assert Regex.run(%r'c(d)', 'abcd', return: :index) == [{2,2},{3,1}]
assert Regex.run(%r'e', 'abcd', return: :index) == nil
+67
View File
@@ -42,6 +42,7 @@ defmodule StringTest do
test :upcase do
assert String.upcase("123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz") == "123 ABCD 456 EFG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ"
assert String.upcase("") == ""
assert String.upcase("abcD") == "ABCD"
end
test :upcase_utf8 do
@@ -51,16 +52,19 @@ defmodule StringTest do
test :upcase_utf8_multibyte do
assert String.upcase("straße") == "STRASSE"
assert String.upcase("áüÈß") == "ÁÜÈSS"
end
test :downcase do
assert String.downcase("123 ABcD 456 EfG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ") == "123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz"
assert String.downcase("abcD") == "abcd"
assert String.downcase("") == ""
end
test :downcase_utf8 do
assert String.downcase("& % # ÀÁÂ ÃÄÅ 1 2 Ç Æ") == "& % # àáâ ãäå 1 2 ç æ"
assert String.downcase("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ") == "àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ"
assert String.downcase("áüÈß") == "áüèß"
end
test :capitalize do
@@ -281,6 +285,7 @@ defmodule StringTest do
end
test :starts_with? do
## Normal cases ##
assert String.starts_with? "hello", "he"
assert String.starts_with? "hello", "hello"
assert String.starts_with? "hello", ["hellö", "hell"]
@@ -289,9 +294,28 @@ defmodule StringTest do
refute String.starts_with? "hello", "hellö"
refute String.starts_with? "hello", ["hellö", "goodbye"]
refute String.starts_with? "エリクシア", "仙丹"
## Edge cases ##
assert String.starts_with? "", ""
assert String.starts_with? "", ["", "a"]
assert String.starts_with? "b", ["", "a"]
assert String.starts_with? "abc", ""
assert String.starts_with? "abc", [""]
refute String.starts_with? "", "abc"
refute String.starts_with? "", [" "]
## Sanity checks ##
assert String.starts_with? "", ["", ""]
assert String.starts_with? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.starts_with? "abc", [["a"], "a"]
end
end
test :ends_with? do
## Normal cases ##
assert String.ends_with? "hello", "lo"
assert String.ends_with? "hello", "hello"
assert String.ends_with? "hello", ["hell", "lo", "xx"]
@@ -301,6 +325,49 @@ defmodule StringTest do
refute String.ends_with? "hello", "hellö"
refute String.ends_with? "hello", ["hel", "goodbye"]
refute String.ends_with? "エリクシア", "仙丹"
## Edge cases ##
assert String.ends_with? "", ""
assert String.ends_with? "", ["", "a"]
refute String.ends_with? "", ["a", "b"]
assert String.ends_with? "abc", ""
assert String.ends_with? "abc", ["", "x"]
refute String.ends_with? "", "abc"
refute String.ends_with? "", [" "]
## Sanity checks ##
assert String.ends_with? "", ["", ""]
assert String.ends_with? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.ends_with? "abc", [["c"], "c"]
end
end
test :contains? do
## Normal cases ##
assert String.contains? "elixir of life", "of"
assert String.contains? "エリクシア", "シ"
assert String.contains? "elixir of life", ["mercury", "life"]
refute String.contains? "exlixir of life", "death"
refute String.contains? "エリクシア", "仙"
refute String.contains? "elixir of life", ["death", "mercury", "eternal life"]
## Edge cases ##
assert String.contains? "", ""
assert String.contains? "abc", ""
assert String.contains? "abc", ["", "x"]
refute String.contains? "", " "
refute String.contains? "", "a"
## Sanity checks ##
assert String.contains? "", ["", ""]
assert String.contains? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.contains? "abc", [["b"], "b"]
end
end
end
+2 -2
View File
@@ -55,8 +55,8 @@ defmodule SystemTest do
end
test :cmd do
assert is_binary(System.cmd "binary")
assert is_list(System.cmd 'binary')
assert is_binary(System.cmd "cd .")
assert is_list(System.cmd 'cd .')
end
test :find_executable_with_binary do
+35
View File
@@ -34,3 +34,38 @@ defmodule PathHelpers do
elixir_executable <> "c"
end
end
defmodule CompileAssertion do
def assert_compile_fail(exception, string) do
case format_rescue(string) do
{ ^exception, _ } -> :ok
error ->
raise ExUnit.ExpectationError,
expected: inspect(exception),
actual: inspect(elem(error, 0)),
reason: "match"
end
end
def assert_compile_fail(exception, message, string) do
case format_rescue(string) do
{ ^exception, ^message } -> :ok
error ->
raise ExUnit.ExpectationError,
expected: "#{inspect exception}[message: #{inspect message}]",
actual: "#{inspect elem(error, 0)}[message: #{inspect elem(error, 1)}]",
reason: "match"
end
end
defp format_rescue(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
nil
rescue
error -> { error.__record__(:name), error.message }
end
result || raise(ExUnit.AssertionError, message: "Expected expression to fail")
end
end
+17 -7
View File
@@ -23,16 +23,16 @@ defmodule URITest do
end
test :decode_query do
assert URI.decode_query("q=search%20query&cookie=ab%26cd&block%20buster=") ==
HashDict.new [{"block buster", ""}, {"cookie", "ab&cd"}, {"q", "search query"}]
assert URI.decode_query("") == HashDict.new
assert URI.decode_query("something=weird%3Dhappening") == HashDict.new [{"something", "weird=happening"}]
assert HashDict.equal?(URI.decode_query("q=search%20query&cookie=ab%26cd&block%20buster="),
HashDict.new [{"block buster", ""}, {"cookie", "ab&cd"}, {"q", "search query"}])
assert HashDict.equal?(URI.decode_query(""), HashDict.new)
assert HashDict.equal?(URI.decode_query("something=weird%3Dhappening"), HashDict.new [{"something", "weird=happening"}])
assert URI.decode_query("", []) == []
assert URI.decode_query("garbage") == HashDict.new [{"garbage", nil}]
assert URI.decode_query("=value") == HashDict.new [{"", "value"}]
assert URI.decode_query("something=weird=happening") == HashDict.new [{"something", "weird=happening"}]
assert HashDict.equal?(URI.decode_query("garbage"), HashDict.new [{"garbage", nil}])
assert HashDict.equal?(URI.decode_query("=value"), HashDict.new [{"", "value"}])
assert HashDict.equal?(URI.decode_query("something=weird=happening"), HashDict.new [{"something", "weird=happening"}])
end
test :decoder do
@@ -129,4 +129,14 @@ defmodule URITest do
assert URI.parse("http://GoOgLe.CoM").host == "google.com"
assert URI.parse("http://LOL:wut@GoOgLe.CoM").authority == "LOL:wut@google.com"
end
test :to_binary do
assert to_binary(URI.parse("http://google.com")) == "http://google.com"
assert to_binary(URI.parse("http://google.com:443")) == "http://google.com:443"
assert to_binary(URI.parse("https://google.com:443")) == "https://google.com"
assert to_binary(URI.parse("http://lol:wut@google.com")) == "http://lol:wut@google.com"
assert to_binary(URI.parse("http://google.com/elixir")) == "http://google.com/elixir"
assert to_binary(URI.parse("http://google.com?q=lol")) == "http://google.com?q=lol"
assert to_binary(URI.parse("http://google.com?q=lol#omg")) == "http://google.com?q=lol#omg"
end
end
+28 -5
View File
@@ -16,7 +16,7 @@ defmodule ExUnit do
end
@moduledoc """
Basic unit test structure for Elixir.
Basic unit testing framework for Elixir.
## Example
@@ -24,10 +24,11 @@ defmodule ExUnit do
# File: assertion_test.exs
# 1) Start ExUnit. You can pass some options as argument (list below)
# 1) Start ExUnit. You could also pass some options to the start function
# (see `configure/1` for the list of options)
ExUnit.start
# 2) Next we create a new TestCase and use ExUnit.Case
# 2) Create a new test module (or "case") and use ExUnit.Case
defmodule AssertionTest do
# 3) Notice we pass async: true, this runs the test case
# concurrently with other test cases
@@ -45,7 +46,7 @@ defmodule ExUnit do
end
end
To run the test above, all you need to to is to run the file
To run the test above, all you need to do is to run the file
using elixir from command line. Assuming you named your file
assertion_test.exs, you can run it as:
@@ -53,12 +54,34 @@ defmodule ExUnit do
## Case, callbacks and assertions
Check `ExUnit.Case` and `ExUnit.Callbacks` for more information about
See `ExUnit.Case` and `ExUnit.Callbacks` for more information about
defining test cases.
The `ExUnit.Assertions` module contains a set of macros to easily
generate assertions with appropriate error messages.
## Integration with Mix
Mix is the project management and build tool for Elixir. Invoking `mix test`
from the command line will run tests in each file matching the pattern
"*_test.exs" found in the `test` directory of your project.
By convention, you could also create a test_helper.exs file inside the
`test` directory and put the code common to all tests there.
The minimum example of a test_helper.exs file would be:
# test/test_helper.exs
ExUnit.start
Then, in each test file, require test_helper.exs before defining test modules
(or cases):
# test/myproject_test.exs
Code.require_file "test_helper.exs", __DIR__
# ... test cases follow
"""
use Application.Behaviour
+7 -3
View File
@@ -290,10 +290,14 @@ defmodule ExUnit.Assertions do
1 + "test"
end
"""
def assert_raise(exception, message, function) when is_binary(message) and is_function(function) do
def assert_raise(exception, message, function) when is_function(function) do
error = assert_raise(exception, function)
assert message == error.message, message, error.message,
prelude: "Expected #{inspect error}'s message", reason: "a match"
is_match = case message do
re when is_regex(re) -> error.message =~ re
bin when is_binary(bin) -> error.message == bin
end
assert is_match, message, error.message,
prelude: "Expected #{inspect error}'s message", reason: "match"
error
end
+59 -11
View File
@@ -1,37 +1,83 @@
defmodule ExUnit.Callbacks do
@moduledoc %B"""
This module defines four callbacks: `setup_all`, `teardown_all`,
`setup` and `teardown`. Those callbacks are defined via macros
and receives a keyword list of metadata. The callback may
optionally define extra data which will be available in the test
cases.
`setup` and `teardown`.
Those callbacks are defined via macros and each one can optionally receive a
keyword list with metadata, usually referred to as `context`. The callback
may optionally put extra data into `context` to be used in the tests.
**Note**: `setup` and `teardown` callbacks share the same context, it
provides an ExUnit.Test record associated with the `:test` key. `setup_all`
and `teardown_all` share their own context in a similar way, but this one
provides an ExUnit.TestCase record associated with the `:case` key.
If you return `{ :ok, <keyword list> }` from `setup` or `teardown`, the keyword
list will get merged into the context that will be available in all
subsequent `setup`, `test`, or `teardown` calls.
Similarly, returning `{ :ok, <keyword list> }` from `setup_all` or
`teardown_all` will merge the keyword list into the context that will be
available in all subsequent `setup_all` or `teardown_all` calls.
Returning :ok leaves the context unchanged in both cases.
Returning anything else from `setup` or `teardown` will force the current
test to fail, and subsequent `setup`, `test`, and `teardown` callbacks won't
be called for it.
Returning anything else from `setup_all` or `teardown_all` will force the
whole case to fail, and no other callback will be called.
It is allowed to define multiple `setup` or `teardown` callbacks, they will
be called sequentially in the order of definition before each test. The
returned keyword list from the last `setup` will be merged into the context passed to
the `test` and `teardown` (if defined) callbacks.
In the case of `setup_all` and `teardown_all` callbacks, each `setup_all`
will be called only once before the first test's `setup` and each
`teardown_all` will be called once after the last test. The returned keyword
list from the last `setup_all` will get merged into the context passed to the
`teardown_all` callbacks.
## Examples
defmodule AssertionTest do
use ExUnit.Case, async: true
# `setup` is called before each test is run
setup do
IO.puts "This is a setup callback"
# Returns extra metadata
# Return extra metadata, it has to be a keyword list
{ :ok, [hello: "world"] }
end
# Same as `setup`, but receives the context for the current test
setup context do
# We can access the test name in the context
# We can access the test record in the context
IO.puts "Setting up: #{context[:test]}"
# The metadata returned by the previous setup as well
# We can also access the data returned from `setup/0`
assert context[:hello] == "world"
# No metadata
:ok
end
# This is called after each test finishes
teardown context do
assert context[:hello] == "world"
:ok
end
test "always pass" do
assert true
end
test "another one", context do
assert context[:hello] == "world"
end
end
"""
@@ -91,8 +137,8 @@ defmodule ExUnit.Callbacks do
end
@doc """
Called after the finish of each test. Note that, if the test crasches with an exit
message `teardown` will not be run.
Called after the finish of each test. Note that if the test crashed with an :exit
message, `teardown` will not be run.
"""
defmacro teardown(var // quote(do: _), block) do
quote do
@@ -103,7 +149,8 @@ defmodule ExUnit.Callbacks do
end
@doc """
Called before the start of a case.
Called before the start of a case, i.e. called once before the first test in
the current module and before any `setup` callbacks.
"""
defmacro setup_all(var // quote(do: _), block) do
quote do
@@ -114,7 +161,7 @@ defmodule ExUnit.Callbacks do
end
@doc """
Called after the finish of each case.
Called once after the last test finishes without emitting an :exit message.
"""
defmacro teardown_all(var // quote(do: _), block) do
quote do
@@ -126,6 +173,7 @@ defmodule ExUnit.Callbacks do
## Helpers
@doc false
def __merge__(_mod, other, :ok), do: other
def __merge__(_mod, other, { :ok, data }) when is_list(data), do: Keyword.merge(other, data)
def __merge__(mod, _, failure) do
+81 -41
View File
@@ -1,7 +1,6 @@
defmodule ExUnit.CaptureIO do
@moduledoc %B"""
This module provides functionality to capture IO to test it.
The way to use this module is to import them into your module.
## Examples
@@ -21,13 +20,17 @@ defmodule ExUnit.CaptureIO do
@doc """
Captures IO. Returns nil in case of no output,
otherwise returns the binary which is captured outputs.
otherwise returns the binary which is the captured output.
By default, capture_io replaces the group_leader (`:stdio`)
for the current process. However, the capturing of any other
named device like `:stderr` is also possible globally by
giving the registered device name explicitly as argument.
When capturing `:stdio` and the `:capture_prompt` option is `false`,
prompts (specified as arguments in IO.get* functions) are not
captured.
A developer can set a string as an input. The default
input is `:eof`.
@@ -39,36 +42,49 @@ defmodule ExUnit.CaptureIO do
true
iex> capture_io(:stderr, fn -> IO.write(:stderr, "josé") end) == "josé"
true
iex> capture_io("this is input", fn->
...> input = IO.gets ""
iex> capture_io("this is input", fn ->
...> input = IO.gets ">"
...> IO.write input
...> end) == ">this is input"
true
iex> capture_io([input: "this is input", capture_prompt: false], fn ->
...> input = IO.gets ">"
...> IO.write input
...> end) == "this is input"
true
"""
def capture_io(device, input, fun) do
do_capture_io(map_dev(device), input, fun)
def capture_io(fun) do
do_capture_io(:standard_io, [], fun)
end
def capture_io(device, fun) when is_atom(device) do
do_capture_io(map_dev(device), "", fun)
capture_io(device, [], fun)
end
def capture_io(input, fun) when is_binary(input) do
do_capture_io(:standard_io, input, fun)
capture_io(:standard_io, [input: input], fun)
end
def capture_io(fun) do
do_capture_io(:standard_io, "", fun)
def capture_io(options, fun) when is_list(options) do
capture_io(:standard_io, options, fun)
end
def capture_io(device, input, fun) when is_binary(input) do
capture_io(device, [input: input], fun)
end
def capture_io(device, options, fun) when is_list(options) do
do_capture_io(map_dev(device), options, fun)
end
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp do_capture_io(:standard_io, input, fun) do
defp do_capture_io(:standard_io, options, fun) do
original_gl = :erlang.group_leader
capture_gl = new_group_leader(self, input)
capture_gl = new_group_leader(self, options)
:erlang.group_leader(capture_gl, self)
try do
@@ -83,13 +99,15 @@ defmodule ExUnit.CaptureIO do
end
end
defp do_capture_io(device, input, fun) do
defp do_capture_io(device, options, fun) do
unless original_io = Process.whereis(device) do
raise "could not find IO device registered at #{inspect device}"
end
options = Keyword.put(options, :capture_prompt, false)
Process.unregister(device)
capture_io = new_group_leader(self, input)
capture_io = new_group_leader(self, options)
Process.register(capture_io, device)
try do
@@ -105,24 +123,28 @@ defmodule ExUnit.CaptureIO do
end
end
defp new_group_leader(runner, input) do
spawn_link(fn -> group_leader_process(runner, input) end)
defp new_group_leader(runner, options) do
spawn_link(fn -> group_leader_process(runner, options) end)
end
defp group_leader_process(runner, input) do
defp group_leader_process(runner, options) do
prompt_config = Keyword.get(options, :capture_prompt, true)
input = Keyword.get(options, :input, "")
register_input(input)
register_prompt_config(prompt_config)
group_leader_loop(runner, :infinity, [])
end
defp register_input(nil) do
set_input(nil)
end
defp register_input(input) do
chars = :unicode.characters_to_list(input)
set_input(chars)
end
defp register_prompt_config(bool) do
Process.put(:capture_io_prompt_config, bool)
end
defp set_input(:eof) do
set_input([])
end
@@ -135,6 +157,10 @@ defmodule ExUnit.CaptureIO do
Process.get(:capture_io_input)
end
defp need_prompt? do
Process.get(:capture_io_prompt_config)
end
defp group_leader_loop(runner, wait, buf) do
receive do
{ :io_request, from, reply_as, req } ->
@@ -181,28 +207,42 @@ defmodule ExUnit.CaptureIO do
io_request({ :put_chars, mod, func, args }, buf)
end
defp io_request({ :get_chars, _enc, _prompt, n }, buf) when n >= 0 do
defp io_request({ :get_chars, _enc, prompt, n }, buf) when n >= 0 do
io_request({ :get_chars, prompt, n }, buf)
end
defp io_request({ :get_chars, prompt, n }, buf) when n >= 0 do
if need_prompt? do
buf = [prompt|buf]
end
{ get_chars(n), buf }
end
defp io_request({ :get_chars, _prompt, n }, buf) when n >= 0 do
{ get_chars(n), buf }
defp io_request({ :get_line, _enc, prompt }, buf) do
io_request({ :get_line, prompt }, buf)
end
defp io_request({ :get_line, _prompt }, buf) do
defp io_request({ :get_line, prompt }, buf) do
if need_prompt? do
buf = [prompt|buf]
end
{ get_line, buf }
end
defp io_request({ :get_line, _enc, _prompt }, buf) do
{ get_line, buf }
defp io_request({ :get_until, _encoding, prompt, mod, fun, args}, buf) do
io_request({ :get_until, prompt, mod, fun, args}, buf)
end
defp io_request({ :get_until, _prompt, mod, fun, args }, buf) do
{ get_until(mod, fun, args), buf }
end
defp io_request({ :get_until, prompt, mod, fun, args }, buf) do
{ result, count } = get_until(mod, fun, args)
defp io_request({ :get_until, _encoding, _prompt, mod, fun, args}, buf) do
{ get_until(mod, fun, args), buf }
if need_prompt? do
buf = [:lists.duplicate(count, prompt)|buf]
end
{ result, buf }
end
defp io_request({ :setopts, _opts }, buf) do
@@ -274,19 +314,19 @@ defmodule ExUnit.CaptureIO do
do_get_until(input, mod, fun, args)
end
defp do_get_until([], mod, fun, args, continuation // [])
defp do_get_until([], mod, fun, args, continuation // [], count // 0)
defp do_get_until([], mod, fun, args, continuation) do
defp do_get_until([], mod, fun, args, continuation, count) do
case apply(mod, fun, [continuation, :eof | args]) do
{ :done, result, rest_chars } ->
set_input(rest_chars)
result
{ result, count + 1 }
{ :more, next_continuation } ->
do_get_until([], mod, fun, args, next_continuation)
do_get_until([], mod, fun, args, next_continuation, count + 1)
end
end
defp do_get_until(input, mod, fun, args, continuation) do
defp do_get_until(input, mod, fun, args, continuation, count) do
{ line, rest } = Enum.split_while(input, fn(char) -> char != ?\n end)
case rest do
@@ -294,17 +334,17 @@ defmodule ExUnit.CaptureIO do
case apply(mod, fun, [continuation, line | args]) do
{ :done, result, rest_chars } ->
set_input(rest_chars)
result
{ result, count + 1 }
{ :more, next_continuation } ->
do_get_until([], mod, fun, args, next_continuation)
do_get_until([], mod, fun, args, next_continuation, count + 1)
end
[_|t] ->
case apply(mod, fun, [continuation, line ++ '\n' | args]) do
{ :done, result, rest_chars } ->
set_input(rest_chars ++ t)
result
{ result, count + 1 }
{ :more, next_continuation } ->
do_get_until(t, mod, fun, args, next_continuation)
do_get_until(t, mod, fun, args, next_continuation, count + 1)
end
end
end
+7 -4
View File
@@ -9,15 +9,18 @@ defmodule ExUnit.Case do
in parallel with others. Must be used for performance
when your test cases do not change any global state;
This module automatically includes all callbacks defined
in `ExUnit.Callbacks`. Read it for more information.
This module automatically includes all callbacks defined in
`ExUnit.Callbacks`. See that module's documentation for more
information.
## Examples
## Examples
defmodule AssertionTest do
# Use the module
use ExUnit.Case, async: true
def test_always_pass
# The `test` macro is imported by ExUnit.Case
test "always pass" do
assert true
end
end
+13 -9
View File
@@ -49,8 +49,8 @@ defmodule ExUnit.CLIFormatter do
{ :stop, :normal, length(config.test_failures), config }
end
def handle_call(_, _, _) do
super
def handle_call(reqest, from, config) do
super(reqest, from, config)
end
def handle_cast({ :test_finished, test = ExUnit.Test[invalid: true] }, config) do
@@ -78,8 +78,8 @@ defmodule ExUnit.CLIFormatter do
end
end
def handle_cast(_, _) do
super
def handle_cast(request, config) do
super(request, config)
end
defp print_suite(counter, [], [], run_us, load_us) do
@@ -213,24 +213,28 @@ defmodule ExUnit.CLIFormatter do
# Print styles
defp colorize(escape, string) do
IO.ANSI.escape_fragment("%{#{escape}}") <> string <> IO.ANSI.escape_fragment("%{reset}")
end
defp success(msg) do
IO.ANSI.escape("%{green}" <> msg)
colorize("green", msg)
end
defp invalid(msg) do
IO.ANSI.escape("%{yellow}" <> msg)
colorize("yellow", msg)
end
defp failure(msg) do
IO.ANSI.escape("%{red}" <> msg)
colorize("red", msg)
end
defp error_info(msg) do
IO.ANSI.escape("%{red} " <> msg)
colorize("red", " " <> msg)
end
defp location_info(msg) do
IO.ANSI.escape("%{cyan} " <> msg)
colorize("cyan", " " <> msg)
end
defp stacktrace_info(msg) do
+36 -30
View File
@@ -191,52 +191,40 @@ defmodule ExUnit.DocTest do
end
whole_expr = String.strip(whole_expr)
{ exception, message } = case Enum.find(exprs, exc_filter_fn) do
exception = case Enum.find(exprs, exc_filter_fn) do
{ _, {:error, exception, message} } ->
{ exception, message }
inspect(exception) <> "[message: \"#{message}\"]"
nil ->
{ nil, nil }
"nothing"
end
quote do
unquote_splicing(test_import(module, do_import))
unquote(gen_code_for_tests(tests, whole_expr, exception, message, stack))
unquote(gen_code_for_tests(tests, whole_expr, exception, stack))
end
end
defp gen_code_for_tests(tests, whole_expr, exception, message, stack) do
defp gen_code_for_tests(tests, whole_expr, exception, stack) do
quote do
stack = unquote(stack)
try do
# Put all tests into one context
unquote_splicing(tests)
rescue
e in [ExUnit.ExpectationError] ->
raise e, [], unquote(stack)
# If there was no exception among the tests, `exception` here will be
# nil and this clause won't match.
error in [unquote(exception)] ->
unless error.message == unquote(message) do
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(whole_expr),
expected: "#{inspect unquote(exception)} with message #{inspect unquote(message)}",
reason: "raise",
actual: inspect(error) ],
unquote(stack)
end
raise e, [], stack
error ->
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(whole_expr),
expected: "#{inspect unquote(exception)}",
expected: unquote(exception),
# We're using a combined message here because all expressions
# (those that are expected to raise and those that aren't) are in
# the same try block above.
reason: "complete or raise",
actual: inspect(error) ],
unquote(stack)
stack
end
end
end
@@ -261,18 +249,36 @@ defmodule ExUnit.DocTest do
end
end
defp test_case_content(expr, { :error, exception, _ }, module, line, file, stack) do
defp test_case_content(expr, { :error, exception, message }, module, line, file, stack) do
expr_ast = string_to_ast(module, line, file, expr)
quote do
v = unquote(expr_ast)
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(expr),
expected: "#{inspect unquote(exception)}[]",
reason: "raise",
actual: inspect(v) ],
unquote(stack)
stack = unquote(stack)
expr = unquote(expr)
exception = inspect(unquote(exception)) <> "[message: \"#{unquote(message)}\"]"
try do
v = unquote(expr_ast)
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: expr,
expected: exception,
reason: "raise",
actual: inspect(v) ],
stack
rescue
error in [unquote(exception)] ->
unless error.message == unquote(message) do
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: expr,
expected: exception,
reason: "raise",
actual: inspect(error) ],
stack
end
other -> raise other
end
end
end
+21 -1
View File
@@ -78,6 +78,10 @@ defmodule ExUnit.CaptureIOTest do
test :capture_io_with_get_chars do
assert capture_io(fn ->
:io.get_chars(">", 3)
end) == ">"
assert capture_io([capture_prompt: false], fn ->
:io.get_chars(">", 3)
end) == nil
capture_io(fn ->
@@ -104,6 +108,10 @@ defmodule ExUnit.CaptureIOTest do
test :capture_io_with_get_line do
assert capture_io(fn ->
:io.get_line ">"
end) == ">"
assert capture_io([capture_prompt: false], fn ->
:io.get_line ">"
end) == nil
capture_io(fn ->
@@ -144,7 +152,19 @@ defmodule ExUnit.CaptureIOTest do
test :capture_io_with_get_until do
assert capture_io(fn ->
assert :io.scan_erl_form('>')
:io.scan_erl_form('>')
end) == ">"
assert capture_io("1.\n", fn ->
:io.scan_erl_form('>')
end) == ">"
assert capture_io("1\n.\n", fn ->
:io.scan_erl_form('>')
end) == ">>"
assert capture_io([capture_prompt: false], fn ->
:io.scan_erl_form('>')
end) == nil
capture_io(fn ->
@@ -25,8 +25,8 @@ defmodule ExUnit.CLIFormatterTest do
"""
out_put = exec_test("raising_test.exs")
assert out_put =~ %r/stacktrace:/
refute out_put =~ %r/ExUnit\.Runner/
assert out_put =~ "stacktrace:"
refute out_put =~ "ExUnit.Runner"
after
File.rm("raising_test.exs")
end
@@ -44,7 +44,7 @@ defmodule ExUnit.CLIFormatterTest do
"""
out_put = exec_test("failure_test.exs")
assert out_put =~ %r/at failure_test.exs:6/
assert out_put =~ "at failure_test.exs:6"
after
File.rm("failure_test.exs")
end
+27 -36
View File
@@ -44,7 +44,7 @@ defmodule ExUnit.DocTestTest.ExceptionModule do
def two_exceptions
end
defmodule ExUnit.DocTestTest.SomewhatGoodModule do
defmodule ExUnit.DocTestTest.SomewhatGoodModuleWithOnly do
@doc """
iex> test_fun
1
@@ -62,7 +62,7 @@ defmodule ExUnit.DocTestTest.SomewhatGoodModule do
def test_fun1, do: 1
end
defmodule ExUnit.DocTestTest.SomewhatGoodModule1 do
defmodule ExUnit.DocTestTest.SomewhatGoodModuleWithExcept do
@doc """
iex> test_fun
1
@@ -87,51 +87,42 @@ defmodule ExUnit.DocTestTest.NoImport do
"""
def min(a,b), do: max(a,b)
end
defmodule ExUnit.DocTestTest.Invalid do
@doc """
iex> _a = 1
1
iex> _a + 1
2
"""
def no_leak do
end
end
defmodule ExUnit.DocTestTest do
use ExUnit.Case
# This is intentional. The doctests in DocTest's docs fail for demonstration
# purposes.
#doctest ExUnit.DocTest
# This is intentional. The doctests in DocTest's docs fail
# for demonstration purposes.
# doctest ExUnit.DocTest
doctest ExUnit.DocTestTest.GoodModule, import: true
doctest ExUnit.DocTestTest.SomewhatGoodModule, only: [test_fun: 0], import: true
doctest ExUnit.DocTestTest.SomewhatGoodModule1, except: [test_fun1: 0], import: true
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [test_fun: 0], import: true
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithExcept, except: [test_fun1: 0], import: true
doctest ExUnit.DocTestTest.NoImport
assert_raise ExUnit.DocTest.Error, fn ->
doctest ExUnit.DocTestTest.ExceptionModule
end
test :var_leak do
assert "nofile:9: function '_a'/0 undefined" = format_rescue("""
defmodule M do
@doc \"\"\"
iex> _a = 1
1
iex> _a + 1
2
\"\"\"
def no_leak
test :no_var_leak do
assert_raise CompileError, %r"function '_a'/0 undefined", fn ->
defmodule NeverCompiled do
import ExUnit.DocTest
doctest ExUnit.DocTestTest.Invalid
end
defmodule M.Test do
require ExUnit.DocTest
ExUnit.DocTest.doctest M
end
"""
)
end
defp format_rescue(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
nil
rescue
error -> error.message
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
end
end
+10 -19
View File
@@ -205,7 +205,11 @@ defmodule IEx do
:application.start(:iex)
end
defp boot_config(opts) do
@doc """
Returns default config used to launch IEx. This config is also used by
IEx.TestFramework.
"""
def boot_config(opts) do
scope = :elixir.scope_for_eval(
file: "iex",
delegate_locals_to: IEx.Helpers
@@ -244,26 +248,13 @@ defmodule IEx do
end
@doc """
Returns `string` escaped using the specified color.
Returns `string` escaped using the specified color. ANSI escapes in `string`
are not processed in any way.
"""
def color(color_name, string) do
colors = IEx.Options.get(:colors)
IO.ANSI.escape "%{#{colors[color_name]}}#{string}", colors[:enabled]
end
@doc """
Returns an escaped fragment using the specified color.
"""
def color_fragment(color_name) do
colors = IEx.Options.get(:colors)
IO.ANSI.escape_fragment "%{#{colors[color_name]}}", colors[:enabled]
end
@doc """
Returns an escaped fragment that resets colors and attributes.
"""
def color_reset() do
colors = IEx.Options.get(:colors)
IO.ANSI.escape_fragment "%{reset}", colors[:enabled]
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{colors[color_name]}}", enabled)
<> string <> IO.ANSI.escape_fragment("%{reset}", enabled)
end
end
+27 -5
View File
@@ -99,12 +99,26 @@ defmodule IEx.Helpers do
h Enum.all?/2
h Enum.all?
"""
# Special case for `h AnyModule.__info__/1`
defmacro h({ :/, _, [{ { :., _, [_mod, :__info__] }, _, [] }, 1] }) do
quote do
IEx.Introspection.h(Module, :__info__, 1)
end
end
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
IEx.Introspection.h(unquote(mod), unquote(fun), unquote(arity))
end
end
# Special case for `h AnyModule.__info__`
defmacro h({ { :., _, [_mod, :__info__] }, _, [] }) do
quote do
IEx.Introspection.h(Module, :__info__, 1)
end
end
defmacro h({ { :., _, [mod, fun] }, _, [] }) do
quote do
IEx.Introspection.h(unquote(mod), unquote(fun))
@@ -233,7 +247,7 @@ defmodule IEx.Helpers do
in the current IEx session.
"""
def r do
Enum.map iex_reloaded, r(&1)
List.flatten(Enum.map(iex_reloaded, do_r(&1)))
end
@doc """
@@ -243,16 +257,24 @@ defmodule IEx.Helpers do
are recompiled and reloaded.
"""
def r(module) do
case do_r(module) do
mods when is_list(mods) -> { module, mods }
other -> other
end
end
defp do_r(module) do
if source = source(module) do
Process.put(:iex_reloaded, :ordsets.add_element(module, iex_reloaded))
{ module, Code.load_file source }
Enum.map(Code.load_file(source), fn {name, _} -> name end)
else
:nosource
end
end
@doc """
Purges and reloads specified module.
Loads given module beam code (and ensures any previous
old version was properly purged before).
"""
def l(module) do
:code.purge(module)
@@ -285,8 +307,8 @@ defmodule IEx.Helpers do
source = module.module_info(:compile)[:source]
case source do
{ :source, source } -> list_to_binary(source)
_ -> nil
nil -> nil
source -> list_to_binary(source)
end
end
+2 -7
View File
@@ -9,10 +9,7 @@ defmodule IEx.Introspection do
{ :module, _ } ->
case module.__info__(:moduledoc) do
{ _, binary } when is_binary(binary) ->
IO.puts IEx.color(:info, "# #{inspect module}\n")
# We use fragments here so that escape sequences in `binary` are
# treated literally and don't get converted to ANSI escapes.
IO.write IEx.color_fragment(:info) <> binary <> IEx.color_reset()
IO.write IEx.color(:info, "# #{inspect module}\n\n" <> binary)
{ _, _ } ->
IO.puts IEx.color(:error, "No docs for #{inspect module} have been found")
_ ->
@@ -150,9 +147,7 @@ defmodule IEx.Introspection do
defp print_doc({ { fun, _ }, _line, kind, args, doc }) do
args = Enum.map_join(args, ", ", print_doc_arg(&1))
IO.puts IEx.color(:info, "* #{kind} #{fun}(#{args})\n")
# We use fragments here so that escape sequences in `doc` are
# treated literally and don't get converted to ANSI escapes.
if doc, do: IO.write IEx.color_fragment(:info) <> doc <> IEx.color_reset()
if doc, do: IO.write IEx.color(:info, doc)
end
defp print_doc_arg({ ://, _, [left, right] }) do
+1 -1
View File
@@ -157,7 +157,7 @@ defmodule IEx.Server do
end
defp io_put(result) do
IO.puts :stdio, IEx.color(:eval_result, inspect(result, IEx.Options.get(:inspect)))
IO.puts IEx.color(:eval_result, inspect(result, IEx.Options.get(:inspect)))
end
defp io_error(result) do
+321
View File
@@ -0,0 +1,321 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.HelpersTest do
use IEx.Case
import IEx.Helpers
@doc """
Test function 1
"""
def test_fun_1
@doc """
Test function 2
"""
def test_fun_1(arg)
test "h helper" do
assert "# IEx.Helpers\n\nWelcome to Interactive Elixir" <> _
= capture_iex("h")
end
test "h helper module" do
assert "# IEx.Helpers\n\nWelcome to Interactive Elixir" <> _
= capture_io(fn -> h IEx.Helpers end)
assert capture_io(fn -> h :whatever end)
== "Could not load module :whatever: nofile\n"
end
test "h helper function" do
doc_1 = "* def test_fun_1()\n\nTest function 1\n"
doc_2 = "* def test_fun_1(arg)\n\nTest function 2\n"
assert capture_io(fn -> h IEx.HelpersTest.test_fun_1/0 end) == doc_1
assert capture_io(fn -> h IEx.HelpersTest.test_fun_1/1 end) == doc_2
output = capture_io(fn -> h IEx.HelpersTest.test_fun_1 end)
assert :binary.match(output, doc_1)
assert :binary.match(output, doc_2)
assert capture_io(fn -> h pwd end)
== "* def pwd()\n\nPrints the current working directory.\n\n"
end
test "h helper __info__" do
h_output_module = capture_io(fn -> h Module.__info__ end)
assert capture_io(fn -> h Module.UnlikelyTo.Exist.__info__ end) == h_output_module
assert capture_io(fn -> h Module.UnlikelyTo.Exist.__info__/1 end) == h_output_module
assert capture_io(fn -> h __info__ end) == "No docs for __info__ have been found\n"
end
test "t helper" do
assert capture_io(fn -> t ExUnit end) == "No types for ExUnit have been found\n"
# Test that it shows at least two types
assert Enum.count(capture_io(fn -> t Enum end) |> String.split("\n"), fn line ->
String.starts_with? line, "@type"
end) >= 2
assert "@type t() :: " <> _
= capture_io(fn -> t Enum.t end)
assert capture_io(fn -> t Enum.t end) == capture_io(fn -> t Enum.t/0 end)
end
test "s helper" do
assert capture_io(fn -> s ExUnit end) == "No specs for ExUnit have been found\n"
# Test that it shows at least two specs
assert Enum.count(capture_io(fn -> s Enum end) |> String.split("\n"), fn line ->
String.starts_with? line, "@spec"
end) >= 2
assert Enum.count(capture_io(fn -> s Enum.all? end) |> String.split("\n"), fn line ->
String.starts_with? line, "@spec"
end) >= 2
assert capture_io(fn -> s Enum.all?/1 end) == "@spec all?(t()) :: boolean()\n"
assert capture_io(fn -> s list_to_binary end) == "@spec list_to_binary(iolist()) :: binary()\n"
end
test "v helper" do
assert capture_iex("v") == ":ok"
assert capture_iex("1\n2\nv") == String.rstrip """
1
2
1: 1
#=> 1
2: 2
#=> 2
:ok
"""
assert "** (RuntimeError) Out of bounds" <> _
= capture_iex("v(0)")
assert capture_iex("1\n2\nv(2)") == "1\n2\n2"
assert capture_iex("1\n2\nv(2)") == capture_iex("1\n2\nv(-1)")
end
test "flush helper" do
assert capture_io(fn -> self() <- :hello; flush end) == ":hello\n"
end
test "pwd helper" do
assert capture_io(fn -> pwd end) =~ %r"lib[\\/]iex\n$"
end
test "ls helper" do
assert ["ebin", "lib", "mix.exs", "test"]
= capture_io(fn -> ls end)
|> String.split
|> Enum.map(String.strip(&1))
|> Enum.sort
assert capture_io(fn -> ls "~" end) == capture_io(fn -> ls System.user_home end)
end
test "import_file helper" do
File.write! "dot-iex", "variable = :hello\nimport IO"
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.variable/0" <> _
= capture_iex("variable")
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.puts/1" <> _
= capture_iex("puts \"hi\"")
assert capture_iex("import_file \"dot-iex\"\nvariable\nputs \"hi\"")
== "nil\n:hello\nhi\n:ok"
after
File.rm! "dot-iex"
end
test "import_file nested" do
File.write! "dot-iex-1", "variable = :hello\nimport IO"
File.write! "dot-iex", "parent = true\nimport_file \"dot-iex-1\""
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.parent/0" <> _
= capture_iex("parent")
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.variable/0" <> _
= capture_iex("variable")
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.puts/1" <> _
= capture_iex("puts \"hi\"")
assert capture_iex("import_file \"dot-iex\"\nvariable\nputs \"hi\"\nparent")
== "nil\n:hello\nhi\n:ok\ntrue"
after
File.rm "dot-iex-1"
File.rm! "dot-iex"
end
test "m helper" do
regexes = [
%r/^:application\s+.+application\.beam$/,
%r/^:code\s+.+code\.beam$/,
%r/^Kernel\s+.+Elixir\.Kernel\.beam$/,
]
assert Enum.count(capture_io(fn -> m end) |> String.split("\n"), fn line ->
Enum.any? regexes, fn re ->
Regex.match? re, line
end
end) >= 2
end
defp purge(mod) do
true = :code.delete mod
:code.purge mod
end
defp cleanup_modules(mods) do
Enum.each mods, fn mod ->
File.rm! "#{mod}.beam"
purge mod
end
end
defp with_file(names, codes, fun) when is_list(names) and is_list(codes) do
Enum.each Enum.zip(names, codes), fn { name, code } ->
File.write! name, code
end
try do
fun.()
after
Enum.each names, File.rm(&1)
end
end
defp with_file(name, code, fun) do
with_file(List.wrap(name), List.wrap(code), fun)
end
test "c helper" do
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
filename = "sample.ex"
with_file filename, test_module_code, fn ->
assert c(filename) == [Sample]
assert Sample.run == :run
end
after
cleanup_modules([Sample])
end
test "c helper multiple modules" do
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
filename = "sample.ex"
with_file filename, test_module_code <> "\n" <> another_test_module, fn ->
assert c(filename) |> Enum.sort == [Sample,Sample2]
assert Sample.run == :run
assert Sample2.hello == :world
end
after
cleanup_modules([Sample, Sample2])
end
test "c helper list" do
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
filenames = ["sample1.ex", "sample2.ex"]
with_file filenames, [test_module_code, another_test_module], fn ->
assert c(filenames) |> Enum.sort == [Sample,Sample2]
assert Sample.run == :run
assert Sample2.hello == :world
end
after
cleanup_modules([Sample, Sample2])
end
test "l helper" do
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
assert l(:non_existent_module) == {:error,:nofile}
filename = "sample.ex"
with_file filename, test_module_code, fn ->
assert c(filename) == [Sample]
assert Sample.run == :run
File.write! filename, "defmodule Sample do end"
# FIXME: this could probably be improved
System.cmd "../../bin/elixirc sample.ex"
assert l(Sample) == {:module, Sample}
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
end
after
# Clean up the old version left over after l()
:code.purge(Sample)
cleanup_modules([Sample])
end
test "r helper basic" do
assert r == []
assert_raise UndefinedFunctionError, "undefined function: :non_existent_module.module_info/1", fn ->
r :non_existent_module
end
# There is no source file for the module defined in IEx
assert ":ok\n** (Code.LoadError) could not load" <> _
= capture_iex("{:module, Sample, _, {:run,0}} = #{String.strip test_module_code}; :ok\nr Sample")
after
purge Sample
end
test "r helper" do
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
filename = "sample.ex"
with_file filename, test_module_code, fn ->
assert capture_io(:stdio, fn ->
assert c(filename) == [Sample]
assert Sample.run == :run
File.write! filename, "defmodule Sample do end"
assert {Sample, [Sample]} = r(Sample)
assert_raise UndefinedFunctionError, "undefined function: Sample.run/0", fn ->
Sample.run
end
assert [Sample] = r()
end) =~ %r"^.+?sample\.ex:1: redefining module Sample\n.+?sample\.ex:1: redefining module Sample\n$"
end
after
# Clean up old version produced by the r helper
:code.purge(Sample)
cleanup_modules([Sample])
end
defp test_module_code do
"""
defmodule Sample do
def run do
:run
end
end
"""
end
defp another_test_module do
"""
defmodule Sample2 do
def hello do
:world
end
end
"""
end
end
+81
View File
@@ -0,0 +1,81 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.InteractionTest do
use IEx.Case
### basic interaction ###
test "whole output" do
IEx.Options.set :colors, enabled: false
# We're using bare-bones capture_io instead of capture_iex only once here
assert capture_io("IO.puts \"Hello world\"", fn ->
IEx.Server.start(IEx.boot_config(dot_iex_path: ""))
end) =~ %r"^Interactive Elixir \(.+?\) - press Ctrl\+C to exit \(type h\(\) ENTER for help\)\niex\(1\)> Hello world\n:ok\niex\(2\)> $"
end
test "empty input" do
assert capture_iex("\n") == "nil"
end
test "normal input" do
assert capture_iex("1 + 2") == "3"
end
test "exception" do
exception = Regex.escape("** (ArithmeticError) bad argument in arithmetic expression")
assert capture_iex("1 + :atom\n:this_is_still_working")
=~ %r/^#{exception}.+\n:this_is_still_working$/s
end
test "empty history at the start" do
assert "** (RuntimeError) Out of bounds" <> _ = capture_iex("v(-1)")
end
test "empty history at the start redux" do
assert "** (RuntimeError) Out of bounds" <> _ = capture_iex("v(1)")
end
test "no break" do
input = """
["a
b
c
"""
assert capture_iex(input) == ""
end
test "break" do
input = """
["a
b
c
#iex:break
"""
assert "** (TokenMissingError) iex:1: incomplete expression" <> _ = capture_iex(input)
end
### .iex file loading ###
test "no .iex" do
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.my_variable/0" <> _ = capture_iex("my_variable")
end
test ".iex" do
File.write!("dot-iex", "my_variable = 144")
assert capture_iex("my_variable", [], "dot-iex") == "144"
after
File.rm!("dot-iex")
end
test "nested .iex" do
File.write!("dot-iex-1", "nested_var = 13\nimport IO")
File.write!("dot-iex", "import_file \"dot-iex-1\"\nmy_variable=14")
input = "nested_var\nmy_variable\nputs \"hello\""
assert capture_iex(input, [], "dot-iex") == "13\n14\nhello\n:ok"
after
File.rm("dot-iex-1")
File.rm!("dot-iex")
end
end
+51
View File
@@ -0,0 +1,51 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.OptionsTest do
use IEx.Case
@doc """
Hello, I have %{red}ANSI%{reset} escapes.
"""
def ansi_escapes
test "color" do
opts = [colors: [enabled: true, eval_result: "red"]]
assert capture_iex("1 + 2", opts) == "\e[31m3\e[0m"
# Sanity checks
assert capture_iex("IO.ANSI.escape(\"%{blue}hello\", true)", opts)
== "\e[31m\"\\e[34mhello\\e[0m\"\e[0m"
assert capture_iex("IO.puts IO.ANSI.escape(\"%{blue}hello\", true)", opts)
== "\e[34mhello\e[0m\n\e[31m:ok\e[0m"
assert capture_iex("IO.puts IO.ANSI.escape(\"%{blue}hello\", true)", [colors: [enabled: false]])
== "\e[34mhello\e[0m\n:ok"
# Test that ANSI escapes in the docs are left alone
opts = [colors: [enabled: true, info: "red", eval_result: "red"]]
assert capture_iex("h IEx.OptionsTest.ansi_escapes", opts)
== "\e[31m* def ansi_escapes()\n\e[0m\n\e[31mHello, I have %{red}ANSI%{reset} escapes.\n\e[0m\n\e[31m:ok\e[0m"
# Test that ANSI escapes in iex output are left alone
assert capture_iex("\"%{red} %{blue}\"", opts) == "\e[31m\"%{red} %{blue}\"\e[0m"
assert capture_iex("IO.puts IEx.color(:info, \"%{red} %{blue}\")", opts)
== "\e[31m%{red} %{blue}\e[0m\n\e[31m:ok\e[0m"
end
test "inspect opts" do
opts = [inspect: [limit: 3, raw: true]]
assert capture_iex("[1,2,3,4,5]\nArgumentError[]", opts) ==
"[1,2,3,...]\n{ArgumentError,:__exception__,\"argument error\"}"
opts = [inspect: [raw: false]]
assert capture_iex("ArgumentError[]", opts) == "ArgumentError[message: \"argument error\"]"
end
test "history size" do
opts = [history_size: 3]
assert capture_iex("1\n2\n3\nv(1)", opts) == "1\n2\n3\n1"
assert "1\n2\n3\n4\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(1)", opts)
assert "1\n2\n3\n4\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(-4)", opts)
assert "1\n2\n3\n4\n2\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(2)\nv(2)", opts)
end
end
+75 -1
View File
@@ -1,2 +1,76 @@
:application.start(:iex)
ExUnit.start []
ExUnit.start []
defmodule IEx.Case do
@moduledoc false
#
# Provides convenience functions for testing IEx-related functionality.
# Use this module inside your test module like this:
#
# defmodule IEx.InteractionTest do
# use IEx.Case
#
# test :input do
# assert capture_iex("1+2") == "3"
# end
# end
#
# The environment provided by capture_iex is mostly similar to the normal IEx
# session, except colors are disabled by default and .iex files are not
# loaded.
#
# You can provide your own IEx.Options and a path to a .iex file as
# additional arguments to the capture_iex function.
#
defmacro __using__(_) do
quote do
use ExUnit.Case, async: false
import ExUnit.CaptureIO
import unquote(__MODULE__)
setup do
opts = IEx.Options.get
IEx.Options.set :colors, [enabled: false]
{ :ok, [iex_opts: opts] }
end
teardown context do
IEx.Options.set context[:iex_opts]
:ok
end
end
end
@doc """
Starts an IEx eval loop, feeds it the provided input and returns produced
output. The output is stripped of the first intro line and of any trailing
whitespace.
Options, if provided, will be set before the eval loop is started.
If you provide the dot_iex_path argument, it will be passed to
IEx.Server.start to be used in the normal .iex loading process.
"""
def capture_iex(input, options // [], dot_iex_path // "") do
Enum.each options, fn { opt, value } ->
IEx.Options.set(opt, value)
end
ExUnit.CaptureIO.capture_io([input: input, capture_prompt: false], fn ->
IEx.Server.start(IEx.boot_config(dot_iex_path: dot_iex_path))
end) |> strip_iex
end
defp strip_iex(string) do
string
|> strip_line # strip the greeting
|> String.strip
end
defp strip_line(string) do
Regex.replace %r/\A.+?$/ms, string, ""
end
end
+19 -6
View File
@@ -5,10 +5,13 @@ defmodule Mix.CLI do
Runs Mix according to the command line arguments.
"""
def run(args // System.argv) do
if help?(args) do
display_banner()
else
proceed(args)
case check_for_shortcuts(args) do
:help ->
display_banner()
:version ->
display_version()
nil ->
proceed(args)
end
end
@@ -68,6 +71,16 @@ defmodule Mix.CLI do
run_task "help", []
end
defp help?([first_arg|_]) when first_arg in ["--help", "-h", "-help"], do: true
defp help?(_), do: false
defp display_version() do
IO.puts "Elixir #{System.version}"
end
# Check for --help or --version in the args
defp check_for_shortcuts([first_arg|_]) when first_arg in
["--help", "-h", "-help"], do: :help
defp check_for_shortcuts([first_arg|_]) when first_arg in
["--version", "-v"], do: :version
defp check_for_shortcuts(_), do: nil
end
+48 -22
View File
@@ -1,5 +1,5 @@
defrecord Mix.Dep, [ scm: nil, app: nil, requirement: nil, status: nil, opts: nil,
project: nil, deps: [] ] do
deps: [], source: nil, manager: nil ] do
@moduledoc """
This is a record that keeps information about your project
dependencies. It keeps:
@@ -9,7 +9,9 @@ defrecord Mix.Dep, [ scm: nil, app: nil, requirement: nil, status: nil, opts: ni
* requirements - a binary or regexp with the deps requirement;
* status - the current status of dependency, check `Mix.Deps.format_status/1` for more info;
* opts - the options given by the developer
* project - the Mix.Project for the dependency
* source - any possible configuration associated with the manager field,
rebar.config for rebar or the Mix.Project for Mix
* manager - the project management, possible values: :rebar | :mix | :make | nil
"""
end
@@ -81,7 +83,7 @@ defmodule Mix.Deps do
index = Mix.Dep.__index__(:app)
Enum.each apps, fn(app) ->
unless List.keyfind(deps, app, index) do
Mix.shell.info message: "unknown dependency #{app} for env #{Mix.env}"
Mix.shell.info "unknown dependency #{app} for env #{Mix.env}"
end
end
@@ -93,7 +95,20 @@ defmodule Mix.Deps do
changing the current working directory and loading the given
project into the project stack.
"""
def in_dependency(Mix.Dep[app: app, opts: opts], post_config // [], fun) do
def in_dependency(dep, post_config // [], fun)
def in_dependency(Mix.Dep[manager: :rebar, opts: opts], post_config, fun) do
# Use post_config for rebar deps
Mix.Project.post_config(post_config)
Mix.Project.push(Mix.Rebar)
try do
File.cd!(opts[:dest], fn -> fun.(nil) end)
after
Mix.Project.pop
end
end
def in_dependency(Mix.Dep[app: app, opts: opts], post_config, fun) do
env = opts[:env] || :prod
old_env = Mix.env
@@ -207,8 +222,8 @@ defmodule Mix.Deps do
@doc """
Returns all compile paths for the dependency.
"""
def compile_paths(Mix.Dep[app: app, opts: opts] = dep) do
if mix?(dep) do
def compile_paths(Mix.Dep[app: app, opts: opts, manager: manager]) do
if manager == :mix do
Mix.Project.in_project app, opts[:dest], fn _ ->
Mix.Project.compile_paths
end
@@ -220,37 +235,48 @@ defmodule Mix.Deps do
@doc """
Returns all load paths for the dependency.
"""
def load_paths(Mix.Dep[app: app, opts: opts] = dep) do
if mix?(dep) do
paths = Mix.Project.in_project app, opts[:dest], fn _ ->
Mix.Project.load_paths
end
Enum.uniq paths
else
[ Path.join(opts[:dest], "ebin") ]
def load_paths(Mix.Dep[manager: :mix, app: app, opts: opts]) do
paths = Mix.Project.in_project app, opts[:dest], fn _ ->
Mix.Project.load_paths
end
Enum.uniq paths
end
def load_paths(Mix.Dep[manager: :rebar, opts: opts, source: source]) do
# Add root dir and all sub dirs with ebin/ directory
sub_dirs = Enum.map(source[:sub_dirs] || [], fn path ->
Path.join(opts[:dest], path)
end)
[ opts[:dest] | sub_dirs ]
|> Enum.map(Path.wildcard(&1))
|> List.concat
|> Enum.map(Path.join(&1, "ebin"))
|> Enum.filter(File.dir?(&1))
end
def load_paths(Mix.Dep[manager: manager, opts: opts]) when manager in [:make, nil] do
[ Path.join(opts[:dest], "ebin") ]
end
@doc """
Returns true if dependency is a mix project.
"""
def mix?(dep) do
dep.project != nil
def mix?(Mix.Dep[manager: manager]) do
manager == :mix
end
@doc """
Returns true if dependency is a rebar project.
"""
def rebar?(dep) do
Enum.any? ["rebar.config", "rebar.config.script"], fn file ->
File.regular? Path.join(dep.opts[:dest], file)
end
def rebar?(Mix.Dep[manager: manager]) do
manager == :rebar
end
@doc """
Returns true if dependency is a make project.
"""
def make?(dep) do
File.regular? Path.join(dep.opts[:dest], "Makefile")
def make?(Mix.Dep[manager: manager]) do
manager == :make
end
end
+11 -14
View File
@@ -20,8 +20,8 @@ defmodule Mix.Deps.Converger do
def all(rest, callback) do
config = [ deps_path: Path.expand(Mix.project[:deps_path]),
root_lockfile: Path.expand(Mix.project[:lockfile]) ]
{ main, rest } = Mix.Deps.Retriever.all(rest, config,callback)
{ all(Enum.reverse(main), [], [], main), rest }
main = Mix.Deps.Retriever.children |> Enum.reverse
all(main, [], [], main, config, callback, rest)
end
# We traverse the tree of dependencies in a breadth-
@@ -63,25 +63,22 @@ defmodule Mix.Deps.Converger do
# Now, since `d` was specified in a parent project, no
# exception is going to be raised since d is considered
# to be the authorative source.
defp all([dep|t], acc, upper_breadths, current_breadths) do
defp all([dep|t], acc, upper_breadths, current_breadths, config, callback, rest) do
cond do
contains_dep?(upper_breadths, dep) ->
all(t, acc, upper_breadths, current_breadths)
all(t, acc, upper_breadths, current_breadths, config, callback, rest)
match?({ diverged_acc, true }, diverged_dep?(acc, dep)) ->
all(t, diverged_acc, upper_breadths, current_breadths)
all(t, diverged_acc, upper_breadths, current_breadths, config, callback, rest)
true ->
deps = dep.deps
if deps != [] do
acc = all(t, [dep|acc], upper_breadths, current_breadths)
all(deps, acc, current_breadths, deps ++ current_breadths)
else
all(t, [dep|acc], upper_breadths, current_breadths)
end
{ dep, rest } = callback.(dep, rest)
deps = Mix.Deps.Retriever.children(dep, config)
{ acc, rest } = all(t, [dep.deps(deps)|acc], upper_breadths, current_breadths, config, callback, rest)
all(deps, acc, current_breadths, deps ++ current_breadths, config, callback, rest)
end
end
defp all([], acc, _upper, _current) do
acc
defp all([], acc, _upper, _current, _config, _callback, rest) do
{ acc, rest }
end
# Does the list contain the given dependency?
+86 -56
View File
@@ -4,64 +4,33 @@
defmodule Mix.Deps.Retriever do
@moduledoc false
@doc """
Returns all dependencies for the current Mix.Project
as a `Mix.Dep` record.
## Exceptions
This function raises an exception in case the developer
provides a dependency in the wrong format.
"""
def all(post_config // []) do
{ deps, _ } = all(nil, post_config, fn(dep, acc) -> { dep, acc } end)
deps
end
@doc """
Like `all/0` but takes a callback that is invoked for
each dependency and must return an updated depedency
in case some processing is done.
"""
def all(rest, post_config // [], callback) do
Enum.map_reduce children, rest, fn (dep, rest) ->
{ dep, rest } = callback.(dep, rest)
if Mix.Deps.available?(dep) and mixfile?(dep) do
{ dep, rest } = Mix.Deps.in_dependency dep, post_config, fn project ->
{ deps, rest } = all(rest, callback)
# We need to call with_mix_project once again
# here in case the dependency was not available
# the first time and the callback hook just
# happened to fetch it.
{ with_mix_project(dep, project).deps(deps), rest }
end
end
{ dep, rest }
end
end
@doc """
Gets all direct children for the current Mix.Project
as a `Mix.Dep` record. Unlike with `all` the `deps`
field is not populated.
as a `Mix.Dep` record.
"""
def children() do
deps = Mix.project[:deps] || []
# Don't run recursively for the top-level project
scms = Mix.SCM.available
(Mix.project[:deps] || []) |> Enum.map(update(&1, scms, nil))
end
Enum.map deps, fn dep ->
dep = with_scm_and_status(dep, scms)
@doc """
Gets all children for a given dependency.
"""
def children(dep, config) do
cond do
Mix.Deps.available?(dep) and mixfile?(dep) ->
Mix.Deps.in_dependency(dep, config, fn _ ->
mix_children(config)
end)
if Mix.Deps.available?(dep) and mixfile?(dep) do
Mix.Deps.in_dependency dep, fn project ->
with_mix_project(dep, project)
end
else
dep
end
Mix.Deps.available?(dep) and rebarconfig?(dep) ->
Mix.Deps.in_dependency(dep, config, fn _ ->
rebar_children(".")
end)
true ->
[]
end
end
@@ -69,20 +38,71 @@ defmodule Mix.Deps.Retriever do
Receives a dependency and update its status.
"""
def update(Mix.Dep[scm: scm, app: app, requirement: req, opts: opts]) do
with_scm_and_status({ app, req, opts }, [scm])
update({ app, req, opts }, [scm], nil)
end
## Helpers
defp with_mix_project(Mix.Dep[project: nil] = dep, project) do
defp mix_children(config) do
scms = Mix.SCM.available
Mix.Project.recur(config, fn _ ->
(Mix.project[:deps] || []) |> Enum.map(update(&1, scms, nil))
end) |> List.concat
end
defp rebar_children(dir) do
scms = Mix.SCM.available
Mix.Rebar.recur(dir, fn config ->
Mix.Rebar.deps(config) |> Enum.map(update(&1, scms, :rebar))
end) |> List.concat
end
defp update(tuple, scms, manager) do
dep = with_scm_and_status(tuple, scms)
if Mix.Deps.available?(dep) do
cond do
mixfile?(dep) ->
Mix.Deps.in_dependency(dep, fn project ->
mix_dep(dep, project)
end)
rebarconfig?(dep) ->
rebar_dep(dep)
makefile?(dep) ->
make_dep(dep)
true ->
dep.manager(manager)
end
else
dep
end
end
defp mix_dep(Mix.Dep[manager: nil] = dep, project) do
if match?({ :noappfile, _ }, dep.status) and Mix.Project.umbrella? do
dep = dep.update_opts(Keyword.put(&1, :app, false))
.status({ :ok, nil })
end
dep.project(project)
dep.manager(:mix).source(project)
end
defp with_mix_project(dep, _project), do: dep
defp mix_dep(dep, _project), do: dep
defp rebar_dep(Mix.Dep[manager: nil, opts: opts] = dep) do
config = Mix.Rebar.load_config(opts[:dest])
dep.manager(:rebar).source(config)
end
defp rebar_dep(dep), do: dep
defp make_dep(Mix.Dep[manager: nil] = dep) do
dep.manager(:make)
end
defp make_dep(dep), do: dep
defp with_scm_and_status({ app, opts }, scms) when is_atom(app) and is_list(opts) do
with_scm_and_status({ app, nil, opts }, scms)
@@ -156,6 +176,16 @@ defmodule Mix.Deps.Retriever do
defp vsn_match?(expected, actual) when is_regex(expected), do: actual =~ expected
defp mixfile?(dep) do
File.regular?(Path.join dep.opts[:dest], "mix.exs")
File.regular?(Path.join(dep.opts[:dest], "mix.exs"))
end
defp rebarconfig?(dep) do
Enum.any?(["rebar.config", "rebar.config.script"], fn file ->
File.regular?(Path.join(dep.opts[:dest], file))
end)
end
defp makefile?(dep) do
File.regular? Path.join(dep.opts[:dest], "Makefile")
end
end
+2 -1
View File
@@ -17,6 +17,7 @@ defexception Mix.Error, mix_error: true, message: nil
defexception Mix.OutOfDateDepsError, mix_error: true, env: nil do
def message(exception) do
"Some dependencies are out of date, please run `MIX_ENV=#{exception.env} mix deps.get` to proceed"
"Some dependencies are out of date, please run `MIX_ENV=#{exception.env} mix deps.get` to proceed, " <>
"or solve any diverged dependencies, run `MIX_ENV=#{exception.env} mix deps` for more information"
end
end
+7 -4
View File
@@ -31,6 +31,8 @@ defmodule Mix.Project do
defined.
"""
alias Mix.Server.Project
@doc false
defmacro __using__(_) do
quote do
@@ -85,7 +87,7 @@ defmodule Mix.Project do
"""
def get do
case Mix.Server.call(:projects) do
[{ h, _ }|_] -> h
[Project[name: project]|_] -> project
_ -> nil
end
end
@@ -103,7 +105,7 @@ defmodule Mix.Project do
"""
def config do
case Mix.Server.call(:projects) do
[{ h, config }|_] when h != nil -> config
[Project[name: name, config: config]|_] when name != nil -> config
_ -> default_config
end
end
@@ -149,7 +151,7 @@ defmodule Mix.Project do
use this function to transparently go through the project, regardless
if it is an umbrella project or not.
"""
def recur(fun) do
def recur(post_config // [], fun) do
if apps_path = config[:apps_path] do
paths = Path.wildcard(Path.join(apps_path, "*"))
paths = Enum.filter(paths, File.dir?(&1))
@@ -163,11 +165,12 @@ defmodule Mix.Project do
projects = topsort_projects(projects, Path.expand(apps_path))
results = Enum.map projects, fn { app, app_path } ->
in_project(app, app_path, fun)
in_project(app, app_path, post_config, fun)
end
results
else
# Note that post_config isnt used for this case
[fun.(get)]
end
end
+155
View File
@@ -0,0 +1,155 @@
defmodule Mix.Rebar do
@moduledoc false
# Make Mix.Rebar work like a project so we can push it into the stack.
@doc false
def project, do: []
@doc """
Returns the path supposed to host the local copy of rebar.
"""
def local_rebar_path, do: Path.join(Mix.Utils.mix_home, "rebar")
@doc """
Returns the path to the global copy of rebar, if one exists.
"""
def global_rebar_cmd do
wrap_cmd System.find_executable("rebar")
end
@doc """
Returns the path to the local copy of rebar, if one exists.
"""
def local_rebar_cmd do
rebar = local_rebar_path
wrap_cmd(if File.regular?(rebar), do: rebar)
end
@doc """
Returns the path to the rebar copy in the current working directory, if one exists.
"""
def cwd_rebar_cmd do
wrap_cmd(if File.regular?("./rebar"), do: Path.join(File.cwd!, "rebar"))
end
@doc """
Loads the rebar.config and evaluates rebar.config.script if it
exists in the given directory.
"""
def load_config(dir) do
config_path = Path.join(dir, "rebar.config")
script_path = Path.join(dir, "rebar.config.script")
config = case :file.consult(config_path) do
{ :ok, config } ->
config
{ :error, :enoent } ->
[]
{ :error, error } ->
reason = :file.format_error(error)
raise Mix.Error, message: "Error consulting rebar config #{config_path}: #{reason}"
end
if File.exists?(script_path) do
eval_script(script_path, config)
else
config
end
end
@doc """
Parses the dependencies in given rebar.config to Mix's dependency format.
"""
def deps(config) do
if deps = config[:deps] do
deps_dir = config[:deps_dir] || "deps"
Enum.map(deps, parse_dep(&1, deps_dir))
else
[]
end
end
@doc """
Runs `fun` inside the given directory and all specified `sub_dirs` in the
rebar config in the directory.
"""
def recur(dir, fun) do
config = load_config(dir)
if sub_dirs = config[:sub_dirs] do
sub_dirs = sub_dirs
|> Enum.map(Path.wildcard(&1))
|> List.concat
|> Enum.filter(File.dir?(&1))
Enum.map(sub_dirs, fn(dir) ->
recur(dir, fun)
end) |> List.concat
end
[File.cd!(dir, fn -> fun.(config) end)]
end
defp parse_dep({ app, req }, deps_dir) do
{ app, compile_req(req), [path: Path.join(deps_dir, app)] }
end
defp parse_dep({ app, req, source }, _deps_dir) do
[ scm, url | source ] = tuple_to_list(source)
{ ref, source } = case source do
[""|s] -> { [branch: "HEAD"], s }
[{ :branch, branch }|s] -> { [branch: to_binary(branch)], s }
[{ :tag, tag }|s] -> { [tag: to_binary(tag)], s }
[ref|s] -> { [ref: to_binary(ref)], s }
_ -> { [], [] }
end
raw = case source do
[[:raw]|_] -> [app: false]
[[raw: true]|_] -> [app: false]
_ -> []
end
opts = [{ scm, to_binary(url) }] ++ ref ++ raw
{ app, compile_req(req), opts }
end
defp parse_dep(app, deps_dir) do
parse_dep({ app, ".*" }, deps_dir)
end
defp compile_req(req) do
case to_binary(req) |> Regex.compile do
{ :ok, re } ->
re
{ :error, reason } ->
raise Mix.Error, message: "Unable to compile version regex: \"#{req}\", #{reason}"
end
end
defp eval_script(path, config) do
script = Path.basename(path) |> binary_to_list
case :file.script(path, eval_binds(CONFIG: config, SCRIPT: script)) do
{ :ok, config } ->
config
{ :error, error } ->
reason = :file.format_error(error)
raise Mix.Error, message: "Error evaluating rebar config script #{path}: #{reason}"
end
end
defp eval_binds(binds) do
Enum.reduce(binds, :erl_eval.new_bindings, fn ({k, v}, binds) ->
:erl_eval.add_binding(k, v, binds)
end)
end
defp wrap_cmd(nil), do: nil
defp wrap_cmd(rebar) do
case :os.type do
{ :win32, _ } -> "escript.exe #{rebar}"
_ -> rebar
end
end
end
+41 -18
View File
@@ -5,8 +5,9 @@ defmodule Mix.Server do
use GenServer.Behaviour
defrecord Config, tasks: Ordset.new, projects: [], mixfile: [],
shell: Mix.Shell.IO, scm: Ordset.new, env: nil, post_config: [],
io_done: false
shell: Mix.Shell.IO, scm: Ordset.new, env: nil, post_config: []
defrecord Project, name: nil, config: nil, rec_enabled?: true, io_done: false
def start_link(env) do
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, env, [])
@@ -56,8 +57,8 @@ defmodule Mix.Server do
def handle_call(:pop_project, _from, config) do
case config.projects do
[{ project, _ }|tail] ->
{ :reply, project, config.projects(tail).io_done(false) }
[ Project[name: project] | tail ] ->
{ :reply, project, config.projects(tail) }
_ ->
{ :reply, nil, config }
end
@@ -67,15 +68,25 @@ defmodule Mix.Server do
{ :reply, config.mixfile[app], config }
end
def handle_call(:io_done, _from, config) do
{ :reply, config.io_done, config.io_done(true) }
end
def handle_call(:output_app?, _from, config) do
# Check that we haven't already outputted app and that we are part of an
# umbrella project
output = not config.io_done and not umbrella?(config) and in_umbrella?(config)
{ :reply, output, config.io_done(true) }
case config.projects do
[ project | tail ] ->
output = not project.io_done and not umbrella?(config) and in_umbrella?(config)
{ :reply, output, config.projects([project.io_done(true)|tail]) }
_ ->
{ :reply, false, config }
end
end
def handle_call(:recursive_enabled?, _from, config) do
case config.projects do
[ Project[rec_enabled?: bool] | _ ] ->
{ :reply, bool, config }
_ ->
{ :reply, true, config }
end
end
def handle_call(request, from, config) do
@@ -106,11 +117,11 @@ defmodule Mix.Server do
{ :noreply, config.update_tasks Ordset.filter(fn {t, _} -> t != task end, &1) }
end
def handle_cast({ :push_project, name, project }, config) do
project = Keyword.merge(project, config.post_config)
def handle_cast({ :push_project, name, conf }, config) do
conf = Keyword.merge(conf, config.post_config)
project = Project[name: name, config: conf]
config = config.post_config([])
.update_projects([{ name, project }|&1])
.io_done(false)
.update_projects([project|&1])
{ :noreply, config }
end
@@ -130,19 +141,31 @@ defmodule Mix.Server do
{ :noreply, config.mixfile([]) }
end
def handle_cast({:recursive_enabled?, bool}, config) do
case config.projects do
[ project | tail ] ->
{ :noreply, config.projects([project.rec_enabled?(bool)|tail]) }
_ ->
{ :noreply, config }
end
end
def handle_cast(request, config) do
super(request, config)
end
# Returns if project is part of an umbrella project
# Returns true if project is part of an umbrella project
defp in_umbrella?(config) do
Enum.any? config.projects, fn { _, conf } -> conf[:apps_path] != nil end
Enum.any?(config.projects, fn(Project[config: conf]) ->
conf[:apps_path] != nil
end)
end
# Returns if project is an umbrella project
# Returns true if project is an umbrella project
defp umbrella?(config) do
case config.projects do
[ { h, conf } | _ ] when h != nil -> conf[:apps_path] != nil
[ Project[name: name, config: config] | _ ] when name != nil ->
config[:apps_path] != nil
_ -> false
end
end
+18 -1
View File
@@ -38,7 +38,7 @@ defmodule Mix.Shell do
is shared accross different shells.
"""
def cmd(command, callback) do
port = Port.open({ :spawn, to_char_list(command) },
port = Port.open({ :spawn, shell_command(command) },
[:stream, :binary, :exit_status, :hide, :use_stdio, :stderr_to_stdout])
do_cmd(port, callback)
end
@@ -52,4 +52,21 @@ defmodule Mix.Shell do
status
end
end
# Finding shell command logic from :os.cmd in OTP
# https://github.com/erlang/otp/blob/8deb96fb1d017307e22d2ab88968b9ef9f1b71d0/lib/kernel/src/os.erl#L184
defp shell_command(command) do
command = to_char_list(command)
case :os.type do
{ :unix, _ } ->
%c(sh -c '#{command}')
{ :win32, osname } ->
case { System.get_env("COMSPEC"), osname } do
{ nil, :windows } -> 'command.com /c #{command}'
{ nil, _ } -> 'cmd /c #{command}'
{ cmd, _ } -> '#{cmd} /c #{command}'
end
end
end
end
+4 -1
View File
@@ -18,7 +18,7 @@ defmodule Mix.Shell.Process do
@behavior Mix.Shell
@doc """
Flush all :mix_shell messages from the current process.
Flush all :mix_shell and :mix_shell_input messages from the current process.
If a callback is given, it is invoked for each received message.
## Examples
@@ -31,6 +31,9 @@ defmodule Mix.Shell.Process do
{ :mix_shell, _, _ } = message ->
callback.(message)
flush(callback)
{ :mix_shell_input, _, _ } = message ->
callback.(message)
flush(callback)
after
0 -> :done
end
+18 -7
View File
@@ -80,11 +80,12 @@ defmodule Mix.Task do
end
@doc """
Checks if the task is defined for umbrella projects.
Checks if the task should be run recursively for all sub-apps in
umbrella projects. Returns true, false or :both.
"""
def recursive?(module) when is_atom(module) do
def recursive(module) when is_atom(module) do
case List.keyfind module.__info__(:attributes), :recursive, 0 do
{ :recursive, [bool] } -> bool
{ :recursive, [setting] } -> setting
_ -> false
end
end
@@ -120,13 +121,13 @@ defmodule Mix.Task do
@doc """
Runs a `task` with the given `args`.
If the task was not yet invoked, it returns `:ok`.
If the task was not yet invoked, it returns the task result.
If the task was already invoked, it does not run the task
again and simply aborts with `:noop`.
It may raise an exception if the task was not found
or it is invalid. Check `get/2` for more information.
or it is invalid. Check `get/1` for more information.
"""
def run(task, args // []) do
task = to_binary(task)
@@ -138,8 +139,18 @@ defmodule Mix.Task do
module = get(task)
Mix.Server.cast({ :add_task, task, app })
if recursive?(module) do
Mix.Project.recur(fn _ -> module.run(args) end)
recursive = recursive(module)
if recursive && Mix.Server.call(:recursive_enabled?) do
Mix.Server.cast({ :recursive_enabled?, false })
res = if Mix.Project.umbrella? and recursive == :both do
[module.run(args)]
else
[]
end
res = res ++ Mix.Project.recur(fn _ -> module.run(args) end)
Mix.Server.cast({ :recursive_enabled?, true })
res
else
module.run(args)
end
+1 -1
View File
@@ -5,7 +5,7 @@ defmodule Mix.Tasks.Deps.Check do
@hidden true
@shortdoc "Check if all dependencies are ok"
@recursive true
@recursive :both
@moduledoc """
Checks if all dependencies are valid and if not, abort.
+2 -2
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Clean do
use Mix.Task
@shortdoc "Remove dependencies files"
@recursive true
@recursive :both
@moduledoc """
Clean dependencies.
@@ -34,4 +34,4 @@ defmodule Mix.Tasks.Deps.Clean do
Mix.Task.run "deps.unlock", apps
end
end
end
end
+29 -38
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Compile do
use Mix.Task
@shortdoc "Compile dependencies"
@recursive true
@recursive :both
@moduledoc """
Compile dependencies.
@@ -104,15 +104,34 @@ defmodule Mix.Tasks.Deps.Compile do
end
end
defp do_rebar(app, root_path) do
do_command app, rebar_cmd(app), "compile skip_deps=true deps_dir=#{inspect root_path}"
end
@rebar_cmds [:cwd_rebar_cmd, :local_rebar_cmd, :global_rebar_cmd]
defp rebar_cmd(app) do
Enum.find_value(@rebar_cmds, apply(Mix.Rebar, &1, [])) || handle_rebar_not_found(app)
end
defp handle_rebar_not_found(app) do
shell = Mix.shell
shell.info "Could not find rebar, which is needed to build #{app}"
shell.info "I can install a local copy which is just used by mix"
unless shell.yes?("Shall I install this local copy?") do
raise Mix.Error, message: "could not find rebar to compile " <>
"dependency #{app}, please ensure rebar is available"
end
Mix.Task.run "local.rebar", []
Mix.Rebar.local_rebar_cmd || raise Mix.Error, message: "Rebar instalation failed"
end
defp do_command(app, command, extra // "") do
if System.find_executable(command) do
if Mix.shell.cmd("#{command} #{extra}") != 0 do
raise Mix.Error, message: "could not compile dependency #{app}, #{command} command failed. " <>
"In case you want to recompile this dependency, please run: mix deps.compile #{app}"
end
else
raise Mix.Error, message: "could not find executable #{command} to compile " <>
"dependency #{app}, please ensure #{command} is available"
if Mix.shell.cmd("#{command} #{extra}") != 0 do
raise Mix.Error, message: "could not compile dependency #{app}, #{command} command failed. " <>
"In case you want to recompile this dependency, please run: mix deps.compile #{app}"
end
end
@@ -121,38 +140,10 @@ defmodule Mix.Tasks.Deps.Compile do
end
defp do_compile(app, command) when is_binary(command) do
Mix.shell.info("#{app}: #{command}")
if Mix.shell.cmd(command) != 0 do
raise Mix.Error, message: "could not compile dependency #{app}, custom #{command} command failed. " <>
"In case you want to recompile this dependency, please run: mix deps.compile #{app}"
end
end
defp do_rebar(app, root_path) do
do_command app, find_rebar(app), "compile deps_dir=#{inspect root_path}"
end
defp find_rebar(app) do
cond do
File.regular?("./rebar") ->
Path.join(File.cwd!, "rebar")
File.regular?(Mix.Tasks.Local.Rebar.local_rebar_path) ->
Mix.Tasks.Local.Rebar.local_rebar_path
System.find_executable("rebar") ->
"rebar"
true ->
shell = Mix.shell
shell.info "Could not find rebar, which is needed to build #{app}"
shell.info "I can install a local copy which is just used by mix"
unless shell.yes?("Shall I install this local copy?") do
raise Mix.Error, message: "could not find rebar to compile " <>
"dependency #{app}, please ensure rebar is available"
end
Mix.Task.run "local.rebar", []
Mix.Tasks.Local.Rebar.local_rebar_path
end
end
end
+2 -2
View File
@@ -4,7 +4,7 @@ defmodule Mix.Tasks.Deps do
import Mix.Deps, only: [all: 0, format_dep: 1, format_status: 1, check_lock: 2]
@shortdoc "List dependencies and their status"
@recursive true
@recursive :both
@moduledoc """
List all dependencies and their status.
@@ -28,4 +28,4 @@ defmodule Mix.Tasks.Deps do
shell.info " #{format_status dep}"
end
end
end
end
+1 -1
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Get do
use Mix.Task
@shortdoc "Get all out of date dependencies"
@recursive true
@recursive :both
@moduledoc """
Get all out of date dependencies, i.e. dependencies
+1 -1
View File
@@ -5,7 +5,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
@hidden true
@shortdoc "Load all dependencies paths"
@recursive true
@recursive :both
@moduledoc """
Loads all dependencies. This is invoked directly
+2 -2
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Unlock do
use Mix.Task
@shortdoc "Unlock the given dependencies"
@recursive true
@recursive :both
@moduledoc """
Unlock the given dependencies. If no dependencies
@@ -22,4 +22,4 @@ defmodule Mix.Tasks.Deps.Unlock do
Mix.Deps.Lock.write(lock)
end
end
end
+1 -1
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Update do
use Mix.Task
@shortdoc "Update dependencies"
@recursive true
@recursive :both
@moduledoc """
Update dependencies.
+26 -9
View File
@@ -18,15 +18,18 @@ defmodule Mix.Tasks.Escriptize do
The following options can be specified in your mix.exs file:
* `escript_name` - the name of the generated escript
Defaults to project name
Defaults to app name
* `escript_path` - the path to write the escript to
Defaults to project name
Defaults to app name
* `escript_main_module` - the module containing the main/1 function.
* `escript_app` - the app to start with the escript
Defaults to app name
* `escript_main_module` - the module containing the main/1 function
Defaults to `Project`
* `escript_embed_elixir` - if true embed elixir in the escript file.
* `escript_embed_elixir` - if true embed elixir in the escript file
Defaults to true
* `escript_embed_extra_apps` - embed additional Elixir applications
@@ -58,6 +61,7 @@ defmodule Mix.Tasks.Escriptize do
filename = project[:escript_path] || atom_to_binary(script_name)
embed = Keyword.get(project, :escript_embed_elixir, true)
beams = all_beams()
app = Keyword.get(project, :escript_app, project[:app])
cond do
!script_name ->
@@ -67,7 +71,7 @@ defmodule Mix.Tasks.Escriptize do
raise Mix.Error, message: "Could not generate escript #{filename}, " <>
"no beam files available"
force or Mix.Utils.stale?(beams, [filename]) ->
files = gen_main(script_name, project[:escript_main_module])
files = gen_main(script_name, project[:escript_main_module], app)
files = files ++ all_files()
if embed do
@@ -149,23 +153,25 @@ defmodule Mix.Tasks.Escriptize do
end
end
defp gen_main(script_name, nil) do
defp gen_main(script_name, nil, app) do
camelized = Mix.Utils.camelize(atom_to_binary(script_name))
gen_main(script_name, Module.concat([camelized]))
gen_main(script_name, Module.concat([camelized]), app)
end
defp gen_main(script_name, script_name) do
defp gen_main(script_name, script_name, _app) do
[]
end
defp gen_main(name, module) do
defp gen_main(name, module, app) do
{ :module, ^name, binary, _ } =
defmodule name do
@module module
@app app
def main(args) do
case :application.start(:elixir) do
:ok ->
start_app
args = Enum.map(args, :unicode.characters_to_binary(&1))
Kernel.CLI.run fn -> @module.main(args) end, true
_ ->
@@ -173,6 +179,17 @@ defmodule Mix.Tasks.Escriptize do
System.halt(1)
end
end
defp start_app do
if app = @app do
case Application.Behaviour.start(app) do
:ok -> :ok
{ :error, reason } ->
IO.puts :stderr, IO.ANSI.escape("%{red, bright} Could not start application #{app}: #{inspect reason}.")
System.halt(1)
end
end
end
end
[{ '#{name}.beam', binary }]
+10 -5
View File
@@ -17,26 +17,31 @@ defmodule Mix.Tasks.Local.Install do
mix some_task
## Command line options
* `--force` forces installation without a shell prompt. Primarily
intended for automation in build systems like Make.
"""
def run(argv) do
{ _, argv } = OptionParser.parse(argv)
{ opts, argv } = OptionParser.parse(argv, switches: [force: :boolean])
case argv do
[] ->
raise Mix.Error, message: "expected PATH to be given, please use `mix local.install PATH`"
[path|_] ->
do_install path
do_install path, opts
end
end
defp do_install(path) do
defp do_install(path, opts) do
beam = Mix.Utils.read_path(path)
{ :module, module } = get_module(path, beam)
validate_module_name!(path, module)
task_name = Mix.Task.task_name(module)
if Mix.shell.yes?("Are you sure you want to install task #{inspect task_name}?") do
if opts[:force] || Mix.shell.yes?("Are you sure you want to install task #{inspect task_name}?") do
tasks = Mix.Local.tasks_path
File.mkdir_p! tasks
create_file Path.join(tasks, "#{module}.beam"), beam
@@ -60,4 +65,4 @@ defmodule Mix.Tasks.Local.Install do
raise Mix.Error, message: "expected a Mix.Tasks module at #{path}, got #{other}"
end
end
end
end
+4 -11
View File
@@ -3,10 +3,8 @@ defmodule Mix.Tasks.Local.Rebar do
import Mix.Generator, only: [create_file: 2]
@rebar_url "https://github.com/downloads/basho/rebar/rebar"
@local_rebar_name "rebar"
@shortdoc "Install rebar locally"
@rebar_url "https://github.com/downloads/basho/rebar/rebar"
@shortdoc "Install rebar locally"
@moduledoc """
Fetch a copy of rebar from the given path or url, defaulting to
@@ -16,12 +14,6 @@ defmodule Mix.Tasks.Local.Rebar do
This version of rebar will be used as required by mix deps.compile
"""
@doc """
Return the path to the local copy of rebar. Used when building deps
"""
def local_rebar_path, do: Path.join(local_rebar_dir, @local_rebar_name)
defp local_rebar_dir, do: Mix.Utils.mix_home
def run(argv) do
{ _, argv } = OptionParser.parse(argv)
do_install(case argv do
@@ -32,7 +24,8 @@ defmodule Mix.Tasks.Local.Rebar do
defp do_install(path) do
rebar = Mix.Utils.read_path(path)
File.mkdir_p! local_rebar_dir
local_rebar_path = Mix.Rebar.local_rebar_path
File.mkdir_p! Path.dirname(local_rebar_path)
create_file local_rebar_path, rebar
:file.change_mode local_rebar_path, 0755
end
+9 -1
View File
@@ -137,7 +137,15 @@ defmodule Mix.Tasks.New do
use Mix.Project
def project do
[ apps_path: "apps" ]
[ apps_path: "apps",
deps: deps ]
end
# Returns the list of dependencies in the format:
# { :foobar, "0.1", git: "https://github.com/elixir-lang/foobar.git" }
# These dependencies are not accessible from child applications
defp deps do
[]
end
end
"""
+2 -1
View File
@@ -1,2 +1,3 @@
git_repo
deps_on_git_repo
deps_on_git_repo
git_rebar
-9
View File
@@ -1,9 +0,0 @@
defmodule Escripttest.Mixfile do
use Mix.Project
def project do
[ app: :escripttest,
version: "0.0.1",
escript_embed_elixir: true ]
end
end
@@ -1,9 +0,0 @@
defmodule Escripttestwithpath do
def start do
:ok = :application.start(:escripttestwithpath)
end
def main(_args) do
IO.puts "TEST_WITH_PATH"
end
end
-11
View File
@@ -1,11 +0,0 @@
defmodule Escripttestwithpath.Mixfile do
use Mix.Project
def project do
[ app: :escripttestwithpath,
version: "0.0.1",
escript_embed_elixir: true,
escript_name: :escripttestwithpath,
escript_path: Path.join("ebin", "escripttestwithpath") ]
end
end
View File
+5
View File
@@ -0,0 +1,5 @@
{ sub_dirs, ["apps/*"] }.
{ deps, [
{ git_rebar, "0.1..*", { git, "../../test/fixtures/git_rebar", master } }
] }.

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