Run the code formatter on MacroTest

Run the code formatter on MacroTest

Code.require_file("test_helper.exs", __DIR__)

defmodule Macro.ExternalTest do
  defmacro external do
    line = 18
    file = __ENV__.file
    ^line = __CALLER__.line
    ^file = __CALLER__.file
    ^line = Macro.Env.location(__CALLER__)[:line]
    ^file = Macro.Env.location(__CALLER__)[:file]
  end

  defmacro oror(left, right) do
    quote(do: unquote(left) || unquote(right))
  end
end

defmodule MacroTest do
  use ExUnit.Case, async: true

  # Changing the lines above will make compilation
  # fail since we are asserting on the caller lines
  import Macro.ExternalTest

  describe "escape/2" do
    test "handles tuples with size different than two" do
      assert Macro.escape({:a}) == {:{}, [], [:a]}
      assert Macro.escape({:a, :b, :c}) == {:{}, [], [:a, :b, :c]}
      assert Macro.escape({:a, {1, 2, 3}, :c}) == {:{}, [], [:a, {:{}, [], [1, 2, 3]}, :c]}
    end

    test "simply returns tuples with size equal to two" do
      assert Macro.escape({:a, :b}) == {:a, :b}
    end

    test "simply returns any other structure" do
      assert Macro.escape([1, 2, 3]) == [1, 2, 3]
    end

    test "handles maps" do
      assert Macro.escape(%{a: 1}) == {:%{}, [], [a: 1]}
    end

    test "handles bitstring" do
      assert {:<<>>, [], args} = Macro.escape(<<300::12>>)
      assert [{:::, [], [1, {:size, [], [4]}]}, {:::, [], [",", {:binary, [], []}]}] = args
    end

    test "works recursively" do
      assert Macro.escape([1, {:a, :b, :c}, 3]) == [1, {:{}, [], [:a, :b, :c]}, 3]
    end

    test "with improper lists" do
      assert Macro.escape([1 | 2]) == [{:|, [], [1, 2]}]
      assert Macro.escape([1, 2 | 3]) == [1, {:|, [], [2, 3]}]
    end

    test "with unquote" do
      contents = quote(unquote: false, do: unquote(1))
      assert Macro.escape(contents, unquote: true) == 1

      contents = quote(unquote: false, do: unquote(x))
      assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest}
    end

    defp eval_escaped(contents) do
      {eval, []} = Code.eval_quoted(Macro.escape(contents, unquote: true))
      eval
    end

    test "with remote unquote" do
      contents = quote(unquote: false, do: Kernel.unquote(:is_atom)(:ok))
      assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
    end

    test "with nested unquote" do
      contents =
        quote do
          quote(do: unquote(x))
        end

      assert eval_escaped(contents) == quote(do: quote(do: unquote(x)))
    end

    test "with alias or no arguments remote unquote" do
      contents = quote(unquote: false, do: Kernel.unquote(:self))
      assert eval_escaped(contents) == quote(do: Kernel.self())

      contents = quote(unquote: false, do: x.unquote(Foo))
      assert eval_escaped(contents) == quote(do: x.unquote(Foo))
    end

    test "with splicing" do
      contents = quote(unquote: false, do: [1, 2, 3, 4, 5])
      assert Macro.escape(contents, unquote: true) == [1, 2, 3, 4, 5]

      contents = quote(unquote: false, do: [1, 2, unquote_splicing([3, 4, 5])])
      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents = quote(unquote: false, do: [unquote_splicing([1, 2, 3]), 4, 5])
      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [unquote_splicing([1, 2, 3]), unquote_splicing([4, 5])])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]), 5])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]) | [5]])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]
    end
  end

  describe "expand_once/2" do
    test "with external macro" do
      assert {:||, _, [1, false]} = Macro.expand_once(quote(do: oror(1, false)), __ENV__)
    end

    test "with raw atom" do
      assert Macro.expand_once(quote(do: :foo), __ENV__) == :foo
    end

    test "with current module" do
      assert Macro.expand_once(quote(do: __MODULE__), __ENV__) == __MODULE__
    end

    test "with main" do
      assert Macro.expand_once(quote(do: Elixir), __ENV__) == Elixir
    end

    test "with simple alias" do
      assert Macro.expand_once(quote(do: Foo), __ENV__) == Foo
    end

    test "with current module plus alias" do
      assert Macro.expand_once(quote(do: __MODULE__.Foo), __ENV__) == __MODULE__.Foo
    end

    test "with main plus alias" do
      assert Macro.expand_once(quote(do: Elixir.Foo), __ENV__) == Foo
    end

    test "with custom alias" do
      alias Foo, as: Bar
      assert Macro.expand_once(quote(do: Bar.Baz), __ENV__) == Foo.Baz
    end

    test "with main plus custom alias" do
      alias Foo, as: Bar, warn: false
      assert Macro.expand_once(quote(do: Elixir.Bar.Baz), __ENV__) == Elixir.Bar.Baz
    end

    test "with call in alias" do
      assert Macro.expand_once(quote(do: Foo.bar().Baz), __ENV__) == quote(do: Foo.bar().Baz)
    end

    test "env" do
      env = %{__ENV__ | line: 0}
      assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
      assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
      assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
    end

    defmacro local_macro() do
      :local_macro
    end

    test "local macro" do
      assert Macro.expand_once(quote(do: local_macro), __ENV__) == :local_macro
    end

    test "checks vars" do
      local_macro = 1
      assert local_macro == 1
      expr = {:local_macro, [], nil}
      assert Macro.expand_once(expr, __ENV__) == expr
    end

    defp expand_once_and_clean(quoted, env) do
      cleaner = &Keyword.drop(&1, [:counter])

      quoted
      |> Macro.expand_once(env)
      |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
    end

    test "with imported macro" do
      temp_var = {:x, [], Kernel}

      quoted =
        quote context: Kernel do
          case 1 do
            unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
            unquote(temp_var) -> unquote(temp_var)
          end
        end

      assert expand_once_and_clean(quote(do: 1 || false), __ENV__) == quoted
    end

    test "with require macro" do
      temp_var = {:x, [], Kernel}

      quoted =
        quote context: Kernel do
          case 1 do
            unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
            unquote(temp_var) -> unquote(temp_var)
          end
        end

      assert expand_once_and_clean(quote(do: Kernel.||(1, false)), __ENV__) == quoted
    end

    test "with not expandable expression" do
      expr = quote(do: other(1, 2, 3))
      assert Macro.expand_once(expr, __ENV__) == expr
    end

    test "does not expand module attributes" do
      message =
        "could not call get_attribute with argument #{inspect(__MODULE__)} " <>
          "because the module is already compiled"

      assert_raise ArgumentError, message, fn ->
        Macro.expand_once(quote(do: @foo), __ENV__)
      end
    end
  end

  defp expand_and_clean(quoted, env) do
    cleaner = &Keyword.drop(&1, [:counter])

    quoted
    |> Macro.expand(env)
    |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
  end

  test "expand/2" do
    temp_var = {:x, [], Kernel}

    quoted =
      quote context: Kernel do
        case 1 do
          unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
          unquote(temp_var) -> unquote(temp_var)
        end
      end

    assert expand_and_clean(quote(do: oror(1, false)), __ENV__) == quoted
  end

  test "var/2" do
    assert Macro.var(:foo, nil) == {:foo, [], nil}
    assert Macro.var(:foo, Other) == {:foo, [], Other}
  end

  describe "to_string/1" do
    test "variable" do
      assert Macro.to_string(quote(do: foo)) == "foo"
    end

    test "local call" do
      assert Macro.to_string(quote(do: foo(1, 2, 3))) == "foo(1, 2, 3)"
      assert Macro.to_string(quote(do: foo([1, 2, 3]))) == "foo([1, 2, 3])"
    end

    test "remote call" do
      assert Macro.to_string(quote(do: foo.bar(1, 2, 3))) == "foo.bar(1, 2, 3)"
      assert Macro.to_string(quote(do: foo.bar([1, 2, 3]))) == "foo.bar([1, 2, 3])"

      quoted =
        quote do
          (foo do
             :ok
           end).bar([1, 2, 3])
        end

      assert Macro.to_string(quoted) == "(foo() do\n  :ok\nend).bar([1, 2, 3])"
    end

    test "atom remote call" do
      assert Macro.to_string(quote(do: :foo.bar(1, 2, 3))) == ":foo.bar(1, 2, 3)"
    end

    test "remote and fun call" do
      assert Macro.to_string(quote(do: foo.bar.(1, 2, 3))) == "foo.bar().(1, 2, 3)"
      assert Macro.to_string(quote(do: foo.bar.([1, 2, 3]))) == "foo.bar().([1, 2, 3])"
    end

    test "unusual remote atom fun call" do
      assert Macro.to_string(quote(do: Foo."42"())) == ~s/Foo."42"()/
      assert Macro.to_string(quote(do: Foo."Bar"())) == ~s/Foo."Bar"()/
      assert Macro.to_string(quote(do: Foo."bar baz"()."")) == ~s/Foo."bar baz"().""()/
      assert Macro.to_string(quote(do: Foo."%{}"())) == ~s/Foo."%{}"()/
      assert Macro.to_string(quote(do: Foo."..."())) == ~s/Foo."..."()/
    end

    test "atom fun call" do
      assert Macro.to_string(quote(do: :foo.(1, 2, 3))) == ":foo.(1, 2, 3)"
    end

    test "aliases call" do
      assert Macro.to_string(quote(do: Foo.Bar.baz(1, 2, 3))) == "Foo.Bar.baz(1, 2, 3)"
      assert Macro.to_string(quote(do: Foo.Bar.baz([1, 2, 3]))) == "Foo.Bar.baz([1, 2, 3])"
      assert Macro.to_string(quote(do: Foo.bar(<<>>, []))) == "Foo.bar(<<>>, [])"
    end

    test "keyword call" do
      assert Macro.to_string(quote(do: Foo.bar(foo: :bar))) == "Foo.bar(foo: :bar)"
      assert Macro.to_string(quote(do: Foo.bar("Elixir.Foo": :bar))) == "Foo.bar([{Foo, :bar}])"
    end

    test "sigil call" do
      assert Macro.to_string(quote(do: ~r"123")) == ~s/~r"123"/
      assert Macro.to_string(quote(do: ~r"123"u)) == ~s/~r"123"u/
      assert Macro.to_string(quote(do: ~r"\n123")) == ~s/~r"\\\\n123"/

      assert Macro.to_string(quote(do: ~r"1#{two}3")) == ~S/~r"1#{two}3"/
      assert Macro.to_string(quote(do: ~r"1#{two}3"u)) == ~S/~r"1#{two}3"u/

      assert Macro.to_string(quote(do: ~R"123")) == ~s/~R"123"/
      assert Macro.to_string(quote(do: ~R"123"u)) == ~s/~R"123"u/
      assert Macro.to_string(quote(do: ~R"\n123")) == ~s/~R"\\\\n123"/
    end

    test "tuple call" do
      assert Macro.to_string(quote(do: alias(Foo.{Bar, Baz, Bong}))) ==
               "alias(Foo.{Bar, Baz, Bong})"

      assert Macro.to_string(quote(do: foo(Foo.{}))) == "foo(Foo.{})"
    end

    test "arrow" do
      assert Macro.to_string(quote(do: foo(1, (2 -> 3)))) == "foo(1, (2 -> 3))"
    end

    test "block" do
      quoted =
        quote do
          1
          2

          (
            :foo
            :bar
          )

          3
        end

      expected = """
      (
        1
        2
        (
          :foo
          :bar
        )
        3
      )
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "not in" do
      assert Macro.to_string(quote(do: false not in [])) == "false not in []"
    end

    test "if else" do
      expected = """
      if(foo) do
        bar
      else
        baz
      end
      """

      assert Macro.to_string(quote(do: if(foo, do: bar, else: baz))) <> "\n" == expected
    end

    test "case" do
      quoted =
        quote do
          case foo do
            true ->
              0

            false ->
              1
              2
          end
        end

      expected = """
      case(foo) do
        true ->
          0
        false ->
          1
          2
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "fn" do
      assert Macro.to_string(quote(do: fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
      assert Macro.to_string(quote(do: fn x -> x + 1 end)) == "fn x -> x + 1 end"

      quoted =
        quote do
          fn x ->
            y = x + 1
            y
          end
        end

      expected = """
      fn x ->
        y = x + 1
        y
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected

      quoted =
        quote do
          fn
            x ->
              y = x + 1
              y

            z ->
              z
          end
        end

      expected = """
      fn
        x ->
          y = x + 1
          y
        z ->
          z
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected

      assert Macro.to_string(quote(do: (fn x -> x end).(1))) == "(fn x -> x end).(1)"

      quoted =
        quote do
          (fn
             %{} -> :map
             _ -> :other
           end).(1)
        end

      expected = """
      (fn
        %{} ->
          :map
        _ ->
          :other
      end).(1)
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "range" do
      assert Macro.to_string(quote(do: unquote(-1..+2))) == "-1..2"
      assert Macro.to_string(quote(do: Foo.integer()..3)) == "Foo.integer()..3"
    end

    test "when" do
      assert Macro.to_string(quote(do: (() -> x))) == "(() -> x)"
      assert Macro.to_string(quote(do: (x when y -> z))) == "(x when y -> z)"
      assert Macro.to_string(quote(do: (x, y when z -> w))) == "((x, y) when z -> w)"
      assert Macro.to_string(quote(do: (x, y when z -> w))) == "((x, y) when z -> w)"
    end

    test "nested" do
      quoted =
        quote do
          defmodule Foo do
            def foo do
              1 + 1
            end
          end
        end

      expected = """
      defmodule(Foo) do
        def(foo) do
          1 + 1
        end
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "operator precedence" do
      assert Macro.to_string(quote(do: (1 + 2) * (3 - 4))) == "(1 + 2) * (3 - 4)"
      assert Macro.to_string(quote(do: (1 + 2) * 3 - 4)) == "(1 + 2) * 3 - 4"
      assert Macro.to_string(quote(do: 1 + 2 + 3 == "(1 + 2 + 3)"))
      assert Macro.to_string(quote(do: 1 + 2 - 3 == "(1 + 2 - 3)"))
    end

    test "capture operator" do
      assert Macro.to_string(quote(do: &foo/0)) == "&foo/0"
      assert Macro.to_string(quote(do: &Foo.foo/0)) == "&Foo.foo/0"
      assert Macro.to_string(quote(do: &(&1 + &2))) == "&(&1 + &2)"
      assert Macro.to_string(quote(do: & &1)) == "&(&1)"
      assert Macro.to_string(quote(do: & &1.(:x))) == "&(&1.(:x))"
      assert Macro.to_string(quote(do: (& &1).(:x))) == "(&(&1)).(:x)"
    end

    test "containers" do
      assert Macro.to_string(quote(do: {})) == "{}"
      assert Macro.to_string(quote(do: [])) == "[]"
      assert Macro.to_string(quote(do: {1, 2, 3})) == "{1, 2, 3}"
      assert Macro.to_string(quote(do: [1, 2, 3])) == "[1, 2, 3]"
      assert Macro.to_string(quote(do: ["Elixir.Foo": :bar])) == "[{Foo, :bar}]"
      assert Macro.to_string(quote(do: %{})) == "%{}"
      assert Macro.to_string(quote(do: %{:foo => :bar})) == "%{foo: :bar}"
      assert Macro.to_string(quote(do: %{:"Elixir.Foo" => :bar})) == "%{Foo => :bar}"
      assert Macro.to_string(quote(do: %{{1, 2} => [1, 2, 3]})) == "%{{1, 2} => [1, 2, 3]}"
      assert Macro.to_string(quote(do: %{map | "a" => "b"})) == "%{map | \"a\" => \"b\"}"
      assert Macro.to_string(quote(do: [1, 2, 3])) == "[1, 2, 3]"
    end

    test "struct" do
      assert Macro.to_string(quote(do: %Test{})) == "%Test{}"
      assert Macro.to_string(quote(do: %Test{foo: 1, bar: 1})) == "%Test{foo: 1, bar: 1}"
      assert Macro.to_string(quote(do: %Test{struct | foo: 2})) == "%Test{struct | foo: 2}"
      assert Macro.to_string(quote(do: %Test{} + 1)) == "%Test{} + 1"
    end

    test "binary operators" do
      assert Macro.to_string(quote(do: 1 + 2)) == "1 + 2"
      assert Macro.to_string(quote(do: [1, 2 | 3])) == "[1, 2 | 3]"
      assert Macro.to_string(quote(do: [h | t] = [1, 2, 3])) == "[h | t] = [1, 2, 3]"
      assert Macro.to_string(quote(do: (x ++ y) ++ z)) == "(x ++ y) ++ z"
    end

    test "unary operators" do
      assert Macro.to_string(quote(do: not 1)) == "not(1)"
      assert Macro.to_string(quote(do: not foo)) == "not(foo)"
      assert Macro.to_string(quote(do: -1)) == "-1"
      assert Macro.to_string(quote(do: +(+1))) == "+(+1)"
      assert Macro.to_string(quote(do: !(foo > bar))) == "!(foo > bar)"
      assert Macro.to_string(quote(do: @foo(bar))) == "@foo(bar)"
      assert Macro.to_string(quote(do: identity(&1))) == "identity(&1)"
    end

    test "access" do
      assert Macro.to_string(quote(do: a[b])) == "a[b]"
      assert Macro.to_string(quote(do: a[1 + 2])) == "a[1 + 2]"
      assert Macro.to_string(quote(do: (a || [a: 1])[:a])) == "(a || [a: 1])[:a]"
      assert Macro.to_string(quote(do: Map.put(%{}, :a, 1)[:a])) == "Map.put(%{}, :a, 1)[:a]"
    end

    test "keyword list" do
      assert Macro.to_string(quote(do: [a: a, b: b])) == "[a: a, b: b]"
      assert Macro.to_string(quote(do: [a: 1, b: 1 + 2])) == "[a: 1, b: 1 + 2]"
      assert Macro.to_string(quote(do: ["a.b": 1, c: 1 + 2])) == "[\"a.b\": 1, c: 1 + 2]"
    end

    test "interpolation" do
      assert Macro.to_string(quote(do: "foo#{bar}baz")) == ~S["foo#{bar}baz"]
    end

    test "bit syntax" do
      ast = quote(do: <<1::8*4>>)
      assert Macro.to_string(ast) == "<<1::8*4>>"

      ast = quote(do: @type(foo :: <<_::8, _::_*4>>))
      assert Macro.to_string(ast) == "@type(foo :: <<_::8, _::_*4>>)"

      ast = quote(do: <<69 - 4::bits-size(8 - 4)-unit(1), 65>>)
      assert Macro.to_string(ast) == "<<69 - 4::bits-size(8 - 4)-unit(1), 65>>"

      ast = quote(do: <<(<<65>>), 65>>)
      assert Macro.to_string(ast) == "<<(<<65>>), 65>>"

      ast = quote(do: <<65, (<<65>>)>>)
      assert Macro.to_string(ast) == "<<65, (<<65>>)>>"

      ast = quote(do: for(<<(a::4 <- <<1, 2>>)>>, do: a))
      assert Macro.to_string(ast) == "for(<<(a :: 4 <- <<1, 2>>)>>) do\n  a\nend"
    end

    test "charlist" do
      assert Macro.to_string(quote(do: [])) == "[]"
      assert Macro.to_string(quote(do: 'abc')) == "'abc'"
    end

    test "last arg keyword list" do
      assert Macro.to_string(quote(do: foo([]))) == "foo([])"
      assert Macro.to_string(quote(do: foo(x: y))) == "foo(x: y)"
      assert Macro.to_string(quote(do: foo(x: 1 + 2))) == "foo(x: 1 + 2)"
      assert Macro.to_string(quote(do: foo(x: y, p: q))) == "foo(x: y, p: q)"
      assert Macro.to_string(quote(do: foo(a, x: y, p: q))) == "foo(a, x: y, p: q)"

      assert Macro.to_string(quote(do: {[]})) == "{[]}"
      assert Macro.to_string(quote(do: {[a: b]})) == "{[a: b]}"
      assert Macro.to_string(quote(do: {x, a: b})) == "{x, [a: b]}"
      assert Macro.to_string(quote(do: foo(else: a))) == "foo(else: a)"
      assert Macro.to_string(quote(do: foo(catch: a))) == "foo(catch: a)"
    end

    test "with fun" do
      assert Macro.to_string(quote(do: foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
               ":foo(:1:, :2:, :3:):"

      assert Macro.to_string(quote(do: Bar.foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
               "::Bar:.foo(:1:, :2:, :3:):"
    end
  end

  test "validate/1" do
    ref = make_ref()

    assert Macro.validate(1) == :ok
    assert Macro.validate(1.0) == :ok
    assert Macro.validate(:foo) == :ok
    assert Macro.validate("bar") == :ok
    assert Macro.validate(self()) == :ok
    assert Macro.validate({1, 2}) == :ok
    assert Macro.validate({:foo, [], :baz}) == :ok
    assert Macro.validate({:foo, [], []}) == :ok
    assert Macro.validate([1, 2, 3]) == :ok

    assert Macro.validate(<<0::4>>) == {:error, <<0::4>>}
    assert Macro.validate(ref) == {:error, ref}
    assert Macro.validate({1, ref}) == {:error, ref}
    assert Macro.validate({ref, 2}) == {:error, ref}
    assert Macro.validate([1, ref, 3]) == {:error, ref}
    assert Macro.validate({:foo, [], 0}) == {:error, {:foo, [], 0}}
    assert Macro.validate({:foo, 0, []}) == {:error, {:foo, 0, []}}
  end

  test "decompose_call/1" do
    assert Macro.decompose_call(quote(do: foo)) == {:foo, []}
    assert Macro.decompose_call(quote(do: foo())) == {:foo, []}
    assert Macro.decompose_call(quote(do: foo(1, 2, 3))) == {:foo, [1, 2, 3]}

    assert Macro.decompose_call(quote(do: M.N.foo(1, 2, 3))) ==
             {{:__aliases__, [alias: false], [:M, :N]}, :foo, [1, 2, 3]}

    assert Macro.decompose_call(quote(do: :foo.foo(1, 2, 3))) == {:foo, :foo, [1, 2, 3]}
    assert Macro.decompose_call(quote(do: 1.(1, 2, 3))) == :error
    assert Macro.decompose_call(quote(do: "some string")) == :error
  end

  describe "env" do
    test "stacktrace" do
      env = %{__ENV__ | file: "foo", line: 12}

      assert Macro.Env.stacktrace(env) ==
               [{__MODULE__, :"test env stacktrace", 1, [file: "foo", line: 12]}]

      env = %{env | function: nil}
      assert Macro.Env.stacktrace(env) == [{__MODULE__, :__MODULE__, 0, [file: "foo", line: 12]}]

      env = %{env | module: nil}

      assert Macro.Env.stacktrace(env) ==
               [{:elixir_compiler, :__FILE__, 1, [file: "foo", line: 12]}]
    end

    test "context modules" do
      defmodule Foo.Bar do
        assert __MODULE__ in __ENV__.context_modules
      end
    end
  end

  ## pipe/unpipe

  test "pipe/3" do
    assert Macro.pipe(1, quote(do: foo), 0) == quote(do: foo(1))
    assert Macro.pipe(1, quote(do: foo(2)), 0) == quote(do: foo(1, 2))
    assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
    assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))

    assert_raise ArgumentError, ~r"cannot pipe 1 into 2", fn ->
      Macro.pipe(1, 2, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into {:ok}", fn ->
      Macro.pipe(1, {:ok}, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into 1 \+ 1", fn ->
      Macro.pipe(1, quote(do: 1 + 1), 0) == quote(do: foo(1))
    end

    # TODO: restore this test when we drop unary operator support in pipes
    # assert_raise ArgumentError, ~r"cannot pipe 1 into \+1", fn ->
    #   Macro.pipe(1, quote(do: + 1), 0)
    # end

    assert_raise ArgumentError, ~r"cannot pipe Macro into Env", fn ->
      Macro.pipe(Macro, quote(do: Env), 0)
    end

    message = ~r"cannot pipe :foo into an anonymous function without calling"

    assert_raise ArgumentError, message, fn ->
      Macro.pipe(:foo, quote(do: fn x -> x end), 0)
    end
  end

  test "unpipe/1" do
    assert Macro.unpipe(quote(do: foo)) == quote(do: [{foo, 0}])
    assert Macro.unpipe(quote(do: foo |> bar)) == quote(do: [{foo, 0}, {bar, 0}])
    assert Macro.unpipe(quote(do: foo |> bar |> baz)) == quote(do: [{foo, 0}, {bar, 0}, {baz, 0}])
  end

  ## traverse/pre/postwalk

  test "traverse/4" do
    assert traverse({:foo, [], nil}) == [{:foo, [], nil}, {:foo, [], nil}]

    assert traverse({:foo, [], [1, 2, 3]}) ==
             [{:foo, [], [1, 2, 3]}, 1, 1, 2, 2, 3, 3, {:foo, [], [1, 2, 3]}]

    assert traverse({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
             [
               {{:., [], [:foo, :bar]}, [], [1, 2, 3]},
               {:., [], [:foo, :bar]},
               :foo,
               :foo,
               :bar,
               :bar,
               {:., [], [:foo, :bar]},
               1,
               1,
               2,
               2,
               3,
               3,
               {{:., [], [:foo, :bar]}, [], [1, 2, 3]}
             ]

    assert traverse({[1, 2, 3], [4, 5, 6]}) ==
             [
               {[1, 2, 3], [4, 5, 6]},
               [1, 2, 3],
               1,
               1,
               2,
               2,
               3,
               3,
               [1, 2, 3],
               [4, 5, 6],
               4,
               4,
               5,
               5,
               6,
               6,
               [4, 5, 6],
               {[1, 2, 3], [4, 5, 6]}
             ]
  end

  defp traverse(ast) do
    Macro.traverse(ast, [], &{&1, [&1 | &2]}, &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
  end

  test "prewalk/3" do
    assert prewalk({:foo, [], nil}) == [{:foo, [], nil}]

    assert prewalk({:foo, [], [1, 2, 3]}) == [{:foo, [], [1, 2, 3]}, 1, 2, 3]

    assert prewalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
             [
               {{:., [], [:foo, :bar]}, [], [1, 2, 3]},
               {:., [], [:foo, :bar]},
               :foo,
               :bar,
               1,
               2,
               3
             ]

    assert prewalk({[1, 2, 3], [4, 5, 6]}) ==
             [{[1, 2, 3], [4, 5, 6]}, [1, 2, 3], 1, 2, 3, [4, 5, 6], 4, 5, 6]
  end

  defp prewalk(ast) do
    Macro.prewalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
  end

  test "postwalk/3" do
    assert postwalk({:foo, [], nil}) == [{:foo, [], nil}]

    assert postwalk({:foo, [], [1, 2, 3]}) == [1, 2, 3, {:foo, [], [1, 2, 3]}]

    assert postwalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
             [
               :foo,
               :bar,
               {:., [], [:foo, :bar]},
               1,
               2,
               3,
               {{:., [], [:foo, :bar]}, [], [1, 2, 3]}
             ]

    assert postwalk({[1, 2, 3], [4, 5, 6]}) ==
             [1, 2, 3, [1, 2, 3], 4, 5, 6, [4, 5, 6], {[1, 2, 3], [4, 5, 6]}]
  end

  test "generate_arguments/2" do
    assert Macro.generate_arguments(0, __MODULE__) == []
    assert Macro.generate_arguments(1, __MODULE__) == [{:var1, [], __MODULE__}]
    assert Macro.generate_arguments(4, __MODULE__) |> length == 4
  end

  defp postwalk(ast) do
    Macro.postwalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
  end

  test "underscore/1" do
    assert Macro.underscore("foo") == "foo"
    assert Macro.underscore("foo_bar") == "foo_bar"
    assert Macro.underscore("Foo") == "foo"
    assert Macro.underscore("FooBar") == "foo_bar"
    assert Macro.underscore("FOOBar") == "foo_bar"
    assert Macro.underscore("FooBAR") == "foo_bar"
    assert Macro.underscore("FOO_BAR") == "foo_bar"
    assert Macro.underscore("FoBaZa") == "fo_ba_za"
    assert Macro.underscore("Foo10") == "foo10"
    assert Macro.underscore("10Foo") == "10_foo"
    assert Macro.underscore("FooBar10") == "foo_bar10"
    assert Macro.underscore("Foo10Bar") == "foo10_bar"
    assert Macro.underscore("Foo.Bar") == "foo/bar"
    assert Macro.underscore(Foo.Bar) == "foo/bar"
    assert Macro.underscore("API.V1.User") == "api/v1/user"
    assert Macro.underscore("") == ""
  end

  test "camelize/1" do
    assert Macro.camelize("Foo") == "Foo"
    assert Macro.camelize("FooBar") == "FooBar"
    assert Macro.camelize("foo") == "Foo"
    assert Macro.camelize("foo_bar") == "FooBar"
    assert Macro.camelize("foo_") == "Foo"
    assert Macro.camelize("_foo") == "Foo"
    assert Macro.camelize("foo10") == "Foo10"
    assert Macro.camelize("_10foo") == "10foo"
    assert Macro.camelize("foo_10") == "Foo10"
    assert Macro.camelize("foo__10") == "Foo10"
    assert Macro.camelize("foo__bar") == "FooBar"
    assert Macro.camelize("foo/bar") == "Foo.Bar"
    assert Macro.camelize("Foo.Bar") == "Foo.Bar"
    assert Macro.camelize("FOO_BAR") == "FOO_BAR"
    assert Macro.camelize("FOO.BAR") == "FOO.BAR"
    assert Macro.camelize("") == ""
  end
end

Code.require_file("test_helper.exs", __DIR__)

defmodule Macro.ExternalTest do
  defmacro external do
    line = 18
    file = __ENV__.file
    ^line = __CALLER__.line
    ^file = __CALLER__.file
    ^line = Macro.Env.location(__CALLER__)[:line]
    ^file = Macro.Env.location(__CALLER__)[:file]
  end

  defmacro oror(left, right) do
    quote(do: unquote(left) || unquote(right))
  end
end

defmodule MacroTest do
  use ExUnit.Case, async: true

  # Changing the lines above will make compilation
  # fail since we are asserting on the caller lines
  import Macro.ExternalTest

  describe "escape/2" do
    test "handles tuples with size different than two" do
      assert Macro.escape({:a}) == {:{}, [], [:a]}
      assert Macro.escape({:a, :b, :c}) == {:{}, [], [:a, :b, :c]}
      assert Macro.escape({:a, {1, 2, 3}, :c}) == {:{}, [], [:a, {:{}, [], [1, 2, 3]}, :c]}
    end

    test "simply returns tuples with size equal to two" do
      assert Macro.escape({:a, :b}) == {:a, :b}
    end

    test "simply returns any other structure" do
      assert Macro.escape([1, 2, 3]) == [1, 2, 3]
    end

    test "handles maps" do
      assert Macro.escape(%{a: 1}) == {:%{}, [], [a: 1]}
    end

    test "handles bitstring" do
      assert {:<<>>, [], args} = Macro.escape(<<300::12>>)
      assert [{:::, [], [1, {:size, [], [4]}]}, {:::, [], [",", {:binary, [], []}]}] = args
    end

    test "works recursively" do
      assert Macro.escape([1, {:a, :b, :c}, 3]) == [1, {:{}, [], [:a, :b, :c]}, 3]
    end

    test "with improper lists" do
      assert Macro.escape([1 | 2]) == [{:|, [], [1, 2]}]
      assert Macro.escape([1, 2 | 3]) == [1, {:|, [], [2, 3]}]
    end

    test "with unquote" do
      contents = quote(unquote: false, do: unquote(1))
      assert Macro.escape(contents, unquote: true) == 1

      contents = quote(unquote: false, do: unquote(x))
      assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest}
    end

    defp eval_escaped(contents) do
      {eval, []} = Code.eval_quoted(Macro.escape(contents, unquote: true))
      eval
    end

    test "with remote unquote" do
      contents = quote(unquote: false, do: Kernel.unquote(:is_atom)(:ok))
      assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
    end

    test "with nested unquote" do
      contents =
        quote do
          quote(do: unquote(x))
        end

      assert eval_escaped(contents) == quote(do: quote(do: unquote(x)))
    end

    test "with alias or no arguments remote unquote" do
      contents = quote(unquote: false, do: Kernel.unquote(:self))
      assert eval_escaped(contents) == quote(do: Kernel.self())

      contents = quote(unquote: false, do: x.unquote(Foo))
      assert eval_escaped(contents) == quote(do: x.unquote(Foo))
    end

    test "with splicing" do
      contents = quote(unquote: false, do: [1, 2, 3, 4, 5])
      assert Macro.escape(contents, unquote: true) == [1, 2, 3, 4, 5]

      contents = quote(unquote: false, do: [1, 2, unquote_splicing([3, 4, 5])])
      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents = quote(unquote: false, do: [unquote_splicing([1, 2, 3]), 4, 5])
      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [unquote_splicing([1, 2, 3]), unquote_splicing([4, 5])])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]), 5])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]

      contents =
        quote(unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]) | [5]])

      assert eval_escaped(contents) == [1, 2, 3, 4, 5]
    end
  end

  describe "expand_once/2" do
    test "with external macro" do
      assert {:||, _, [1, false]} = Macro.expand_once(quote(do: oror(1, false)), __ENV__)
    end

    test "with raw atom" do
      assert Macro.expand_once(quote(do: :foo), __ENV__) == :foo
    end

    test "with current module" do
      assert Macro.expand_once(quote(do: __MODULE__), __ENV__) == __MODULE__
    end

    test "with main" do
      assert Macro.expand_once(quote(do: Elixir), __ENV__) == Elixir
    end

    test "with simple alias" do
      assert Macro.expand_once(quote(do: Foo), __ENV__) == Foo
    end

    test "with current module plus alias" do
      assert Macro.expand_once(quote(do: __MODULE__.Foo), __ENV__) == __MODULE__.Foo
    end

    test "with main plus alias" do
      assert Macro.expand_once(quote(do: Elixir.Foo), __ENV__) == Foo
    end

    test "with custom alias" do
      alias Foo, as: Bar
      assert Macro.expand_once(quote(do: Bar.Baz), __ENV__) == Foo.Baz
    end

    test "with main plus custom alias" do
      alias Foo, as: Bar, warn: false
      assert Macro.expand_once(quote(do: Elixir.Bar.Baz), __ENV__) == Elixir.Bar.Baz
    end

    test "with call in alias" do
      assert Macro.expand_once(quote(do: Foo.bar().Baz), __ENV__) == quote(do: Foo.bar().Baz)
    end

    test "env" do
      env = %{__ENV__ | line: 0}
      assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
      assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
      assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
    end

    defmacro local_macro() do
      :local_macro
    end

    test "local macro" do
      assert Macro.expand_once(quote(do: local_macro), __ENV__) == :local_macro
    end

    test "checks vars" do
      local_macro = 1
      assert local_macro == 1
      expr = {:local_macro, [], nil}
      assert Macro.expand_once(expr, __ENV__) == expr
    end

    defp expand_once_and_clean(quoted, env) do
      cleaner = &Keyword.drop(&1, [:counter])

      quoted
      |> Macro.expand_once(env)
      |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
    end

    test "with imported macro" do
      temp_var = {:x, [], Kernel}

      quoted =
        quote context: Kernel do
          case 1 do
            unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
            unquote(temp_var) -> unquote(temp_var)
          end
        end

      assert expand_once_and_clean(quote(do: 1 || false), __ENV__) == quoted
    end

    test "with require macro" do
      temp_var = {:x, [], Kernel}

      quoted =
        quote context: Kernel do
          case 1 do
            unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
            unquote(temp_var) -> unquote(temp_var)
          end
        end

      assert expand_once_and_clean(quote(do: Kernel.||(1, false)), __ENV__) == quoted
    end

    test "with not expandable expression" do
      expr = quote(do: other(1, 2, 3))
      assert Macro.expand_once(expr, __ENV__) == expr
    end

    test "does not expand module attributes" do
      message =
        "could not call get_attribute with argument #{inspect(__MODULE__)} " <>
          "because the module is already compiled"

      assert_raise ArgumentError, message, fn ->
        Macro.expand_once(quote(do: @foo), __ENV__)
      end
    end
  end

  defp expand_and_clean(quoted, env) do
    cleaner = &Keyword.drop(&1, [:counter])

    quoted
    |> Macro.expand(env)
    |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
  end

  test "expand/2" do
    temp_var = {:x, [], Kernel}

    quoted =
      quote context: Kernel do
        case 1 do
          unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
          unquote(temp_var) -> unquote(temp_var)
        end
      end

    assert expand_and_clean(quote(do: oror(1, false)), __ENV__) == quoted
  end

  test "var/2" do
    assert Macro.var(:foo, nil) == {:foo, [], nil}
    assert Macro.var(:foo, Other) == {:foo, [], Other}
  end

  describe "to_string/1" do
    test "variable" do
      assert Macro.to_string(quote(do: foo)) == "foo"
    end

    test "local call" do
      assert Macro.to_string(quote(do: foo(1, 2, 3))) == "foo(1, 2, 3)"
      assert Macro.to_string(quote(do: foo([1, 2, 3]))) == "foo([1, 2, 3])"
    end

    test "remote call" do
      assert Macro.to_string(quote(do: foo.bar(1, 2, 3))) == "foo.bar(1, 2, 3)"
      assert Macro.to_string(quote(do: foo.bar([1, 2, 3]))) == "foo.bar([1, 2, 3])"

      quoted =
        quote do
          (foo do
             :ok
           end).bar([1, 2, 3])
        end

      assert Macro.to_string(quoted) == "(foo() do\n  :ok\nend).bar([1, 2, 3])"
    end

    test "atom remote call" do
      assert Macro.to_string(quote(do: :foo.bar(1, 2, 3))) == ":foo.bar(1, 2, 3)"
    end

    test "remote and fun call" do
      assert Macro.to_string(quote(do: foo.bar.(1, 2, 3))) == "foo.bar().(1, 2, 3)"
      assert Macro.to_string(quote(do: foo.bar.([1, 2, 3]))) == "foo.bar().([1, 2, 3])"
    end

    test "unusual remote atom fun call" do
      assert Macro.to_string(quote(do: Foo."42"())) == ~s/Foo."42"()/
      assert Macro.to_string(quote(do: Foo."Bar"())) == ~s/Foo."Bar"()/
      assert Macro.to_string(quote(do: Foo."bar baz"()."")) == ~s/Foo."bar baz"().""()/
      assert Macro.to_string(quote(do: Foo."%{}"())) == ~s/Foo."%{}"()/
      assert Macro.to_string(quote(do: Foo."..."())) == ~s/Foo."..."()/
    end

    test "atom fun call" do
      assert Macro.to_string(quote(do: :foo.(1, 2, 3))) == ":foo.(1, 2, 3)"
    end

    test "aliases call" do
      assert Macro.to_string(quote(do: Foo.Bar.baz(1, 2, 3))) == "Foo.Bar.baz(1, 2, 3)"
      assert Macro.to_string(quote(do: Foo.Bar.baz([1, 2, 3]))) == "Foo.Bar.baz([1, 2, 3])"
      assert Macro.to_string(quote(do: Foo.bar(<<>>, []))) == "Foo.bar(<<>>, [])"
    end

    test "keyword call" do
      assert Macro.to_string(quote(do: Foo.bar(foo: :bar))) == "Foo.bar(foo: :bar)"
      assert Macro.to_string(quote(do: Foo.bar("Elixir.Foo": :bar))) == "Foo.bar([{Foo, :bar}])"
    end

    test "sigil call" do
      assert Macro.to_string(quote(do: ~r"123")) == ~s/~r"123"/
      assert Macro.to_string(quote(do: ~r"123"u)) == ~s/~r"123"u/
      assert Macro.to_string(quote(do: ~r"\n123")) == ~s/~r"\\\\n123"/

      assert Macro.to_string(quote(do: ~r"1#{two}3")) == ~S/~r"1#{two}3"/
      assert Macro.to_string(quote(do: ~r"1#{two}3"u)) == ~S/~r"1#{two}3"u/

      assert Macro.to_string(quote(do: ~R"123")) == ~s/~R"123"/
      assert Macro.to_string(quote(do: ~R"123"u)) == ~s/~R"123"u/
      assert Macro.to_string(quote(do: ~R"\n123")) == ~s/~R"\\\\n123"/
    end

    test "tuple call" do
      assert Macro.to_string(quote(do: alias(Foo.{Bar, Baz, Bong}))) ==
               "alias(Foo.{Bar, Baz, Bong})"

      assert Macro.to_string(quote(do: foo(Foo.{}))) == "foo(Foo.{})"
    end

    test "arrow" do
      assert Macro.to_string(quote(do: foo(1, (2 -> 3)))) == "foo(1, (2 -> 3))"
    end

    test "block" do
      quoted =
        quote do
          1
          2

          (
            :foo
            :bar
          )

          3
        end

      expected = """
      (
        1
        2
        (
          :foo
          :bar
        )
        3
      )
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "not in" do
      assert Macro.to_string(quote(do: false not in [])) == "false not in []"
    end

    test "if else" do
      expected = """
      if(foo) do
        bar
      else
        baz
      end
      """

      assert Macro.to_string(quote(do: if(foo, do: bar, else: baz))) <> "\n" == expected
    end

    test "case" do
      quoted =
        quote do
          case foo do
            true ->
              0

            false ->
              1
              2
          end
        end

      expected = """
      case(foo) do
        true ->
          0
        false ->
          1
          2
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "fn" do
      assert Macro.to_string(quote(do: fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
      assert Macro.to_string(quote(do: fn x -> x + 1 end)) == "fn x -> x + 1 end"

      quoted =
        quote do
          fn x ->
            y = x + 1
            y
          end
        end

      expected = """
      fn x ->
        y = x + 1
        y
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected

      quoted =
        quote do
          fn
            x ->
              y = x + 1
              y

            z ->
              z
          end
        end

      expected = """
      fn
        x ->
          y = x + 1
          y
        z ->
          z
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected

      assert Macro.to_string(quote(do: (fn x -> x end).(1))) == "(fn x -> x end).(1)"

      quoted =
        quote do
          (fn
             %{} -> :map
             _ -> :other
           end).(1)
        end

      expected = """
      (fn
        %{} ->
          :map
        _ ->
          :other
      end).(1)
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "range" do
      assert Macro.to_string(quote(do: unquote(-1..+2))) == "-1..2"
      assert Macro.to_string(quote(do: Foo.integer()..3)) == "Foo.integer()..3"
    end

    test "when" do
      assert Macro.to_string(quote(do: (() -> x))) == "(() -> x)"
      assert Macro.to_string(quote(do: (x when y -> z))) == "(x when y -> z)"
      assert Macro.to_string(quote(do: (x, y when z -> w))) == "((x, y) when z -> w)"
      assert Macro.to_string(quote(do: (x, y when z -> w))) == "((x, y) when z -> w)"
    end

    test "nested" do
      quoted =
        quote do
          defmodule Foo do
            def foo do
              1 + 1
            end
          end
        end

      expected = """
      defmodule(Foo) do
        def(foo) do
          1 + 1
        end
      end
      """

      assert Macro.to_string(quoted) <> "\n" == expected
    end

    test "operator precedence" do
      assert Macro.to_string(quote(do: (1 + 2) * (3 - 4))) == "(1 + 2) * (3 - 4)"
      assert Macro.to_string(quote(do: (1 + 2) * 3 - 4)) == "(1 + 2) * 3 - 4"
      assert Macro.to_string(quote(do: 1 + 2 + 3 == "(1 + 2 + 3)"))
      assert Macro.to_string(quote(do: 1 + 2 - 3 == "(1 + 2 - 3)"))
    end

    test "capture operator" do
      assert Macro.to_string(quote(do: &foo/0)) == "&foo/0"
      assert Macro.to_string(quote(do: &Foo.foo/0)) == "&Foo.foo/0"
      assert Macro.to_string(quote(do: &(&1 + &2))) == "&(&1 + &2)"
      assert Macro.to_string(quote(do: & &1)) == "&(&1)"
      assert Macro.to_string(quote(do: & &1.(:x))) == "&(&1.(:x))"
      assert Macro.to_string(quote(do: (& &1).(:x))) == "(&(&1)).(:x)"
    end

    test "containers" do
      assert Macro.to_string(quote(do: {})) == "{}"
      assert Macro.to_string(quote(do: [])) == "[]"
      assert Macro.to_string(quote(do: {1, 2, 3})) == "{1, 2, 3}"
      assert Macro.to_string(quote(do: [1, 2, 3])) == "[1, 2, 3]"
      assert Macro.to_string(quote(do: ["Elixir.Foo": :bar])) == "[{Foo, :bar}]"
      assert Macro.to_string(quote(do: %{})) == "%{}"
      assert Macro.to_string(quote(do: %{:foo => :bar})) == "%{foo: :bar}"
      assert Macro.to_string(quote(do: %{:"Elixir.Foo" => :bar})) == "%{Foo => :bar}"
      assert Macro.to_string(quote(do: %{{1, 2} => [1, 2, 3]})) == "%{{1, 2} => [1, 2, 3]}"
      assert Macro.to_string(quote(do: %{map | "a" => "b"})) == "%{map | \"a\" => \"b\"}"
      assert Macro.to_string(quote(do: [1, 2, 3])) == "[1, 2, 3]"
    end

    test "struct" do
      assert Macro.to_string(quote(do: %Test{})) == "%Test{}"
      assert Macro.to_string(quote(do: %Test{foo: 1, bar: 1})) == "%Test{foo: 1, bar: 1}"
      assert Macro.to_string(quote(do: %Test{struct | foo: 2})) == "%Test{struct | foo: 2}"
      assert Macro.to_string(quote(do: %Test{} + 1)) == "%Test{} + 1"
    end

    test "binary operators" do
      assert Macro.to_string(quote(do: 1 + 2)) == "1 + 2"
      assert Macro.to_string(quote(do: [1, 2 | 3])) == "[1, 2 | 3]"
      assert Macro.to_string(quote(do: [h | t] = [1, 2, 3])) == "[h | t] = [1, 2, 3]"
      assert Macro.to_string(quote(do: (x ++ y) ++ z)) == "(x ++ y) ++ z"
    end

    test "unary operators" do
      assert Macro.to_string(quote(do: not 1)) == "not(1)"
      assert Macro.to_string(quote(do: not foo)) == "not(foo)"
      assert Macro.to_string(quote(do: -1)) == "-1"
      assert Macro.to_string(quote(do: +(+1))) == "+(+1)"
      assert Macro.to_string(quote(do: !(foo > bar))) == "!(foo > bar)"
      assert Macro.to_string(quote(do: @foo(bar))) == "@foo(bar)"
      assert Macro.to_string(quote(do: identity(&1))) == "identity(&1)"
    end

    test "access" do
      assert Macro.to_string(quote(do: a[b])) == "a[b]"
      assert Macro.to_string(quote(do: a[1 + 2])) == "a[1 + 2]"
      assert Macro.to_string(quote(do: (a || [a: 1])[:a])) == "(a || [a: 1])[:a]"
      assert Macro.to_string(quote(do: Map.put(%{}, :a, 1)[:a])) == "Map.put(%{}, :a, 1)[:a]"
    end

    test "keyword list" do
      assert Macro.to_string(quote(do: [a: a, b: b])) == "[a: a, b: b]"
      assert Macro.to_string(quote(do: [a: 1, b: 1 + 2])) == "[a: 1, b: 1 + 2]"
      assert Macro.to_string(quote(do: ["a.b": 1, c: 1 + 2])) == "[\"a.b\": 1, c: 1 + 2]"
    end

    test "interpolation" do
      assert Macro.to_string(quote(do: "foo#{bar}baz")) == ~S["foo#{bar}baz"]
    end

    test "bit syntax" do
      ast = quote(do: <<1::8*4>>)
      assert Macro.to_string(ast) == "<<1::8*4>>"

      ast = quote(do: @type(foo :: <<_::8, _::_*4>>))
      assert Macro.to_string(ast) == "@type(foo :: <<_::8, _::_*4>>)"

      ast = quote(do: <<69 - 4::bits-size(8 - 4)-unit(1), 65>>)
      assert Macro.to_string(ast) == "<<69 - 4::bits-size(8 - 4)-unit(1), 65>>"

      ast = quote(do: <<(<<65>>), 65>>)
      assert Macro.to_string(ast) == "<<(<<65>>), 65>>"

      ast = quote(do: <<65, (<<65>>)>>)
      assert Macro.to_string(ast) == "<<65, (<<65>>)>>"

      ast = quote(do: for(<<(a::4 <- <<1, 2>>)>>, do: a))
      assert Macro.to_string(ast) == "for(<<(a :: 4 <- <<1, 2>>)>>) do\n  a\nend"
    end

    test "charlist" do
      assert Macro.to_string(quote(do: [])) == "[]"
      assert Macro.to_string(quote(do: 'abc')) == "'abc'"
    end

    test "last arg keyword list" do
      assert Macro.to_string(quote(do: foo([]))) == "foo([])"
      assert Macro.to_string(quote(do: foo(x: y))) == "foo(x: y)"
      assert Macro.to_string(quote(do: foo(x: 1 + 2))) == "foo(x: 1 + 2)"
      assert Macro.to_string(quote(do: foo(x: y, p: q))) == "foo(x: y, p: q)"
      assert Macro.to_string(quote(do: foo(a, x: y, p: q))) == "foo(a, x: y, p: q)"

      assert Macro.to_string(quote(do: {[]})) == "{[]}"
      assert Macro.to_string(quote(do: {[a: b]})) == "{[a: b]}"
      assert Macro.to_string(quote(do: {x, a: b})) == "{x, [a: b]}"
      assert Macro.to_string(quote(do: foo(else: a))) == "foo(else: a)"
      assert Macro.to_string(quote(do: foo(catch: a))) == "foo(catch: a)"
    end

    test "with fun" do
      assert Macro.to_string(quote(do: foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
               ":foo(:1:, :2:, :3:):"

      assert Macro.to_string(quote(do: Bar.foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
               "::Bar:.foo(:1:, :2:, :3:):"
    end
  end

  test "validate/1" do
    ref = make_ref()

    assert Macro.validate(1) == :ok
    assert Macro.validate(1.0) == :ok
    assert Macro.validate(:foo) == :ok
    assert Macro.validate("bar") == :ok
    assert Macro.validate(self()) == :ok
    assert Macro.validate({1, 2}) == :ok
    assert Macro.validate({:foo, [], :baz}) == :ok
    assert Macro.validate({:foo, [], []}) == :ok
    assert Macro.validate([1, 2, 3]) == :ok

    assert Macro.validate(<<0::4>>) == {:error, <<0::4>>}
    assert Macro.validate(ref) == {:error, ref}
    assert Macro.validate({1, ref}) == {:error, ref}
    assert Macro.validate({ref, 2}) == {:error, ref}
    assert Macro.validate([1, ref, 3]) == {:error, ref}
    assert Macro.validate({:foo, [], 0}) == {:error, {:foo, [], 0}}
    assert Macro.validate({:foo, 0, []}) == {:error, {:foo, 0, []}}
  end

  test "decompose_call/1" do
    assert Macro.decompose_call(quote(do: foo)) == {:foo, []}
    assert Macro.decompose_call(quote(do: foo())) == {:foo, []}
    assert Macro.decompose_call(quote(do: foo(1, 2, 3))) == {:foo, [1, 2, 3]}

    assert Macro.decompose_call(quote(do: M.N.foo(1, 2, 3))) ==
             {{:__aliases__, [alias: false], [:M, :N]}, :foo, [1, 2, 3]}

    assert Macro.decompose_call(quote(do: :foo.foo(1, 2, 3))) == {:foo, :foo, [1, 2, 3]}
    assert Macro.decompose_call(quote(do: 1.(1, 2, 3))) == :error
    assert Macro.decompose_call(quote(do: "some string")) == :error
  end

  describe "env" do
    test "stacktrace" do
      env = %{__ENV__ | file: "foo", line: 12}

      assert Macro.Env.stacktrace(env) ==
               [{__MODULE__, :"test env stacktrace", 1, [file: "foo", line: 12]}]

      env = %{env | function: nil}
      assert Macro.Env.stacktrace(env) == [{__MODULE__, :__MODULE__, 0, [file: "foo", line: 12]}]

      env = %{env | module: nil}

      assert Macro.Env.stacktrace(env) ==
               [{:elixir_compiler, :__FILE__, 1, [file: "foo", line: 12]}]
    end

    test "context modules" do
      defmodule Foo.Bar do
        assert __MODULE__ in __ENV__.context_modules
      end
    end
  end

  ## pipe/unpipe

  test "pipe/3" do
    assert Macro.pipe(1, quote(do: foo), 0) == quote(do: foo(1))
    assert Macro.pipe(1, quote(do: foo(2)), 0) == quote(do: foo(1, 2))
    assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
    assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))

    assert_raise ArgumentError, ~r"cannot pipe 1 into 2", fn ->
      Macro.pipe(1, 2, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into {:ok}", fn ->
      Macro.pipe(1, {:ok}, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into 1 \+ 1", fn ->
      Macro.pipe(1, quote(do: 1 + 1), 0) == quote(do: foo(1))
    end

    # TODO: restore this test when we drop unary operator support in pipes
    # assert_raise ArgumentError, ~r"cannot pipe 1 into \+1", fn ->
    #   Macro.pipe(1, quote(do: + 1), 0)
    # end

    assert_raise ArgumentError, ~r"cannot pipe Macro into Env", fn ->
      Macro.pipe(Macro, quote(do: Env), 0)
    end

    message = ~r"cannot pipe :foo into an anonymous function without calling"

    assert_raise ArgumentError, message, fn ->
      Macro.pipe(:foo, quote(do: fn x -> x end), 0)
    end
  end

  test "unpipe/1" do
    assert Macro.unpipe(quote(do: foo)) == quote(do: [{foo, 0}])
    assert Macro.unpipe(quote(do: foo |> bar)) == quote(do: [{foo, 0}, {bar, 0}])
    assert Macro.unpipe(quote(do: foo |> bar |> baz)) == quote(do: [{foo, 0}, {bar, 0}, {baz, 0}])
  end

  ## traverse/pre/postwalk

  test "traverse/4" do
    assert traverse({:foo, [], nil}) ==
           [{:foo, [], nil}, {:foo, [], nil}]

    assert traverse({:foo, [], [1, 2, 3]}) ==
           [{:foo, [], [1, 2, 3]}, 1, 1, 2, 2, 3, 3, {:foo, [], [1, 2, 3]}]

    assert traverse({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [{{:., [], [:foo, :bar]}, [], [1, 2, 3]}, {:., [], [:foo, :bar]}, :foo, :foo, :bar, :bar, {:., [], [:foo, :bar]},
            1, 1, 2, 2, 3, 3, {{:., [], [:foo, :bar]}, [], [1, 2, 3]}]

    assert traverse({[1, 2, 3], [4, 5, 6]}) ==
           [{[1, 2, 3], [4, 5, 6]}, [1, 2, 3], 1, 1, 2, 2, 3, 3, [1, 2, 3],
            [4, 5, 6], 4, 4, 5, 5, 6, 6, [4, 5, 6], {[1, 2, 3], [4, 5, 6]}]
  end

  defp traverse(ast) do
    Macro.traverse(ast, [], &{&1, [&1 | &2]}, &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse
  end

  test "prewalk/3" do
    assert prewalk({:foo, [], nil}) ==
           [{:foo, [], nil}]

    assert prewalk({:foo, [], [1, 2, 3]}) ==
           [{:foo, [], [1, 2, 3]}, 1, 2, 3]

    assert prewalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [{{:., [], [:foo, :bar]}, [], [1, 2, 3]}, {:., [], [:foo, :bar]}, :foo, :bar, 1, 2, 3]

    assert prewalk({[1, 2, 3], [4, 5, 6]}) ==
           [{[1, 2, 3], [4, 5, 6]}, [1, 2, 3], 1, 2, 3, [4, 5, 6], 4, 5, 6]
  end

  defp prewalk(ast) do
    Macro.prewalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse
  end

  test "postwalk/3" do
    assert postwalk({:foo, [], nil}) ==
           [{:foo, [], nil}]

    assert postwalk({:foo, [], [1, 2, 3]}) ==
           [1, 2, 3, {:foo, [], [1, 2, 3]}]

    assert postwalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [:foo, :bar, {:., [], [:foo, :bar]}, 1, 2, 3, {{:., [], [:foo, :bar]}, [], [1, 2, 3]}]

    assert postwalk({[1, 2, 3], [4, 5, 6]}) ==
           [1, 2, 3, [1, 2, 3], 4, 5, 6, [4, 5, 6], {[1, 2, 3], [4, 5, 6]}]
  end

  test "generate_arguments/2" do
    assert Macro.generate_arguments(0, __MODULE__) == []
    assert Macro.generate_arguments(1, __MODULE__) == [{:var1, [], __MODULE__}]
    assert Macro.generate_arguments(4, __MODULE__) |> length == 4
  end

  defp postwalk(ast) do
    Macro.postwalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
  end

  test "underscore/1" do
    assert Macro.underscore("foo") == "foo"
    assert Macro.underscore("foo_bar") == "foo_bar"
    assert Macro.underscore("Foo") == "foo"
    assert Macro.underscore("FooBar") == "foo_bar"
    assert Macro.underscore("FOOBar") == "foo_bar"
    assert Macro.underscore("FooBAR") == "foo_bar"
    assert Macro.underscore("FOO_BAR") == "foo_bar"
    assert Macro.underscore("FoBaZa") == "fo_ba_za"
    assert Macro.underscore("Foo10") == "foo10"
    assert Macro.underscore("10Foo") == "10_foo"
    assert Macro.underscore("FooBar10") == "foo_bar10"
    assert Macro.underscore("Foo10Bar") == "foo10_bar"
    assert Macro.underscore("Foo.Bar") == "foo/bar"
    assert Macro.underscore(Foo.Bar) == "foo/bar"
    assert Macro.underscore("API.V1.User") == "api/v1/user"
    assert Macro.underscore("") == ""
  end

  test "camelize/1" do
    assert Macro.camelize("Foo") == "Foo"
    assert Macro.camelize("FooBar") == "FooBar"
    assert Macro.camelize("foo") == "Foo"
    assert Macro.camelize("foo_bar") == "FooBar"
    assert Macro.camelize("foo_") == "Foo"
    assert Macro.camelize("_foo") == "Foo"
    assert Macro.camelize("foo10") == "Foo10"
    assert Macro.camelize("_10foo") == "10foo"
    assert Macro.camelize("foo_10") == "Foo10"
    assert Macro.camelize("foo__10") == "Foo10"
    assert Macro.camelize("foo__bar") == "FooBar"
    assert Macro.camelize("foo/bar") == "Foo.Bar"
    assert Macro.camelize("Foo.Bar") == "Foo.Bar"
    assert Macro.camelize("FOO_BAR") == "FOO_BAR"
    assert Macro.camelize("FOO.BAR") == "FOO.BAR"
    assert Macro.camelize("") == ""
  end
end


Test beta upgrade.

Test beta upgrade.

defmodule Farmbot.System.Updates do
  @moduledoc "Handles over the air updates."
  use Supervisor

  @data_path Application.get_env(:farmbot, :data_path)
  @current_version Farmbot.Project.version()
  @target Farmbot.Project.target()
  @current_commit Farmbot.Project.commit()
  @env Farmbot.Project.env()
  use Farmbot.Logger

  @handler Application.get_env(:farmbot, :behaviour)[:update_handler]
  @handler || Mix.raise("Please configure update_handler")

  alias Farmbot.System.ConfigStorage

  @doc "Overwrite os update server field"
  def override_update_server(url) do
    ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", true)
    ConfigStorage.update_config_value(:string, "settings", "os_update_server_overwrite", url)
  end

  @doc "Force check updates."
  def check_updates(reboot) do
    token = ConfigStorage.get_config_value(:string, "authorization", "token")
    if token do
      case Farmbot.Jwt.decode(token) do
        {:ok, %Farmbot.Jwt{os_update_server: update_server}} ->
          override = ConfigStorage.get_config_value(:string, "settings", "os_update_server_overwrite")
          do_check_updates_http(override || update_server, reboot)
        _ -> no_token()
      end
    else
      no_token()
    end
  end

  defp no_token do
    Logger.debug 3, "Not checking for updates. (No token)"
    :ok
  end

  defp do_check_updates_http(url, reboot) do
    Logger.info 3, "Checking: #{url} for updates."
    with {:ok, %{body: body, status_code: 200}} <- Farmbot.HTTP.get(url),
    {:ok, data} <- Poison.decode(body),
    {:ok, prerelease} <- Map.fetch(data, "prerelease"),
    {:ok, new_commit} <- Map.fetch(data, "target_commitish"),
    {:ok, cl} <- Map.fetch(data, "body"),
    {:ok, false} <- Map.fetch(data, "draft"),
    {:ok, "v" <> new_version_str} <- Map.fetch(data, "tag_name"),
    {:ok, new_version} <- Version.parse(new_version_str),
    {:ok, current_version} <- Version.parse(@current_version),
    {:ok, fw_url} <- find_fw_url(data, new_version) do
      needs_update = if prerelease do
        val = new_commit == @current_commit
        Logger.info 1, "Checking prerelease commits: current_commit: #{@current_commit} new_commit: #{new_commit} #{val}"
        !val
      else
        case Version.compare(current_version, new_version) do
          s when s in [:gt, :eq] ->
            Logger.success 2, "Farmbot is up to date."
            false
          :lt ->
            Logger.busy 1, "New Farmbot firmware update: #{new_version}"
            true
        end

      end

      if should_apply_update(@env, prerelease, needs_update) do
        Logger.busy 1, "Downloading FarmbotOS over the air update"
        IO.puts cl
        # Logger.info 1, "Downloading update. Here is the release notes"
        # Logger.info 1, cl
        do_download_and_apply(fw_url, new_version, reboot)
      else
        :no_update
      end
    else
      :error ->
        msg = "Unexpected release HTTP response or wrong formated `tag_name`"
        Logger.error 2, msg

      {:error, :no_fw_url} ->
        Logger.error 2, "No firmware in update asssets."

      {:error, reason} ->
        Logger.error 1, "Failed to fetch update data: #{inspect reason}"

      {:ok, %{body: body, status_code: code}} ->
        reason = case Poison.decode(body) do
          {:ok, res} -> res
          _ -> body
        end
        Logger.error 1, "HTTP error: #{code}: #{inspect reason}"
    end
  end

  defp find_fw_url(%{"assets" => assets}, version) do
    expected_name = "farmbot-#{@target}-#{version}.fw"
    res = Enum.find_value(assets, fn(asset) ->
      IO.puts "checking: #{inspect asset["name"]} == #{inspect expected_name}"
      case asset do
        %{"browser_download_url" => fw_url, "name" => ^expected_name} -> fw_url
        _ -> nil
      end
    end)

    if res do
      {:ok, res}
    else
      {:error, :no_fw_url}
    end
  end

  defp should_apply_update(env, prerelease?, needs_update?)
  defp should_apply_update(_, _, false), do: false
  defp should_apply_update(:prod, true, _) do
    if ConfigStorage.get_config_value(:bool, "settings", "beta_opt_in") do
      Logger.info 3, "Applying beta update for production firmware"
      true
    else
      Logger.info 3, "Not applying prerelease update for production firmware"
      false
    end
  end

  defp should_apply_update(_env, true, _) do
    Logger.info 3, "Applying prerelease firmware."
    true
  end

  defp should_apply_update(_, _, true) do
    true
  end

  defp do_download_and_apply(dl_url, new_version, reboot) do
    dl_fun = Farmbot.BotState.download_progress_fun("FBOS_OTA")
    dl_path = Path.join(@data_path, "#{new_version}.fw")
    case Farmbot.HTTP.download_file(dl_url, dl_path, dl_fun, "", []) do
      {:ok, path} ->
        apply_firmware(path, reboot)
      {:error, reason} ->
        Logger.error 1, "Failed to download update file: #{inspect reason}"
        {:error, reason}
    end
  end

  @doc "Apply an OS (fwup) firmware."
  def apply_firmware(file_path, reboot) do
    Logger.busy 1, "Applying #{@target} OS update"
    before_update()
    case @handler.apply_firmware(file_path) do
      :ok ->
        Logger.success 1, "OS Firmware updated!"
        if reboot do
          Logger.warn 1, "Farmbot going down for OS update."
          Farmbot.System.reboot("OS Firmware Update.")
        end
      {:error, reason} ->
        Logger.error 1, "Failed to apply update: #{inspect reason}"
        {:error, reason}
    end
  end

  defp before_update do
    File.write!(update_file(), @current_version)
  end

  defp maybe_post_update do
    case File.read(update_file()) do
      {:ok, @current_version} -> :ok
      {:ok, old_version} ->
        Logger.info 1, "Updating from #{old_version} to #{@current_version}"
        @handler.post_update()
      {:error, :enoent} ->
        Logger.info 1, "Updating to #{@current_version}"
      {:error, err} -> raise err
    end
    before_update()
  end

  defp update_file do
    Path.join(@data_path, "update")
  end

  @doc false
  def start_link do
    Supervisor.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def init([]) do
    case @handler.setup(@env) do
      :ok ->
        maybe_post_update()
        children = [
          worker(Farmbot.System.UpdateTimer, [])
        ]
        opts = [strategy: :one_for_one]
        supervise(children, opts)
      {:error, reason} ->
        {:stop, reason}
    end
  end
end


Use category trending endpoint for category ed.

defmodule Ello.V2.EditorialView do
  use Ello.V2.Web, :view
  use Ello.V2.JSONAPI
  alias Ello.V2.{
    PostView,
    CategoryView,
    UserView,
    AssetView,
    ImageView,
  }

  def stale_checks(_, %{data: editorials}) do
    [etag: etag(editorials)]
  end

  def render("index.json", %{data: editorials} = opts) do
    posts     = editorials |> Enum.map(&(&1.post)) |> Enum.reject(&is_nil/1)
    reposts   = posts |> Enum.map(&(&1.reposted_source)) |> Enum.reject(&is_nil/1)
    all_posts = posts ++ reposts
    users     = Enum.map(all_posts, &(&1.author))
    assets    = Enum.flat_map(all_posts, &(&1.assets))
    categories = Enum.flat_map(all_posts ++ users, &(&1.categories))

    json_response()
    |> render_resource(:editorials, editorials, __MODULE__, opts)
    |> include_linked(:posts, all_posts, PostView, opts)
    |> include_linked(:users, users, UserView, opts)
    |> include_linked(:categories, categories, CategoryView, opts)
    |> include_linked(:assets, assets, AssetView, opts)
  end

  def render("editorial.json", %{editorial: editorial} = opts) do
    editorial
    |> render_self(__MODULE__, opts)
    |> add_subtitle(editorial)
    |> add_url(editorial)
  end

  def attributes, do: []
  def computed_attributes, do: [
    :title,
    :kind,
    :one_by_one_image,
    :one_by_two_image,
    :two_by_one_image,
    :two_by_two_image,
  ]

  def links(%{kind: "post"} = ed, _conn) do
    %{
      post: %{
        id: "#{ed.post_id}",
        type: "posts",
        href: "/api/v2/posts/#{ed.post_id}",
      }
    }
  end
  def links(%{kind: "curated_posts"} = ed, _conn) do
    query = ed.content["post_tokens"]
            |> Enum.map(&("token[]=#{&1}"))
            |> Enum.join("&")
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/editorials/posts/?#{query}",
      }
    }
  end
  def links(%{kind: "category"} = ed, _conn) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/categories/#{ed.content["slug"]}/posts/trending?stream_source=editorial&per_page=#{per_page()}&images_only=true",
      }
    }
  end
  def links(%{kind: "following"}, %{assigns: %{current_user: user}}) when not is_nil(user) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/following/posts/trending?stream_source=editorial&per_page=#{per_page()}&images_only=true",
      }
    }
  end
  def links(%{kind: "following"}, _conn) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/discover/posts/trending?stream_source=editorial&per_page=#{per_page()}&images_only=true",
      }
    }
  end
  def links(_, _), do: nil

  def title(ed, _), do: ed.content["title"]

  def kind(%{kind: kind}, _) when kind in ["category", "curated_posts", "following"],
    do: "post_stream"
  def kind(%{kind: "invite_join"}, %{assigns: %{current_user: user}}) when not is_nil(user), do: "invite"
  def kind(%{kind: "invite_join"}, _), do: "join"
  def kind(%{kind: kind}, _), do: kind

  defp add_subtitle(json, %{kind: kind} = ed) when kind in ["external", "post"],
    do: Map.put(json, :subtitle, ed.content["subtitle"])
  defp add_subtitle(json, _), do: json

  defp add_url(json, %{kind: "external"} = ed),
    do: Map.put(json, :url, ed.content["url"])
  defp add_url(json, _), do: json

  def one_by_one_image(%{one_by_one_image_struct: nil}, _conn) do
    %{}
  end
  def one_by_one_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.one_by_one_image_struct, conn: conn)
  end

  def one_by_two_image(%{one_by_two_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def one_by_two_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.one_by_two_image_struct, conn: conn)
  end

  def two_by_one_image(%{two_by_one_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def two_by_one_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.two_by_one_image_struct, conn: conn)
  end

  def two_by_two_image(%{two_by_two_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def two_by_two_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.two_by_two_image_struct, conn: conn)
  end

  defp per_page do
    Application.get_env(:ello_v2, :editorial_stream_kind_size, 5)
  end
end

defmodule Ello.V2.EditorialView do
  use Ello.V2.Web, :view
  use Ello.V2.JSONAPI
  alias Ello.V2.{
    PostView,
    CategoryView,
    UserView,
    AssetView,
    ImageView,
  }

  def stale_checks(_, %{data: editorials}) do
    [etag: etag(editorials)]
  end

  def render("index.json", %{data: editorials} = opts) do
    posts     = editorials |> Enum.map(&(&1.post)) |> Enum.reject(&is_nil/1)
    reposts   = posts |> Enum.map(&(&1.reposted_source)) |> Enum.reject(&is_nil/1)
    all_posts = posts ++ reposts
    users     = Enum.map(all_posts, &(&1.author))
    assets    = Enum.flat_map(all_posts, &(&1.assets))
    categories = Enum.flat_map(all_posts ++ users, &(&1.categories))

    json_response()
    |> render_resource(:editorials, editorials, __MODULE__, opts)
    |> include_linked(:posts, all_posts, PostView, opts)
    |> include_linked(:users, users, UserView, opts)
    |> include_linked(:categories, categories, CategoryView, opts)
    |> include_linked(:assets, assets, AssetView, opts)
  end

  def render("editorial.json", %{editorial: editorial} = opts) do
    editorial
    |> render_self(__MODULE__, opts)
    |> add_subtitle(editorial)
    |> add_url(editorial)
  end

  def attributes, do: []
  def computed_attributes, do: [
    :title,
    :kind,
    :one_by_one_image,
    :one_by_two_image,
    :two_by_one_image,
    :two_by_two_image,
  ]

  def links(%{kind: "post"} = ed, _conn) do
    %{
      post: %{
        id: "#{ed.post_id}",
        type: "posts",
        href: "/api/v2/posts/#{ed.post_id}",
      }
    }
  end
  def links(%{kind: "curated_posts"} = ed, _conn) do
    query = ed.content["post_tokens"]
            |> Enum.map(&("token[]=#{&1}"))
            |> Enum.join("&")
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/editorials/posts/?#{query}",
      }
    }
  end
  def links(%{kind: "category"} = ed, _conn) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/categories/#{ed.content["slug"]}/posts/recent?stream_source=editorial&per_page=#{per_page()}",
      }
    }
  end
  def links(%{kind: "following"}, %{assigns: %{current_user: user}}) when not is_nil(user) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/following/posts/trending?stream_source=editorial&per_page=#{per_page()}&images_only=true",
      }
    }
  end
  def links(%{kind: "following"}, _conn) do
    %{
      post_stream: %{
        type: "posts",
        href: "/api/v2/discover/posts/trending?stream_source=editorial&per_page=#{per_page()}&images_only=true",
      }
    }
  end
  def links(_, _), do: nil

  def title(ed, _), do: ed.content["title"]

  def kind(%{kind: kind}, _) when kind in ["category", "curated_posts", "following"],
    do: "post_stream"
  def kind(%{kind: "invite_join"}, %{assigns: %{current_user: user}}) when not is_nil(user), do: "invite"
  def kind(%{kind: "invite_join"}, _), do: "join"
  def kind(%{kind: kind}, _), do: kind

  defp add_subtitle(json, %{kind: kind} = ed) when kind in ["external", "post"],
    do: Map.put(json, :subtitle, ed.content["subtitle"])
  defp add_subtitle(json, _), do: json

  defp add_url(json, %{kind: "external"} = ed),
    do: Map.put(json, :url, ed.content["url"])
  defp add_url(json, _), do: json

  def one_by_one_image(%{one_by_one_image_struct: nil}, _conn) do
    %{}
  end
  def one_by_one_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.one_by_one_image_struct, conn: conn)
  end

  def one_by_two_image(%{one_by_two_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def one_by_two_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.one_by_two_image_struct, conn: conn)
  end

  def two_by_one_image(%{two_by_one_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def two_by_one_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.two_by_one_image_struct, conn: conn)
  end

  def two_by_two_image(%{two_by_two_image_struct: nil} = e, conn) do
    one_by_one_image(e, conn)
  end
  def two_by_two_image(editorial, conn) do
    render(ImageView, "image.json", image: editorial.two_by_two_image_struct, conn: conn)
  end

  defp per_page do
    Application.get_env(:ello_v2, :editorial_stream_kind_size, 5)
  end
end


properly delete features when syncing

properly delete features when syncing

defmodule Yodado.FeaturePersistence do
  @key_prefix "feature"
  @id_prefix "id"
  @index_key "index"

  def ping do
    case :eredis.q(:redis, ["PING"]) do
      {:ok, "PONG"} ->  true
      _any          ->  false
    end
  end

  def all() do
    # TODO make this transactional
    {:ok, feature_ids} = :eredis.q(:redis, ["SMEMBERS", index_key()])
    features = feature_ids |> Enum.reduce([], fn(id, acc) ->
      # TODO use MGET here rather than lots of GETs
      case find_feature({:key, id}) do
        :not_found      -> acc
        {:ok, feature}  -> [feature|acc]
      end
    end)
    {:ok, features}
  end

  def find_feature({:key, feature_id}) do
    {:ok, feature_json} = :eredis.q(:redis, ["GET", feature_id])
    case feature_json do
      :undefined  ->  :not_found
      _           ->  feature = JSEX.decode!(feature_json, labels: :atom)
                      {:ok, Yodado.Feature.from_json(feature)}
    end
  end

  def find_feature(feature_id) do
    find_feature({:key, key(feature_id)})
  end

  def save_feature(feature) do
    feature_json = Yodado.Feature.json(feature) |> JSEX.encode!
    feature_key = key(feature)
    [{:ok, "OK"}, {:ok, _lpush_result}]  = :eredis.qp(:redis, [
      ["SET", feature_key, feature_json],
      ["SADD", index_key(), feature_key],
    ])
    {:ok, true}
  end

  def delete_feature({:key, feature_id}) do
    [{:ok, _del_result}, {:ok, _srem_result}] = :eredis.qp(:redis, [
      ["DEL", feature_id],
      ["SREM", index_key(), feature_id]
    ])
    {:ok, true}
  end

  def delete_feature(feature_id) do
    delete_feature({:key, feature_id})
  end

  def sync(features) do
    # TODO make this transactional
    {:ok, feature_ids} = :eredis.q(:redis, ["SMEMBERS", index_key()])
    # TODO do M DEL (or whatever)
    feature_ids |> Enum.each(fn(id) ->
      {:ok, true} = delete_feature({:key, id})
    end)

    features |> Enum.each(fn(f) ->
      {:ok, true} = save_feature(f)
    end)
    {:ok, true}
  end

  defp key(feature) when is_record(feature, Yodado.Feature.Feature) do
    key(feature.title)
  end

  defp key(feature_id) do
    id = Regex.replace(%r/[^\w]+/, feature_id, "_") |> String.downcase
    "#{@key_prefix}:#{@id_prefix}:#{id}"
  end

  defp index_key() do
    "#{@key_prefix}:#{@index_key}"
  end
end

defmodule Yodado.FeaturePersistence do
  @key_prefix "feature"
  @id_prefix "id"
  @index_key "index"

  def ping do
    case :eredis.q(:redis, ["PING"]) do
      {:ok, "PONG"} ->  true
      _any          ->  false
    end
  end

  def all() do
    # TODO make this transactional
    {:ok, feature_ids} = :eredis.q(:redis, ["SMEMBERS", index_key()])
    features = feature_ids |> Enum.reduce([], fn(id, acc) ->
      # TODO use MGET here rather than lots of GETs
      case find_feature({:key, id}) do
        :not_found      -> acc
        {:ok, feature}  -> [feature|acc]
      end
    end)
    {:ok, features}
  end

  def find_feature({:key, feature_id}) do
    {:ok, feature_json} = :eredis.q(:redis, ["GET", feature_id])
    case feature_json do
      :undefined  ->  :not_found
      _           ->  feature = JSEX.decode!(feature_json, labels: :atom)
                      {:ok, Yodado.Feature.from_json(feature)}
    end
  end

  def find_feature(feature_id) do
    find_feature({:key, key(feature_id)})
  end

  def save_feature(feature) do
    feature_json = Yodado.Feature.json(feature) |> JSEX.encode!
    feature_key = key(feature)
    [{:ok, "OK"}, {:ok, _lpush_result}]  = :eredis.qp(:redis, [
      ["SET", feature_key, feature_json],
      ["SADD", index_key(), feature_key],
    ])
    {:ok, true}
  end

  def delete_feature(feature_id) do
    feature_key = key(feature_id)
    [{:ok, _del_result}, {:ok, _srem_result}] = :eredis.qp(:redis, [
      ["DEL", feature_key],
      ["SREM", index_key(), feature_key]
    ])
    {:ok, true}
  end

  def sync(features) do
    # TODO make this transactional
    {:ok, feature_ids} = :eredis.q(:redis, ["SMEMBERS", index_key()])
    # TODO do M DEL (or whatever)
    feature_ids |> Enum.each(fn(id) ->
      {:ok, true} = delete_feature(id)
    end)

    features |> Enum.each(fn(f) ->
      {:ok, true} = save_feature(f)
    end)
    {:ok, true}
  end

  defp key(feature) when is_record(feature, Yodado.Feature.Feature) do
    key(feature.title)
  end

  defp key(feature_id) do
    id = Regex.replace(%r/[^\w]+/, feature_id, "_") |> String.downcase
    "#{@key_prefix}:#{@id_prefix}:#{id}"
  end

  defp index_key() do
    "#{@key_prefix}:#{@index_key}"
  end
end


add guard when loading config file for an unknown environment

use Mix.Config

if Mix.env in [:dev, :prod, :test] do
  import_config "#{Mix.env}.exs"
end

use Mix.Config
import_config "#{Mix.env}.exs"


Run training matches every hour, instead of every 30 minutes

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# General application configuration
config :api,
  ecto_repos: [Api.Repo],
  generators: [binary_id: true],
  # Use this tutorial to get a Slack token with client privileges
  # https://medium.com/@andrewarrow/how-to-get-slack-api-tokens-with-client-scope-e311856ebe9
  slack_token: System.get_env("SLACK_TOKEN"),
  # Maximum number of users each team is allowed to have
  team_user_limit: 4,
  # Library used to make external HTTP requests
  http_lib: HTTPoison,
  # Github API data
  github_token: System.get_env("GITHUB_TOKEN"),
  github_org: "makeorbreak-io"

# Endpoint configuration
config :api, ApiWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: System.get_env("SECRET_KEY_BASE"),
  render_errors: [view: ApiWeb.ErrorView, accepts: ~w(json)]

# Elixir's Logger configuration
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Database configuration
config :api, Api.Repo,
  adapter: Ecto.Adapters.Postgres

# Guardian configuration
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "Api",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key: System.get_env("SECRET_KEY_BASE"),
  serializer: ApiWeb.GuardianSerializer,
  permissions: %{
    admin: [:full],
    participant: [:full]
  }

# Bamboo configuration
config :api, Api.Mailer,
  adapter: Bamboo.LocalAdapter

# Sentry.io configuration
config :sentry,
  dsn: System.get_env("SENTRY_DSN"),
  environment_name: Mix.env,
  included_environments: [:prod],
  enable_source_code_context: true,
  root_source_code_path: File.cwd!

# Quantum scheduler config
# https://github.com/quantum-elixir/quantum-core
config :api, Api.Scheduler,
  jobs: [
    {"@hourly", &Api.AICompetition.perform_training_matches/0}, # run games every 30 minutes
  ]

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# General application configuration
config :api,
  ecto_repos: [Api.Repo],
  generators: [binary_id: true],
  # Use this tutorial to get a Slack token with client privileges
  # https://medium.com/@andrewarrow/how-to-get-slack-api-tokens-with-client-scope-e311856ebe9
  slack_token: System.get_env("SLACK_TOKEN"),
  # Maximum number of users each team is allowed to have
  team_user_limit: 4,
  # Library used to make external HTTP requests
  http_lib: HTTPoison,
  # Github API data
  github_token: System.get_env("GITHUB_TOKEN"),
  github_org: "makeorbreak-io"

# Endpoint configuration
config :api, ApiWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: System.get_env("SECRET_KEY_BASE"),
  render_errors: [view: ApiWeb.ErrorView, accepts: ~w(json)]

# Elixir's Logger configuration
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Database configuration
config :api, Api.Repo,
  adapter: Ecto.Adapters.Postgres

# Guardian configuration
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "Api",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key: System.get_env("SECRET_KEY_BASE"),
  serializer: ApiWeb.GuardianSerializer,
  permissions: %{
    admin: [:full],
    participant: [:full]
  }

# Bamboo configuration
config :api, Api.Mailer,
  adapter: Bamboo.LocalAdapter

# Sentry.io configuration
config :sentry,
  dsn: System.get_env("SENTRY_DSN"),
  environment_name: Mix.env,
  included_environments: [:prod],
  enable_source_code_context: true,
  root_source_code_path: File.cwd!

# Quantum scheduler config
# https://github.com/quantum-elixir/quantum-core
config :api, Api.Scheduler,
  jobs: [
    {"*/30 * * * *", &Api.AICompetition.perform_training_matches/0}, # run games every 30 minutes
  ]

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"


Drop Mix.Config, import Config

Drop Mix.Config, import Config

Using Mix.Config was deprecated. Use import Config in config/config.exs
instead.

import Config

if Mix.env() in [:bench, :test, :test_no_nif] do
  config :appsignal, appsignal_system: Appsignal.FakeSystem
  config :appsignal, appsignal_nif: Appsignal.FakeNif
  config :appsignal, appsignal_diagnose_report: Appsignal.Diagnose.FakeReport
  config :appsignal, appsignal: Appsignal.FakeAppsignal
  config :appsignal, inet: FakeInet
  config :appsignal, io: FakeIO
  config :appsignal, file: FakeFile
  config :appsignal, os_internal: FakeOS

  config :appsignal, appsignal_tracer_nif: Appsignal.Test.Nif
  config :appsignal, appsignal_tracer: Appsignal.Test.Tracer
  config :appsignal, appsignal_span: Appsignal.Test.Span
  config :appsignal, appsignal_monitor: Appsignal.Test.Monitor
  config :appsignal, deletion_delay: 100

  config :appsignal, :config,
    otp_app: :appsignal,
    push_api_key: "00000000-0000-0000-0000-000000000000",
    name: "AppSignal test suite app v0",
    env: "test",
    active: true

  config :appsignal,
    ecto_repos: [Appsignal.Test.Repo]
end

use Mix.Config

if Mix.env() in [:bench, :test, :test_no_nif] do
  config :appsignal, appsignal_system: Appsignal.FakeSystem
  config :appsignal, appsignal_nif: Appsignal.FakeNif
  config :appsignal, appsignal_diagnose_report: Appsignal.Diagnose.FakeReport
  config :appsignal, appsignal: Appsignal.FakeAppsignal
  config :appsignal, inet: FakeInet
  config :appsignal, io: FakeIO
  config :appsignal, file: FakeFile
  config :appsignal, os_internal: FakeOS

  config :appsignal, appsignal_tracer_nif: Appsignal.Test.Nif
  config :appsignal, appsignal_tracer: Appsignal.Test.Tracer
  config :appsignal, appsignal_span: Appsignal.Test.Span
  config :appsignal, appsignal_monitor: Appsignal.Test.Monitor
  config :appsignal, deletion_delay: 100

  config :appsignal, :config,
    otp_app: :appsignal,
    push_api_key: "00000000-0000-0000-0000-000000000000",
    name: "AppSignal test suite app v0",
    env: "test",
    active: true

  config :appsignal,
    ecto_repos: [Appsignal.Test.Repo]
end


Set up a more robust scheme of calling to git

# Since Elixir does not (yet) support local macros,
# we need to wrap the step that extracts git information
# in this module.
defmodule System.GitCompiler do
  @moduledoc false

  defmacro generate do
    quote do
      @doc """
      Returns a tuple { Elixir version, commit sha-1, build date }.

      The format of the return value may change in a future release. Please
      make sure your code doesn't depend on it.
      """
      def build_info do
        { System.version,
          unquote(get_head_sha),
          unquote(get_date) }
      end
    end
  end

  @doc """
  Tries to run `git rev-parse HEAD`. In case of success returns the
  commit sha, otherwise returns an empty string.
  """
  defp get_head_sha do
    # The following failures are possible:
    #
    #  1) there is no `git` command
    #  2) pwd is not a git repository
    #
    command = 'git rev-parse HEAD'
    opts = [:stream, :exit_status, :use_stdio,
            :stderr_to_stdout, :in, :eof]
    port = :erlang.open_port {:spawn, command}, opts

    output = read_port port
    case output do
    match: { 0, data }
      normalize data
    else:
      ""
    end
  end

  defp read_port(port, data // []) do
    receive do
    match: {^port, {:data, new_data}}
      read_port port, [new_data|data]
    match: {^port, :eof}
      :erlang.port_close port
      receive do
      match: {^port, {:exit_status, exit_status}}
        {exit_status, List.reverse data}
      end
    end
  end

  defp get_date do
    normalize :os.cmd 'TZ=GMT date'
  end

  defp normalize(string) do
    Regex.replace_all %r(\n), to_binary(string), ""
  end
end

defmodule System do
  @moduledoc """
  The System module provides access to some variables used or
  maintained by the VM and to functions that interact strongly
  with the VM or the host system.
  """

  require System.GitCompiler
  System.GitCompiler.generate

  @doc """
  Returns Elixir's version as binary.
  """
  def version, do: "0.9.0.dev"

  @doc """
  Returns the list of command-line arguments passed to the program.
  """
  def argv do
    Erlang.gen_server.call(:elixir_code_server, :argv)
  end

  @doc """
  Registers a function that will be invoked
  at the end of program execution. Useful for
  invoking a hook on scripted mode.

  The function must expect the exit status code
  as argument.
  """
  def at_exit(fun) when is_function(fun, 1) do
    server_call { :at_exit, fun }
  end

  @doc """
  Executes `command` in a command shell of the target OS, captures the standard
  output of the command and returns this result.

  `command` can a charlist or a binary.
  """
  def cmd(command) when is_binary(command) do
      list_to_binary cmd(binary_to_list(command))
  end

  def cmd(command), do: :os.cmd command

  @doc """
  Returns a list of all environment variables. Each environment variable is
  given as a single string of the format "VarName=Value", where VarName is the
  name of the variable and Value its value.
  """
  def get_env do
    Enum.map :os.getenv, list_to_binary &1
  end

  @doc """
  Returns the Value of the environment variable `varname`, or nil if the
  environment variable is undefined.
  """
  def get_env(varname) when is_binary(varname) do
    list_to_binary get_env(binary_to_list(varname))
  end

  def get_env(varname) do
    case :os.getenv(varname) do
    match: false
      nil
    match: other
      other
    end
  end

  @doc """
  Returns the process identifier of the current Erlang emulator in the format
  most commonly used by the operating system environment.

  See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
  """
  def get_pid, do: list_to_binary(:os.getpid)

  @doc """
  Sets a new `value` for the environment variable `varname`.
  """
  def put_env(varname, value) when is_list(varname) and is_list(value) do
   :os.putenv varname, value
  end

  def put_env(varname, value) do
   :os.putenv to_char_list(varname), to_char_list(value)
  end

  @doc """
  Sets a new value for each environment variable corresponding to each key in
  `dict`.
  """
  def put_env(dict) do
    Enum.each dict, fn({key, val}) -> put_env key, val end
  end

  @doc """
  Returns the current working directory as a binary.
  """
  def pwd, do: :filename.absname("")

  @doc """
  Get the stacktrace.
  """
  def stacktrace do
    filter_stacktrace Erlang.erlang.get_stacktrace
  end

  ## Helpers

  # Filter stacktrace by removing internal BOOTSTRAP calls.
  defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
  defp filter_stacktrace([]), do: []

  defp server_call(args) do
    Erlang.gen_server.call(:elixir_code_server, args)
  end
end

# Since Elixir does not (yet) support local macros,
# we need to wrap the step that extracts git information
# in this module.
defmodule System.GitCompiler do
  @moduledoc false

  defmacro generate do
    quote do
      @doc """
      Returns a tuple { Elixir version, commit sha-1, build date }.

      The format of the return value may change in a future release. Please
      make sure your code doesn't depend on it.
      """
      def build_info do
        { System.version,
          unquote(get_head_sha),
          unquote(get_date) }
      end
    end
  end

  defp get_head_sha do
    # The following failures are possible:
    #
    #  1) there is no `git` command
    #  2) pwd is not a git repository
    #
    # In order to add proper checks, we should replace :os.cmd with ports which
    # will then have to be tested separately on Windows, Linux, Mac.
    #
    # Here's a discussion that has a couple of code snippets we might use.
    # http://erlang.2086793.n4.nabble.com/Executing-external-commands-td2094235.html
    #
    # I'll come back to this issue soon.
    # <alcosholik@gmail.com>
    normalize :os.cmd 'git rev-parse HEAD'
  end

  defp get_date do
    normalize :os.cmd 'TZ=GMT date'
  end

  defp normalize(string) do
    Regex.replace_all %r(\n), to_binary(string), ""
  end
end

defmodule System do
  @moduledoc """
  The System module provides access to some variables used or
  maintained by the VM and to functions that interact strongly
  with the VM or the host system.
  """

  require System.GitCompiler
  System.GitCompiler.generate

  @doc """
  Returns Elixir's version as binary.
  """
  def version, do: "0.9.0.dev"

  @doc """
  Returns the list of command-line arguments passed to the program.
  """
  def argv do
    Erlang.gen_server.call(:elixir_code_server, :argv)
  end

  @doc """
  Registers a function that will be invoked
  at the end of program execution. Useful for
  invoking a hook on scripted mode.

  The function must expect the exit status code
  as argument.
  """
  def at_exit(fun) when is_function(fun, 1) do
    server_call { :at_exit, fun }
  end

  @doc """
  Executes `command` in a command shell of the target OS, captures the standard
  output of the command and returns this result.

  `command` can a charlist or a binary.
  """
  def cmd(command) when is_binary(command) do
      list_to_binary cmd(binary_to_list(command))
  end

  def cmd(command), do: :os.cmd command

  @doc """
  Returns a list of all environment variables. Each environment variable is
  given as a single string of the format "VarName=Value", where VarName is the
  name of the variable and Value its value.
  """
  def get_env do
    Enum.map :os.getenv, list_to_binary &1
  end

  @doc """
  Returns the Value of the environment variable `varname`, or nil if the
  environment variable is undefined.
  """
  def get_env(varname) when is_binary(varname) do
    list_to_binary get_env(binary_to_list(varname))
  end

  def get_env(varname) do
    case :os.getenv(varname) do
    match: false
      nil
    match: other
      other
    end
  end

  @doc """
  Returns the process identifier of the current Erlang emulator in the format
  most commonly used by the operating system environment.

  See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
  """
  def get_pid, do: list_to_binary(:os.getpid)

  @doc """
  Sets a new `value` for the environment variable `varname`.
  """
  def put_env(varname, value) when is_list(varname) and is_list(value) do
   :os.putenv varname, value
  end

  def put_env(varname, value) do
   :os.putenv to_char_list(varname), to_char_list(value)
  end

  @doc """
  Sets a new value for each environment variable corresponding to each key in
  `dict`.
  """
  def put_env(dict) do
    Enum.each dict, fn({key, val}) -> put_env key, val end
  end

  @doc """
  Returns the current working directory as a binary.
  """
  def pwd, do: :filename.absname("")

  @doc """
  Get the stacktrace.
  """
  def stacktrace do
    filter_stacktrace Erlang.erlang.get_stacktrace
  end

  ## Helpers

  # Filter stacktrace by removing internal BOOTSTRAP calls.
  defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
  defp filter_stacktrace([]), do: []

  defp server_call(args) do
    Erlang.gen_server.call(:elixir_code_server, args)
  end
end


No docs for this

No docs for this

defmodule PactElixir.MismatchesError do
  # /// Request query mismatch
  # QueryMismatch {
  #     /// query parameter name
  #     parameter: String,
  #     /// expected value
  #     expected: String,
  #     /// actual value
  #     actual: String,
  #     /// description of the mismatch
  #     mismatch: String
  # },
  @moduledoc false
  def format_mismatches(mismatches) do
    mismatches
    |> Enum.map(fn %{
                     "actual" => actual,
                     "expected" => expected,
                     "mismatch" => mismatch,
                     "parameter" => _parameter,
                     "type" => _type
                   } ->
      ~s|#{mismatch}. Expected: "#{expected}", got "#{actual}".|
    end)
    |> Enum.join("\n")
  end
end

defmodule PactElixir.VerificationError do
  defexception [:message]
end

defmodule PactElixir.Errors do
  @moduledoc """
  Currently this is the whole error handling stuff.
  """

  # %{
  #   "method" => "GET",
  #   "path" => "/foo",
  #   "request" => %{
  #     "body" => "Missing",
  #     "headers" => nil,
  #     "matching_rules" => nil,
  #     "method" => "GET",
  #     "path" => "/foo",
  #     "query" => nil
  #   },
  #   "type" => "missing-request"
  # }

  def convert_to_error(mismatches) do
    messages = Enum.map(mismatches, &mismatch_to_message/1)

    if messages != [] do
      raise PactElixir.VerificationError,
            "error while verifying expectations\n\n" <> Enum.join(messages, "\n")
    end

    :ok
  end

  defp mismatch_to_message(%{"request" => request, "type" => type} = _mismatch) do
    "#{type_to_name(type)}:\n#{map_to_string(request)}"
  end

  defp mismatch_to_message(
         %{"mismatches" => mismatches, "type" => type, "method" => method, "path" => path} =
           _mismatch
       ) do
    "#{type_to_name(type)}: Mismatches for #{method} '#{path}':\n" <>
      PactElixir.MismatchesError.format_mismatches(mismatches)
  end

  def type_to_name(type) do
    String.split(type, "-")
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
  end

  def map_to_string(map) do
    # Poison.encode!(map, pretty: true)
    Poison.encode!(map)
  end
end

# MethodMismatch {
#     /// Expected request method
#     expected: String,
#     /// Actual request method
#     actual: String
# },
# /// Request Path mismatch
# PathMismatch {
#     /// expected request path
#     expected: String,
#     /// actual request path
#     actual: String,
#     /// description of the mismatch
#     mismatch: String
# },
# /// Response status mismatch
# StatusMismatch {
#     /// expected response status
#     expected: u16,
#     /// actual response status
#     actual: u16
# },
# /// Header mismatch
# HeaderMismatch {
#     /// header key
#     key: String,
#     /// expected value
#     expected: String,
#     /// actual value
#     actual: String,
#     /// description of the mismatch
#     mismatch: String
# },
# /// Mismatch in the content type of the body
# BodyTypeMismatch {
#     /// expected content type of the body
#     expected: String,
#     /// actual content type of the body
#     actual: String
# },
# /// Body element mismatch
# BodyMismatch {
#     /// path expression to where the mismatch occured
#     path: String,
#     /// expected value
#     expected: Option<Vec<u8>>,
#     /// actual value
#     actual: Option<Vec<u8>>,
#     /// description of the mismatch
#     mismatch: String
# }

# RequestMatch
# RequestMismatch
# RequestNotFound
# MissingRequest

# pub enum DifferenceType {
#   /// Methods differ
#   Method,
#   /// Paths differ
#   Path,
#   /// Headers differ
#   Headers,
#   /// Query parameters differ
#   QueryParameters,
#   /// Bodies differ
#   Body,
#   /// Matching Rules differ
#   MatchingRules,
#   /// Response status differ
#   Status
# }
# defp to_error(%{
#        "method" => "GET",
#        "path" => "/organizations/org23/locations/betrieb41",
#        "request" => %{
#          "body" => "Empty",
#          "headers" => %{
#            "connection" => "keep-alive",
#            "content-length" => "0",
#            "host" => "localhost:61627",
#            "te" => ""
#          },
#          "matching_rules" => %{"rules" => %{}},
#          "method" => "GET",
#          "path" => "/organizations/org23/locations/betrieb41",
#          "query" => nil
#        },
#        "type" => "request-not-found"
#      }) do
# end

defmodule PactElixir.MismatchesError do
  # /// Request query mismatch
  # QueryMismatch {
  #     /// query parameter name
  #     parameter: String,
  #     /// expected value
  #     expected: String,
  #     /// actual value
  #     actual: String,
  #     /// description of the mismatch
  #     mismatch: String
  # },
  def format_mismatches(mismatches) do
    mismatches
    |> Enum.map(fn %{
                     "actual" => actual,
                     "expected" => expected,
                     "mismatch" => mismatch,
                     "parameter" => _parameter,
                     "type" => _type
                   } ->
      ~s|#{mismatch}. Expected: "#{expected}", got "#{actual}".|
    end)
    |> Enum.join("\n")
  end
end

defmodule PactElixir.VerificationError do
  defexception [:message]
end

defmodule PactElixir.Errors do
  @moduledoc """
  Currently this is the whole error handling stuff.
  """

  # %{
  #   "method" => "GET",
  #   "path" => "/foo",
  #   "request" => %{
  #     "body" => "Missing",
  #     "headers" => nil,
  #     "matching_rules" => nil,
  #     "method" => "GET",
  #     "path" => "/foo",
  #     "query" => nil
  #   },
  #   "type" => "missing-request"
  # }

  def convert_to_error(mismatches) do
    messages = Enum.map(mismatches, &mismatch_to_message/1)

    if messages != [] do
      raise PactElixir.VerificationError,
            "error while verifying expectations\n\n" <> Enum.join(messages, "\n")
    end

    :ok
  end

  defp mismatch_to_message(%{"request" => request, "type" => type} = _mismatch) do
    "#{type_to_name(type)}:\n#{map_to_string(request)}"
  end

  defp mismatch_to_message(
         %{"mismatches" => mismatches, "type" => type, "method" => method, "path" => path} =
           _mismatch
       ) do
    "#{type_to_name(type)}: Mismatches for #{method} '#{path}':\n" <>
      PactElixir.MismatchesError.format_mismatches(mismatches)
  end

  def type_to_name(type) do
    String.split(type, "-")
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
  end

  def map_to_string(map) do
    # Poison.encode!(map, pretty: true)
    Poison.encode!(map)
  end
end

# MethodMismatch {
#     /// Expected request method
#     expected: String,
#     /// Actual request method
#     actual: String
# },
# /// Request Path mismatch
# PathMismatch {
#     /// expected request path
#     expected: String,
#     /// actual request path
#     actual: String,
#     /// description of the mismatch
#     mismatch: String
# },
# /// Response status mismatch
# StatusMismatch {
#     /// expected response status
#     expected: u16,
#     /// actual response status
#     actual: u16
# },
# /// Header mismatch
# HeaderMismatch {
#     /// header key
#     key: String,
#     /// expected value
#     expected: String,
#     /// actual value
#     actual: String,
#     /// description of the mismatch
#     mismatch: String
# },
# /// Mismatch in the content type of the body
# BodyTypeMismatch {
#     /// expected content type of the body
#     expected: String,
#     /// actual content type of the body
#     actual: String
# },
# /// Body element mismatch
# BodyMismatch {
#     /// path expression to where the mismatch occured
#     path: String,
#     /// expected value
#     expected: Option<Vec<u8>>,
#     /// actual value
#     actual: Option<Vec<u8>>,
#     /// description of the mismatch
#     mismatch: String
# }

# RequestMatch
# RequestMismatch
# RequestNotFound
# MissingRequest

# pub enum DifferenceType {
#   /// Methods differ
#   Method,
#   /// Paths differ
#   Path,
#   /// Headers differ
#   Headers,
#   /// Query parameters differ
#   QueryParameters,
#   /// Bodies differ
#   Body,
#   /// Matching Rules differ
#   MatchingRules,
#   /// Response status differ
#   Status
# }
# defp to_error(%{
#        "method" => "GET",
#        "path" => "/organizations/org23/locations/betrieb41",
#        "request" => %{
#          "body" => "Empty",
#          "headers" => %{
#            "connection" => "keep-alive",
#            "content-length" => "0",
#            "host" => "localhost:61627",
#            "te" => ""
#          },
#          "matching_rules" => %{"rules" => %{}},
#          "method" => "GET",
#          "path" => "/organizations/org23/locations/betrieb41",
#          "query" => nil
#        },
#        "type" => "request-not-found"
#      }) do
# end