Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: lest/elixir
base: 9974d0add9
...
head fork: lest/elixir
compare: fa35d55fdf
Checking mergeability… Don't worry, you can still create the pull request.
  • 14 commits
  • 18 files changed
  • 0 commit comments
  • 4 contributors
View
69 lib/eex.ex
@@ -7,16 +7,77 @@ defmodule EEx do
"""
@doc """
- Get a string `source` and generate the correspondents
- quotes to be evaluated by Elixir.
+ Generates a function definition from the string.
+ The kind (`:def` or `:defp`) must be given, the
+ function name, its arguments and the common
+ compilation options.
+
+ ## Examples
+
+ defmodule Sample do
+ require EEx
+ EEx.def_from_string :def, :sample, "<%= a + b %>", [:a, :b]
+ end
+
+ Sample.sample(1, 2) #=> "3"
+
+ """
+ defmacro function_from_string(kind, name, source, args // [], options // []) do
+ quote do
+ EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
+ unquote(args), EEx.compile_string(unquote(source), unquote(options)),
+ line: __LINE__, file: __FILE__)
+ end
+ end
+
+ @doc """
+ Generates a function definition from the file contents.
+ The kind (`:def` or `:defp`) must be given, the
+ function name, its arguments and the common
+ compilation options.
+
+ This function is useful in case you have templates but
+ you want to precompile inside a module for speed.
+
+ ## Examples
+
+ defmodule Sample do
+ require EEx
+ EEx.function_from_file :def, :sample, "sample.eex", [:a, :b]
+ end
+
+ Sample.sample(1, 2) #=> "3"
+
+ """
+ defmacro function_from_file(kind, name, filename, args // [], options // []) do
+ quote do
+ EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
+ unquote(args), EEx.compile_file(unquote(filename), unquote(options)),
+ line: __LINE__, file: __FILE__)
+ end
+ end
+
+ @doc false
+ # Function used internally by function_from_file and function_from_string
+ def function_from_quoted(module, kind, name, args, source, info) do
+ args = Enum.map args, fn(arg) -> { arg, 0, nil } end
+ quote = quote do
+ unquote(kind).(unquote(name).(unquote_splicing(args)), do: unquote(source))
+ end
+ Module.eval_quoted module, quote, [], info
+ end
+
+ @doc """
+ Get a string `source` and generate a quoted expression
+ that can be evaluated by Elixir or compiled to a function.
"""
def compile_string(source, options // []) do
EEx.Compiler.compile(source, options)
end
@doc """
- Get a `filename` and generate the correspondents quotes to
- be evaluated by Elixir.
+ Get a `filename` and generate a quoted expression
+ that can be evaluated by Elixir or compiled to a function.
"""
def compile_file(filename, options // []) do
options = Keyword.put options, :file, filename
View
4 lib/eex/compiler.ex
@@ -28,9 +28,9 @@ defmodule EEx.Compiler do
generate_buffer(t, buffer, scope, state)
end
- defp generate_buffer([{ :start_expr, line, _, chars }|t], buffer, scope, state) do
+ defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line))
- buffer = state.engine.handle_expr(buffer, '=', contents)
+ buffer = state.engine.handle_expr(buffer, mark, contents)
generate_buffer(t, buffer, scope, state.dict([]))
end
View
26 lib/elixir/builtin.ex
@@ -1059,17 +1059,15 @@ defmodule Elixir.Builtin do
true && 1 #=> 1
false && error(:bad) #=> false
- Notice that, differently from Erlang `and` and `and` operators,
- this operator accepts any expression as arguments, not only booleans.
- Unfortunately cannot be used in macros.
+ Notice that, differently from Erlang `and` operator,
+ this operator accepts any expression as arguments,
+ not only booleans, however it is not allowed in guards.
"""
defmacro :&&.(left, right) do
quote do
case unquote(left) do
- match: false
- false
- match: nil
- nil
+ match: andand in [false, nil]
+ andand
match: _
unquote(right)
end
@@ -1088,17 +1086,17 @@ defmodule Elixir.Builtin do
false || 1 #=> 1
true || error(:bad) #=> true
- Notice that, differently from Erlang `or` and `or` operators,
- this operator accepts any expression as arguments, not only booleans.
- Unfortunately cannot be used in macros.
+ Notice that, differently from Erlang `or` operator,
+ this operator accepts any expression as arguments,
+ not only booleans, however it is not allowed in guards.
"""
defmacro :||.(left, right) do
quote do
- case !(oror = unquote(left)) do
- match: false
- oror
- else:
+ case unquote(left) do
+ match: oror in [false, nil]
unquote(right)
+ match: oror
+ oror
end
end
end
View
4 lib/elixir/cli.ex
@@ -87,12 +87,12 @@ defmodule Elixir.CLI do
end
defp process_shared(['-pa',h|t], config) do
- Enum.each File.wildcard(h), Code.prepend_path(&1)
+ Enum.each File.wildcard(File.expand_path(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(['-pz',h|t], config) do
- Enum.each File.wildcard(h), Code.append_path(&1)
+ Enum.each File.wildcard(File.expand_path(h)), Code.append_path(&1)
process_shared t, config
end
View
4 lib/enum.ex
@@ -693,9 +693,9 @@ defmodule Enum do
defp do_split_with({ h, next }, iterator, fun, acc, module) do
case fun.(h) do
match: x in [false, nil]
- do_split_with(iterator.(next), iterator, fun, [h|acc], module)
- else:
{ List.reverse(acc), module.to_list(h, next) }
+ else:
+ do_split_with(iterator.(next), iterator, fun, [h|acc], module)
end
end
View
83 lib/ex_unit.ex
@@ -1,43 +1,46 @@
-# Basic unit test structure for Elixir.
-#
-# ## Example
-#
-# A basic setup for ExUnit is shown below:
-#
-# # File: assertion_test.exs
-#
-# # 1) Start ExUnit. You can pass some options as argument (list below)
-# ExUnit.start
-#
-# # 2) Next we create a new TestCase and add ExUnit.Case to it
-# defmodule AssertionTest do
-# use ExUnit.Case
-#
-# # 3) A test is a method which name finishes with _test
-# def test_always_pass
-# true = true
-# end
-# end
-#
-# To run the test above, all you need to to is to use the bin/exunit
-# script that ships with Elixir. Assuming you named your file
-# assertion_test.exs, you can run it as:
-#
-# bin/elixir assertion_test.exs
-#
-# ## Assertions
-#
-# Most of ExUnit assertions can be done with pattern matching.
-# However, there are a few assertions over ExUnit.Assertions to aid testing.
-#
-# ## Options
-#
-# ExUnit supports the following options given to configure:
-#
-# * `:formatter` - The formatter that will print results
-# * `:max_cases` - Maximum number of cases to run in parallel
-#
defmodule ExUnit do
+ @moduledoc """
+ Basic unit test structure for Elixir.
+
+ ## Example
+
+ A basic setup for ExUnit is shown below:
+
+ # File: assertion_test.exs
+
+ # 1) Start ExUnit. You can pass some options as argument (list below)
+ ExUnit.start
+
+ # 2) Next we create a new TestCase and add ExUnit.Case to it
+ defmodule AssertionTest do
+ use ExUnit.Case
+
+ # 3) A test is a method which name finishes with _test
+ def test_always_pass
+ true = true
+ end
+ end
+
+ To run the test above, all you need to to is to use the bin/exunit
+ script that ships with Elixir. Assuming you named your file
+ assertion_test.exs, you can run it as:
+
+ bin/elixir assertion_test.exs
+
+ ## Assertions
+
+ Most of ExUnit assertions can be done with pattern matching.
+ However, there are a few assertions over ExUnit.Assertions to aid testing.
+
+ ## Options
+
+ ExUnit supports the following options given to configure:
+
+ * `:formatter` - The formatter that will print results
+ * `:max_cases` - Maximum number of cases to run in parallel
+
+ """
+
def start(options // []) do
ExUnit.Server.start_link
configure(options)
@@ -56,4 +59,4 @@ defmodule ExUnit do
failures = ExUnit.Runner.start config
if failures > 0, do: halt(1), else: halt(0)
end
-end
+end
View
30 lib/ex_unit/case.ex
@@ -17,19 +17,21 @@ defmodule ExUnit.Case do
end
end
- # Provides a convenient macro that allows a test to be
- # defined with a string. This macro automatically inserts
- # the atom :ok as the last line of the test. That said,
- # a passing test always returns :ok, but, more important,
- # it forces Elixir to not tail call optimize the test and
- # therefore avoiding hiding lines from the backtrace.
- #
- # ## Examples
- #
- # test "true is equal to true" do
- # assert true == true
- # end
- #
+ @doc """
+ Provides a convenient macro that allows a test to be
+ defined with a string. This macro automatically inserts
+ the atom :ok as the last line of the test. That said,
+ a passing test always returns :ok, but, more important,
+ it forces Elixir to not tail call optimize the test and
+ therefore avoiding hiding lines from the backtrace.
+
+ ## Examples
+
+ test "true is equal to true" do
+ assert true == true
+ end
+
+ """
defmacro test(message, contents) do
contents =
case contents do
@@ -55,4 +57,4 @@ defmodule ExUnit.Case do
def message, [], [], do: unquote(contents)
end
end
-end
+end
View
25 lib/file.ex
@@ -29,6 +29,31 @@ defmodule File do
end
@doc """
+ Returns if `file` exists
+ This `file` can be regular file, directory, socket,
+ symbolic link, named pipe or device file.
+
+ ## Examples
+
+ File.exists?("test/")
+ #=> true
+
+ File.exists?("missing.txt")
+ #=> false
+
+ File.exists?("/dev/null")
+ #=> true
+ """
+ def exists?(filename) do
+ case F.read_file_info(filename) do
+ match: {:ok, _}
+ true
+ else:
+ false
+ end
+ end
+
+ @doc """
Returns the last component of the `filename` or the file
name itself if it does not contain any directory separators.
View
30 lib/module.ex
@@ -199,23 +199,27 @@ defmodule Module do
end
"""
+ def add_doc(_module, _line, kind, _tuple, nil) when kind in [:defp, :defmacrop] do
+ :ok
+ end
+
+ def add_doc(_module, _line, kind, _tuple, _doc) when kind in [:defp, :defmacrop] do
+ { :error, :private_doc }
+ end
+
def add_doc(module, line, kind, tuple, doc) when
is_binary(doc) or is_boolean(doc) or doc == nil do
assert_not_compiled!(:add_doc, module)
- case kind == :defp and doc != nil do
- match: true
- { :error, :private_doc }
+ table = docs_table_for(module)
+
+ case { ETS.lookup(table, tuple), doc } do
+ match: { [], _ }
+ ETS.insert(table, { tuple, line, kind, doc })
+ :ok
+ match: { _, nil }
+ :ok
else:
- table = docs_table_for(module)
- case { ETS.lookup(table, tuple), doc } do
- match: { [], _ }
- ETS.insert(table, { tuple, line, kind, doc })
- :ok
- match: { _, nil }
- :ok
- else:
- { :error, :existing_doc }
- end
+ { :error, :existing_doc }
end
end
View
84 src/elixir_macros.erl
@@ -176,46 +176,52 @@ translate_macro({use, Line, [Raw, Args]}, S) ->
%% Access
translate_macro({ access, Line, [Element, Keyword] }, S) ->
- case S#elixir_scope.assign of
- true ->
- case translate_each(Element, S) of
- { { atom, _, Atom }, _ } ->
- case is_orddict(Keyword) of
- true -> [];
- false ->
- Message0 = "expected contents inside brackets to be a Keyword",
- syntax_error(Line, S#elixir_scope.filename, Message0)
- end,
-
- elixir_ref:ensure_loaded(Line, Atom, S),
-
- try Atom:'__record__'(fields) of
- Fields ->
- { Match, Remaining } = lists:mapfoldl(fun({Field,_}, KeywordEach) ->
- { case orddict:find(Field, KeywordEach) of
- { ok, Value } -> Value;
- error -> { '_', Line, nil }
- end, orddict:erase(Field, KeywordEach) }
- end, Keyword, Fields),
-
- case Remaining of
- [] -> translate_each({ '{}', Line, [Atom|Match] }, S);
- _ ->
- Keys = [Key || {Key,_} <- Remaining],
- Message1 = "record ~s does not have some of the given keys: ~p",
- syntax_error(Line, S#elixir_scope.filename, Message1, [elixir_errors:inspect(Atom), Keys])
- end
- catch
- error:undef ->
- Message2 = "cannot use module ~s in access protocol because it doesn't represent a record",
- syntax_error(Line, S#elixir_scope.filename, Message2, [elixir_errors:inspect(Atom)])
- end;
- _ ->
- syntax_error(Line, S#elixir_scope.filename, "invalid usage of access protocol in signature")
- end;
- false ->
+ case translate_each(Element, S) of
+ { { atom, _, Atom }, _ } -> Atom;
+ _ -> Atom = false
+ end,
+
+ case { S#elixir_scope.assign, Atom } of
+ { false, false } ->
Fallback = { { '.', Line, ['__MAIN__.Access', access] }, Line, [Element, Keyword] },
- translate_each(Fallback, S)
+ translate_each(Fallback, S);
+ { true, false } ->
+ syntax_error(Line, S#elixir_scope.filename, "invalid usage of access protocol in signature");
+ { Assign, _ } ->
+ case is_orddict(Keyword) of
+ true -> [];
+ false ->
+ Message0 = "expected contents inside brackets to be a Keyword",
+ syntax_error(Line, S#elixir_scope.filename, Message0)
+ end,
+
+ elixir_ref:ensure_loaded(Line, Atom, S),
+
+ try Atom:'__record__'(fields) of
+ Fields ->
+ { Match, Remaining } = lists:mapfoldl(fun({Field, Default}, KeywordEach) ->
+ { case orddict:find(Field, KeywordEach) of
+ { ok, Value } -> Value;
+ error ->
+ case Assign of
+ true -> { '_', Line, nil };
+ false -> '__MAIN__.Macro':escape(Default)
+ end
+ end, orddict:erase(Field, KeywordEach) }
+ end, Keyword, Fields),
+
+ case Remaining of
+ [] -> translate_each({ '{}', Line, [Atom|Match] }, S);
+ _ ->
+ Keys = [Key || {Key,_} <- Remaining],
+ Message1 = "record ~s does not have some of the given keys: ~p",
+ syntax_error(Line, S#elixir_scope.filename, Message1, [elixir_errors:inspect(Atom), Keys])
+ end
+ catch
+ error:undef ->
+ Message2 = "cannot use module ~s in access protocol because it doesn't represent a record",
+ syntax_error(Line, S#elixir_scope.filename, Message2, [elixir_errors:inspect(Atom)])
+ end
end;
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
View
2  src/elixir_module.erl
@@ -190,7 +190,7 @@ macros_clause(Line, Defmacro) ->
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Sorted)] }.
docs_clause(Line, Module, true) ->
- Docs = ets:tab2list(docs_table(Module)),
+ Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
docs_clause(Line, _Module, _) ->
View
45 test/elixir/eex_test.exs
@@ -3,6 +3,8 @@ Code.require_file "../test_helper", __FILE__
defmodule EExTest do
use ExUnit.Case
+ require EEx
+
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
end
@@ -16,36 +18,36 @@ defmodule EExTest do
end
test "evaluates with embedded do end" do
- assert_eval "foo bar", "foo <% if true do %>bar<% end %>"
+ assert_eval "foo bar", "foo <%= if true do %>bar<% end %>"
end
test "evaluates with embedded do end and eval the expression" do
- assert_eval "foo ", "foo <% if false do %>bar<% end %>"
+ assert_eval "foo ", "foo <%= if false do %>bar<% end %>"
end
test "evaluates with embedded do end and nested print expression" do
- assert_eval "foo bar", "foo <% if true do %><%= :bar %><% end %>"
+ assert_eval "foo bar", "foo <%= if true do %><%= :bar %><% end %>"
end
test "evaluates with embedded do end and nested expressions" do
- assert_eval "foo bar baz", "foo <% if true do %>bar <% Process.put(:eex_text, 1) %><%= :baz %><% end %>"
+ assert_eval "foo bar baz", "foo <%= if true do %>bar <% Process.put(:eex_text, 1) %><%= :baz %><% end %>"
assert Process.get(:eex_text) == 1
end
test "evaluates with embedded middle expression" do
- assert_eval "foo bar", "foo <% if true do %>bar<% else: %>baz<% end %>"
+ assert_eval "foo bar", "foo <%= if true do %>bar<% else: %>baz<% end %>"
end
test "evaluates with embedded middle expression and eval the expression" do
- assert_eval "foo baz", "foo <% if false do %>bar<% else: %>baz<% end %>"
+ assert_eval "foo baz", "foo <%= if false do %>bar<% else: %>baz<% end %>"
end
test "evaluates with nested start expression" do
- assert_eval "foo bar", "foo <% if true do %><% if true do %>bar<% end %><% end %>"
+ assert_eval "foo bar", "foo <%= if true do %><%= if true do %>bar<% end %><% end %>"
end
test "evaluates with nested middle expression" do
- assert_eval "foo baz", "foo <% if true do %><% if false do %>bar<% else: %>baz<% end %><% end %>"
+ assert_eval "foo baz", "foo <%= if true do %><%= if false do %>bar<% else: %>baz<% end %><% end %>"
end
test "evaluates with defined variable" do
@@ -80,7 +82,7 @@ defmodule EExTest do
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
- EEx.compile_string "foo <%if true do %><% end %><% end %>"
+ EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
@@ -109,7 +111,7 @@ foo
string = """
foo
-<% if true do %>
+<%= if true do %>
<%= __LINE__ %>
<% end %>
<%= __LINE__ %>
@@ -129,7 +131,7 @@ true
string = """
foo
-<% if __LINE__ == 2 do %>
+<%= if __LINE__ == 2 do %>
<%= true %>
<% end %>
<%= __LINE__ %>
@@ -149,7 +151,7 @@ true
string = """
foo
-<% if false do %>
+<%= if false do %>
<%= false %>
<% elsif: __LINE__ == 4 %>
<%= true %>
@@ -171,7 +173,7 @@ foo
string = """
foo
-<% if false do %>
+<%= if false do %>
<%= __LINE__ %>
<% else: %>
<%= __LINE__ %>
@@ -201,8 +203,21 @@ foo
end
end
- defp assert_eval(expected, atual) do
- result = EEx.eval_string(atual, [], file: __FILE__, engine: EEx.Engine)
+ EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
+
+ filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
+ EEx.function_from_file :defp, :other_sample, filename, [:bar]
+
+ test "defined from string" do
+ assert sample(1, 2) == "3"
+ end
+
+ test "defined from file" do
+ assert other_sample(1) == "foo 1\n"
+ end
+
+ defp assert_eval(expected, actual) do
+ result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
assert result == expected
end
end
View
16 test/elixir/elixir/cli_test.exs
@@ -21,16 +21,16 @@ defmodule Elixir.CLI.OptionParsingTest do
{ path, _ } = Code.eval list, []
# pa
- assert_member 'bin', path
- assert_member 'ebin', path
- assert_member 'exbin', path
- assert_member 'src', path
- assert_member 'lib', path
- assert_member 'include', path
- assert_member 'test', path
+ assert_member File.expand_path('bin'), path
+ assert_member File.expand_path('ebin'), path
+ assert_member File.expand_path('exbin'), path
+ assert_member File.expand_path('src'), path
+ assert_member File.expand_path('lib'), path
+ assert_member File.expand_path('include'), path
+ assert_member File.expand_path('test'), path
# pz
- assert_member 'exbin/__MAIN__', path
+ assert_member File.expand_path('exbin/__MAIN__'), path
end
test :require do
View
5 test/elixir/elixir/errors_test.exs
@@ -181,6 +181,11 @@ defmodule Elixir.ErrorsTest do
format_rescue 'defmodule Foo do\ndef sample(Elixir.ErrorsTest.Config[foo: :bar]), do: true\nend'
end
+ test :invalid_access_protocol_invalid_keywords_outside_assignment do
+ assert "nofile:1: record Elixir.ErrorsTest.Config does not have some of the given keys: [foo]" ==
+ format_rescue 'Elixir.ErrorsTest.Config[foo: :bar]'
+ end
+
test :invalid_access_protocol_on_rescue do
assert "nofile:1: cannot (yet) pattern match against erlang exceptions" ==
format_rescue 'try do\n1\nrescue: UndefinedFunctionError[arity: 1]\nfalse\nend'
View
11 test/elixir/enum_test.exs
@@ -168,10 +168,11 @@ defmodule EnumTest.List do
end
test :split_with do
- assert Enum.split_with([1,2,3], fn(_, do: false)) == { [1,2,3], [] }
- assert Enum.split_with([1,2,3], fn(_, do: true)) == { [], [1,2,3] }
- assert Enum.split_with([1,2,3], fn(x, do: x > 2)) == { [1,2], [3] }
- assert Enum.split_with([1,2,3], fn(x, do: x > 3)) == { [1,2,3], [] }
+ assert Enum.split_with([1,2,3], fn(_, do: false)) == { [], [1,2,3] }
+ assert Enum.split_with([1,2,3], fn(_, do: true)) == { [1,2,3], [] }
+ assert Enum.split_with([1,2,3], fn(x, do: x > 2)) == { [], [1,2,3] }
+ assert Enum.split_with([1,2,3], fn(x, do: x > 3)) == { [], [1,2,3] }
+ assert Enum.split_with([1,2,3], fn(x, do: x < 3)) == { [1,2], [3] }
assert Enum.split_with([], fn(_, do: true)) == { [], [] }
end
@@ -392,7 +393,7 @@ defmodule EnumTest.Orddict do
test :split_with do
dict = Orddict.new [a: 1, b: 3, c: 2, d: 4]
- assert Enum.split_with(dict, fn({_k, v}, do: rem(v, 2) == 0)) == { [a: 1, b: 3], [c: 2, d: 4] }
+ assert Enum.split_with(dict, fn({_k, v}, do: rem(v, 2) == 1)) == { [a: 1, b: 3], [c: 2, d: 4] }
end
test :take do
View
9 test/elixir/file_test.exs
@@ -32,6 +32,15 @@ defmodule FileTest do
refute File.regular?("#{__FILE__}.unknown")
end
+ test :exists do
+ assert File.exists?(__FILE__)
+ assert File.exists?(File.expand_path("../fixtures/foo.txt", __FILE__))
+ assert File.exists?(File.expand_path("../fixtures/", __FILE__))
+
+ refute File.exists?("fixtures/missing.txt")
+ refute File.exists?("_missing.txt")
+ end
+
test :basename_with_binary do
assert File.basename("foo") == "foo"
assert File.basename("/foo/bar") == "bar"
View
3  test/elixir/fixtures/compiled_with_docs.ex
@@ -2,8 +2,9 @@ defmodule CompiledWithDocs do
@moduledoc "moduledoc"
@doc "Some example"
- def example(true), do: 1
+ def example(true), do: private
def example(false), do: 0
def nodoc, do: 2
+ defp private, do: 1
end
View
2  test/elixir/fixtures/eex_template.eex
@@ -1 +1 @@
-foo <% if true do %>bar.<% end %>
+foo <%= if true do %>bar.<% end %>

No commit comments for this range

Something went wrong with that request. Please try again.