Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a613445f78 | ||
|
|
e4ea0a6e36 | ||
|
|
a03639fe1a | ||
|
|
e7b1242e98 | ||
|
|
d3d83b17f4 | ||
|
|
78eb14edae | ||
|
|
a7d931ce21 | ||
|
|
b1cd1ee7e9 | ||
|
|
1513ca7c14 | ||
|
|
016480bdcc | ||
|
|
857af06aff | ||
|
|
961099746e | ||
|
|
8247382967 | ||
|
|
9b0d972571 | ||
|
|
b19c389cee | ||
|
|
2243235e08 | ||
|
|
a72457112f | ||
|
|
3f3e86d7f4 | ||
|
|
15bf0fb16e | ||
|
|
5184edd432 | ||
|
|
fc9506e28e | ||
|
|
5c01be91ea | ||
|
|
fed406a2e6 | ||
|
|
26904197d5 | ||
|
|
f552d7620f | ||
|
|
ce55936b45 | ||
|
|
3167d78711 | ||
|
|
05eb42612b | ||
|
|
6720522707 | ||
|
|
0520b138f8 | ||
|
|
0ddb31992b | ||
|
|
4a1dfccbec | ||
|
|
c62a7e1001 | ||
|
|
2b8854c63e | ||
|
|
b535f0746e | ||
|
|
4b795a042b | ||
|
|
c80e52ee39 | ||
|
|
520415b013 | ||
|
|
27682320d4 | ||
|
|
98ade3b8fb | ||
|
|
263c76f5e5 | ||
|
|
bd984a545d | ||
|
|
115e022025 | ||
|
|
dc5fe48639 | ||
|
|
155b937327 | ||
|
|
6ef10d4b0d | ||
|
|
4f2c2ceb95 | ||
|
|
078522d014 | ||
|
|
c765a95b91 | ||
|
|
8836405bc5 | ||
|
|
dd2e0f6064 | ||
|
|
23982f1744 | ||
|
|
a6f04a2224 | ||
|
|
7291abb26f | ||
|
|
55cb8ab436 | ||
|
|
32cddf2738 | ||
|
|
7893852f2f | ||
|
|
f1b625ccd2 | ||
|
|
caa7d41e96 | ||
|
|
b96a714240 | ||
|
|
d39a52ae79 | ||
|
|
3e66197318 | ||
|
|
065b7f3f8f | ||
|
|
6004a3614f | ||
|
|
22627c44df | ||
|
|
6e959733dd | ||
|
|
7d2d352236 | ||
|
|
9d61e5886c | ||
|
|
37c8867d9f | ||
|
|
da7e830bf7 | ||
|
|
c78d3b3717 | ||
|
|
cafbd2c2d4 | ||
|
|
ae8def380a | ||
|
|
37cb1801b5 | ||
|
|
dd43e799c8 | ||
|
|
e93d4197ef | ||
|
|
d55a1703bd | ||
|
|
87268e9c78 | ||
|
|
f0ecfbfac9 | ||
|
|
854cab51bc | ||
|
|
ffdf9e9a35 | ||
|
|
6d74511e92 | ||
|
|
75a1373bbb | ||
|
|
d26e4b3dbc | ||
|
|
d636dc0ff0 | ||
|
|
ed0b161992 | ||
|
|
5e640336e0 | ||
|
|
99c7d84f9c | ||
|
|
893a52a95a | ||
|
|
a8d2e45d2f | ||
|
|
ecd3c19c64 | ||
|
|
7f9a2ed17b | ||
|
|
7951f73925 | ||
|
|
a79d965248 | ||
|
|
958550c5cd | ||
|
|
997a798994 | ||
|
|
c0547a15ce | ||
|
|
3918b889f1 | ||
|
|
d1fd5a3c2a | ||
|
|
a3aa1e8de5 | ||
|
|
7c379aa50a | ||
|
|
1fa29be448 | ||
|
|
b9a60b2668 | ||
|
|
ab9735533e | ||
|
|
6b2b928867 | ||
|
|
4b1ea18625 | ||
|
|
7cd98e149c | ||
|
|
036d016053 | ||
|
|
be2aedff98 | ||
|
|
e19d666a14 | ||
|
|
9840ac266b | ||
|
|
bcfc927455 | ||
|
|
014a2ec7a8 | ||
|
|
4846d42101 | ||
|
|
636da42f2a | ||
|
|
7c4516f1ec | ||
|
|
65cc78034d | ||
|
|
f97a384b82 | ||
|
|
7e856e1af0 | ||
|
|
893e333204 | ||
|
|
5e94a69693 | ||
|
|
5775d49c08 | ||
|
|
6397cedec6 | ||
|
|
3dd445bcce | ||
|
|
5e803487ef | ||
|
|
caeea8973c | ||
|
|
6656f7c843 | ||
|
|
e7a872c975 | ||
|
|
ea797815a0 | ||
|
|
453d8835bd | ||
|
|
2272dd1c46 | ||
|
|
41b5f4c826 | ||
|
|
ac0ac7f024 | ||
|
|
5ff3b760d9 | ||
|
|
fe97c4886a | ||
|
|
0b45b61de8 | ||
|
|
406edcb07d | ||
|
|
6c813a140d | ||
|
|
dfc40703f0 | ||
|
|
f06f15184c | ||
|
|
0cbf50c53a | ||
|
|
1d87a778ef |
@@ -14,3 +14,4 @@ erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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].
|
||||
|
||||
+7
-2
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Update do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Update dependencies"
|
||||
@recursive true
|
||||
@recursive :both
|
||||
|
||||
@moduledoc """
|
||||
Update dependencies.
|
||||
|
||||
@@ -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 }]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
git_repo
|
||||
deps_on_git_repo
|
||||
deps_on_git_repo
|
||||
git_rebar
|
||||
|
||||
-9
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user