Fix typespec test on OTP18

Fix typespec test on OTP18

Code.require_file "test_helper.exs", __DIR__

defmodule ProtocolTest do
  use ExUnit.Case, async: true

  defprotocol Sample do
    @type t :: any
    @doc "Ok"
    @spec ok(t) :: boolean
    def ok(thing)
  end

  defprotocol WithAny do
    @fallback_to_any true
    @doc "Ok"
    def ok(thing)
  end

  defprotocol Derivable do
    def ok(a)
  end

  defimpl Derivable, for: Any do
    defmacro __deriving__(module, struct, options) do
      quote do
        defimpl Derivable, for: unquote(module) do
          def ok(arg) do
            {:ok, arg, unquote(Macro.escape(struct)), unquote(options)}
          end
        end
      end
    end

    def ok(arg) do
      {:ok, arg}
    end
  end

  defimpl WithAny, for: Map do
    def ok(map) do
      {:ok, map}
    end
  end

  defimpl WithAny, for: Any do
    def ok(any) do
      {:ok, any}
    end
  end

  defmodule NoImplStruct do
    defstruct a: 0, b: 0
  end

  defmodule ImplStruct do
    @derive [WithAny, Derivable]
    defstruct a: 0, b: 0

    defimpl Sample do
      def ok(struct) do
        Unknown.undefined(struct)
      end
    end
  end

  test "protocol implementations without any" do
    assert is_nil Sample.impl_for(:foo)
    assert is_nil Sample.impl_for(fn(x) -> x end)
    assert is_nil Sample.impl_for(1)
    assert is_nil Sample.impl_for(1.1)
    assert is_nil Sample.impl_for([])
    assert is_nil Sample.impl_for([1, 2, 3])
    assert is_nil Sample.impl_for({})
    assert is_nil Sample.impl_for({1, 2, 3})
    assert is_nil Sample.impl_for("foo")
    assert is_nil Sample.impl_for(<<1>>)
    assert is_nil Sample.impl_for(%{})
    assert is_nil Sample.impl_for(self)
    assert is_nil Sample.impl_for(hd(:erlang.ports))
    assert is_nil Sample.impl_for(make_ref)

    assert Sample.impl_for(%ImplStruct{}) ==
           Sample.ProtocolTest.ImplStruct
    assert Sample.impl_for(%NoImplStruct{}) ==
           nil
  end

  test "protocol implementation with any and structs fallback" do
    assert WithAny.impl_for(%NoImplStruct{})      == WithAny.Any
    assert WithAny.impl_for(%ImplStruct{})        == WithAny.Any # Derived
    assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
    assert WithAny.impl_for(%{})                  == WithAny.Map
    assert WithAny.impl_for(self)                 == WithAny.Any
  end

  test "protocol not implemented" do
    assert_raise Protocol.UndefinedError, "protocol ProtocolTest.Sample not implemented for :foo", fn ->
      Sample.ok(:foo)
    end
  end

  test "protocol documentation" do
    import PathHelpers

    write_beam(defprotocol SampleDocsProto do
      @type t :: any
      @doc "Ok"
      @spec ok(t) :: boolean
      def ok(thing)
    end)

    docs = Code.get_docs(SampleDocsProto, :docs)
    assert {{:ok, 1}, _, :def, [{:thing, _, nil}], "Ok"} =
           List.keyfind(docs, {:ok, 1}, 0)
  end

  test "protocol keeps underlying UndefinedFunctionError" do
    assert_raise UndefinedFunctionError, fn ->
      WithAll.ok(%ImplStruct{})
    end
  end

  test "protocol defines callbacks" do
    if :erlang.system_info(:otp_release) >= '18' do
      assert get_callbacks(Sample, :ok, 1) ==
        [{:type, [9], :fun, [{:type, [9], :product, [{:user_type, [9], :t, []}]}, {:type, [9], :boolean, []}]}]

      assert get_callbacks(WithAny, :ok, 1) ==
        [{:type, [16], :fun, [{:type, [16], :product, [{:user_type, [16], :t, []}]}, {:type, [16], :term, []}]}]
    else
      assert get_callbacks(Sample, :ok, 1) ==
        [{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]

      assert get_callbacks(WithAny, :ok, 1) ==
        [{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
    end
  end

  test "protocol defines functions and attributes" do
    assert Sample.__protocol__(:module) == Sample
    assert Sample.__protocol__(:functions) == [ok: 1]
    refute Sample.__protocol__(:consolidated?)
    assert Sample.__info__(:attributes)[:protocol] == [fallback_to_any: false]

    assert WithAny.__protocol__(:module) == WithAny
    assert WithAny.__protocol__(:functions) == [ok: 1]
    refute WithAny.__protocol__(:consolidated?)
    assert WithAny.__info__(:attributes)[:protocol] == [fallback_to_any: true]
  end

  test "defimpl" do
    module = Module.concat(Sample, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == module
    assert module.__impl__(:protocol) == Sample
    assert module.__info__(:attributes)[:impl] ==
           [protocol: Sample, for: ImplStruct]
  end

  test "defimpl with implicit derive" do
    module = Module.concat(WithAny, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == WithAny.Any
    assert module.__impl__(:protocol) == WithAny
    assert module.__info__(:attributes)[:impl] ==
           [protocol: WithAny, for: ImplStruct]
  end

  test "defimpl with explicit derive" do
    module = Module.concat(Derivable, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == module
    assert module.__impl__(:protocol) == Derivable
    assert module.__info__(:attributes)[:impl] ==
           [protocol: Derivable, for: ImplStruct]
  end

  test "defimpl with multiple for" do
    defprotocol Multi do
      def test(a)
    end

    defimpl Multi, for: [Atom, Integer] do
      def test(a), do: a
    end

    assert Multi.test(1) == 1
    assert Multi.test(:a) == :a
  end

  defp get_callbacks(module, name, arity) do
    callbacks = for {:callback, info} <- module.__info__(:attributes), do: hd(info)
    List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
  end

  test "derives protocol implicitly" do
    struct = %ImplStruct{a: 1, b: 1}
    assert WithAny.ok(struct) == {:ok, struct}

    struct = %NoImplStruct{a: 1, b: 1}
    assert WithAny.ok(struct) == {:ok, struct}
  end

  test "derives protocol explicitly" do
    struct = %ImplStruct{a: 1, b: 1}
    assert Derivable.ok(struct) == {:ok, struct, %ImplStruct{}, []}

    assert_raise Protocol.UndefinedError, fn ->
      struct = %NoImplStruct{a: 1, b: 1}
      Derivable.ok(struct)
    end
  end

  test "derives protocol explicitly with options" do
    defmodule AnotherStruct do
      @derive [{Derivable, :ok}]
      @derive [WithAny]
      defstruct a: 0, b: 0
    end

    struct = struct AnotherStruct, a: 1, b: 1
    assert Derivable.ok(struct) ==
           {:ok, struct, struct(AnotherStruct), :ok}
  end

  test "derive protocol explicitly via API" do
    defmodule InlineStruct do
      defstruct a: 0, b: 0
    end

    require Protocol
    assert Protocol.derive(Derivable, InlineStruct, :oops) == :ok

    struct = struct InlineStruct, a: 1, b: 1
    assert Derivable.ok(struct) ==
           {:ok, struct, struct(InlineStruct), :oops}
  end

  test "derived implementation keeps local file/line info" do
    assert ProtocolTest.WithAny.ProtocolTest.ImplStruct.__info__(:compile)[:source] ==
           String.to_char_list(__ENV__.file)
  end

  test "cannot derive without any implementation" do
    assert_raise ArgumentError,
        ~r"#{inspect Sample.Any} is not available, cannot derive #{inspect Sample}", fn ->
      defmodule NotCompiled do
        @derive [Sample]
        defstruct hello: :world
      end
    end
  end
end

path = Path.expand("../ebin", __DIR__)
File.mkdir_p!(path)

compile = fn {:module, module, binary, _} ->
  File.write!("#{path}/#{module}.beam", binary)
end

defmodule Protocol.ConsolidationTest do
  use ExUnit.Case, async: true

  compile.(
    defprotocol Sample do
      @type t :: any
      @doc "Ok"
      @spec ok(t) :: boolean
      def ok(thing)
    end
  )

  compile.(
    defprotocol WithAny do
      @fallback_to_any true
      @doc "Ok"
      def ok(thing)
    end
  )

  defimpl WithAny, for: Map do
    def ok(map) do
      {:ok, map}
    end
  end

  defimpl WithAny, for: Any do
    def ok(any) do
      {:ok, any}
    end
  end

  defmodule NoImplStruct do
    defstruct a: 0, b: 0
  end

  defmodule ImplStruct do
    @derive [WithAny]
    defstruct a: 0, b: 0

    defimpl Sample do
      def ok(struct) do
        Unknown.undefined(struct)
      end
    end
  end

  Code.append_path(path)

  # Any is ignored because there is no fallback
  :code.purge(Sample)
  :code.delete(Sample)
  {:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
  :code.load_binary(Sample, 'protocol_test.exs', binary)

  # Any should be moved to the end
  :code.purge(WithAny)
  :code.delete(WithAny)
  {:ok, binary} = Protocol.consolidate(WithAny, [Any, ImplStruct, Map])
  :code.load_binary(WithAny, 'protocol_test.exs', binary)

  test "consolidated?/1" do
    assert Protocol.consolidated?(WithAny)
    refute Protocol.consolidated?(Enumerable)
  end

  test "consolidated implementations without any" do
    assert is_nil Sample.impl_for(:foo)
    assert is_nil Sample.impl_for(fn(x) -> x end)
    assert is_nil Sample.impl_for(1)
    assert is_nil Sample.impl_for(1.1)
    assert is_nil Sample.impl_for([])
    assert is_nil Sample.impl_for([1, 2, 3])
    assert is_nil Sample.impl_for({})
    assert is_nil Sample.impl_for({1, 2, 3})
    assert is_nil Sample.impl_for("foo")
    assert is_nil Sample.impl_for(<<1>>)
    assert is_nil Sample.impl_for(self)
    assert is_nil Sample.impl_for(%{})
    assert is_nil Sample.impl_for(hd(:erlang.ports))
    assert is_nil Sample.impl_for(make_ref)

    assert Sample.impl_for(%ImplStruct{}) ==
           Sample.Protocol.ConsolidationTest.ImplStruct
    assert Sample.impl_for(%NoImplStruct{}) ==
           nil
  end

  test "consolidated implementations with any and tuple fallback" do
    assert WithAny.impl_for(%NoImplStruct{})      == WithAny.Any
    assert WithAny.impl_for(%ImplStruct{})        == WithAny.Any # Derived
    assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
    assert WithAny.impl_for(%{})                  == WithAny.Map
    assert WithAny.impl_for(self)                 == WithAny.Any
  end

  test "consolidation keeps docs" do
    docs = Code.get_docs(Sample, :docs)
    assert {{:ok, 1}, _, :def, [{:thing, _, nil}], "Ok"} =
           List.keyfind(docs, {:ok, 1}, 0)
  end

  test "consolidated keeps callbacks" do
    callbacks = for {:callback, info} <- Sample.__info__(:attributes), do: hd(info)
    assert callbacks != []
  end

  test "consolidation errors on missing beams" do
    defprotocol NoBeam, do: nil
    assert Protocol.consolidate(String, [])  == {:error, :not_a_protocol}
    assert Protocol.consolidate(NoBeam, [])  == {:error, :no_beam_info}
  end

  test "consolidation updates attributes" do
    assert Sample.__protocol__(:consolidated?)
    assert WithAny.__protocol__(:consolidated?)
  end

  test "consolidation extracts protocols" do
    protos = Protocol.extract_protocols([:code.lib_dir(:elixir, :ebin)])
    assert Enumerable in protos
    assert Inspect in protos
  end

  test "consolidation extracts implementations" do
    protos = Protocol.extract_impls(Enumerable, [:code.lib_dir(:elixir, :ebin)])
    assert List in protos
    assert Function in protos
  end
end

Code.require_file "test_helper.exs", __DIR__

defmodule ProtocolTest do
  use ExUnit.Case, async: true

  defprotocol Sample do
    @type t :: any
    @doc "Ok"
    @spec ok(t) :: boolean
    def ok(thing)
  end

  defprotocol WithAny do
    @fallback_to_any true
    @doc "Ok"
    def ok(thing)
  end

  defprotocol Derivable do
    def ok(a)
  end

  defimpl Derivable, for: Any do
    defmacro __deriving__(module, struct, options) do
      quote do
        defimpl Derivable, for: unquote(module) do
          def ok(arg) do
            {:ok, arg, unquote(Macro.escape(struct)), unquote(options)}
          end
        end
      end
    end

    def ok(arg) do
      {:ok, arg}
    end
  end

  defimpl WithAny, for: Map do
    def ok(map) do
      {:ok, map}
    end
  end

  defimpl WithAny, for: Any do
    def ok(any) do
      {:ok, any}
    end
  end

  defmodule NoImplStruct do
    defstruct a: 0, b: 0
  end

  defmodule ImplStruct do
    @derive [WithAny, Derivable]
    defstruct a: 0, b: 0

    defimpl Sample do
      def ok(struct) do
        Unknown.undefined(struct)
      end
    end
  end

  test "protocol implementations without any" do
    assert is_nil Sample.impl_for(:foo)
    assert is_nil Sample.impl_for(fn(x) -> x end)
    assert is_nil Sample.impl_for(1)
    assert is_nil Sample.impl_for(1.1)
    assert is_nil Sample.impl_for([])
    assert is_nil Sample.impl_for([1, 2, 3])
    assert is_nil Sample.impl_for({})
    assert is_nil Sample.impl_for({1, 2, 3})
    assert is_nil Sample.impl_for("foo")
    assert is_nil Sample.impl_for(<<1>>)
    assert is_nil Sample.impl_for(%{})
    assert is_nil Sample.impl_for(self)
    assert is_nil Sample.impl_for(hd(:erlang.ports))
    assert is_nil Sample.impl_for(make_ref)

    assert Sample.impl_for(%ImplStruct{}) ==
           Sample.ProtocolTest.ImplStruct
    assert Sample.impl_for(%NoImplStruct{}) ==
           nil
  end

  test "protocol implementation with any and structs fallback" do
    assert WithAny.impl_for(%NoImplStruct{})      == WithAny.Any
    assert WithAny.impl_for(%ImplStruct{})        == WithAny.Any # Derived
    assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
    assert WithAny.impl_for(%{})                  == WithAny.Map
    assert WithAny.impl_for(self)                 == WithAny.Any
  end

  test "protocol not implemented" do
    assert_raise Protocol.UndefinedError, "protocol ProtocolTest.Sample not implemented for :foo", fn ->
      Sample.ok(:foo)
    end
  end

  test "protocol documentation" do
    import PathHelpers

    write_beam(defprotocol SampleDocsProto do
      @type t :: any
      @doc "Ok"
      @spec ok(t) :: boolean
      def ok(thing)
    end)

    docs = Code.get_docs(SampleDocsProto, :docs)
    assert {{:ok, 1}, _, :def, [{:thing, _, nil}], "Ok"} =
           List.keyfind(docs, {:ok, 1}, 0)
  end

  test "protocol keeps underlying UndefinedFunctionError" do
    assert_raise UndefinedFunctionError, fn ->
      WithAll.ok(%ImplStruct{})
    end
  end

  test "protocol defines callbacks" do
    if :erlang.system_info(:otp_release) >= '18' do
      assert get_callbacks(Sample, :ok, 1) ==
        [{:type, 9, :fun, [{:type, 9, :product, [{:user_type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]

      assert get_callbacks(WithAny, :ok, 1) ==
        [{:type, 16, :fun, [{:type, 16, :product, [{:user_type, 16, :t, []}]}, {:type, 16, :term, []}]}]
    else
      assert get_callbacks(Sample, :ok, 1) ==
        [{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]

      assert get_callbacks(WithAny, :ok, 1) ==
        [{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
    end
  end

  test "protocol defines functions and attributes" do
    assert Sample.__protocol__(:module) == Sample
    assert Sample.__protocol__(:functions) == [ok: 1]
    refute Sample.__protocol__(:consolidated?)
    assert Sample.__info__(:attributes)[:protocol] == [fallback_to_any: false]

    assert WithAny.__protocol__(:module) == WithAny
    assert WithAny.__protocol__(:functions) == [ok: 1]
    refute WithAny.__protocol__(:consolidated?)
    assert WithAny.__info__(:attributes)[:protocol] == [fallback_to_any: true]
  end

  test "defimpl" do
    module = Module.concat(Sample, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == module
    assert module.__impl__(:protocol) == Sample
    assert module.__info__(:attributes)[:impl] ==
           [protocol: Sample, for: ImplStruct]
  end

  test "defimpl with implicit derive" do
    module = Module.concat(WithAny, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == WithAny.Any
    assert module.__impl__(:protocol) == WithAny
    assert module.__info__(:attributes)[:impl] ==
           [protocol: WithAny, for: ImplStruct]
  end

  test "defimpl with explicit derive" do
    module = Module.concat(Derivable, ImplStruct)
    assert module.__impl__(:for) == ImplStruct
    assert module.__impl__(:target) == module
    assert module.__impl__(:protocol) == Derivable
    assert module.__info__(:attributes)[:impl] ==
           [protocol: Derivable, for: ImplStruct]
  end

  test "defimpl with multiple for" do
    defprotocol Multi do
      def test(a)
    end

    defimpl Multi, for: [Atom, Integer] do
      def test(a), do: a
    end

    assert Multi.test(1) == 1
    assert Multi.test(:a) == :a
  end

  defp get_callbacks(module, name, arity) do
    callbacks = for {:callback, info} <- module.__info__(:attributes), do: hd(info)
    List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
  end

  test "derives protocol implicitly" do
    struct = %ImplStruct{a: 1, b: 1}
    assert WithAny.ok(struct) == {:ok, struct}

    struct = %NoImplStruct{a: 1, b: 1}
    assert WithAny.ok(struct) == {:ok, struct}
  end

  test "derives protocol explicitly" do
    struct = %ImplStruct{a: 1, b: 1}
    assert Derivable.ok(struct) == {:ok, struct, %ImplStruct{}, []}

    assert_raise Protocol.UndefinedError, fn ->
      struct = %NoImplStruct{a: 1, b: 1}
      Derivable.ok(struct)
    end
  end

  test "derives protocol explicitly with options" do
    defmodule AnotherStruct do
      @derive [{Derivable, :ok}]
      @derive [WithAny]
      defstruct a: 0, b: 0
    end

    struct = struct AnotherStruct, a: 1, b: 1
    assert Derivable.ok(struct) ==
           {:ok, struct, struct(AnotherStruct), :ok}
  end

  test "derive protocol explicitly via API" do
    defmodule InlineStruct do
      defstruct a: 0, b: 0
    end

    require Protocol
    assert Protocol.derive(Derivable, InlineStruct, :oops) == :ok

    struct = struct InlineStruct, a: 1, b: 1
    assert Derivable.ok(struct) ==
           {:ok, struct, struct(InlineStruct), :oops}
  end

  test "derived implementation keeps local file/line info" do
    assert ProtocolTest.WithAny.ProtocolTest.ImplStruct.__info__(:compile)[:source] ==
           String.to_char_list(__ENV__.file)
  end

  test "cannot derive without any implementation" do
    assert_raise ArgumentError,
        ~r"#{inspect Sample.Any} is not available, cannot derive #{inspect Sample}", fn ->
      defmodule NotCompiled do
        @derive [Sample]
        defstruct hello: :world
      end
    end
  end
end

path = Path.expand("../ebin", __DIR__)
File.mkdir_p!(path)

compile = fn {:module, module, binary, _} ->
  File.write!("#{path}/#{module}.beam", binary)
end

defmodule Protocol.ConsolidationTest do
  use ExUnit.Case, async: true

  compile.(
    defprotocol Sample do
      @type t :: any
      @doc "Ok"
      @spec ok(t) :: boolean
      def ok(thing)
    end
  )

  compile.(
    defprotocol WithAny do
      @fallback_to_any true
      @doc "Ok"
      def ok(thing)
    end
  )

  defimpl WithAny, for: Map do
    def ok(map) do
      {:ok, map}
    end
  end

  defimpl WithAny, for: Any do
    def ok(any) do
      {:ok, any}
    end
  end

  defmodule NoImplStruct do
    defstruct a: 0, b: 0
  end

  defmodule ImplStruct do
    @derive [WithAny]
    defstruct a: 0, b: 0

    defimpl Sample do
      def ok(struct) do
        Unknown.undefined(struct)
      end
    end
  end

  Code.append_path(path)

  # Any is ignored because there is no fallback
  :code.purge(Sample)
  :code.delete(Sample)
  {:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
  :code.load_binary(Sample, 'protocol_test.exs', binary)

  # Any should be moved to the end
  :code.purge(WithAny)
  :code.delete(WithAny)
  {:ok, binary} = Protocol.consolidate(WithAny, [Any, ImplStruct, Map])
  :code.load_binary(WithAny, 'protocol_test.exs', binary)

  test "consolidated?/1" do
    assert Protocol.consolidated?(WithAny)
    refute Protocol.consolidated?(Enumerable)
  end

  test "consolidated implementations without any" do
    assert is_nil Sample.impl_for(:foo)
    assert is_nil Sample.impl_for(fn(x) -> x end)
    assert is_nil Sample.impl_for(1)
    assert is_nil Sample.impl_for(1.1)
    assert is_nil Sample.impl_for([])
    assert is_nil Sample.impl_for([1, 2, 3])
    assert is_nil Sample.impl_for({})
    assert is_nil Sample.impl_for({1, 2, 3})
    assert is_nil Sample.impl_for("foo")
    assert is_nil Sample.impl_for(<<1>>)
    assert is_nil Sample.impl_for(self)
    assert is_nil Sample.impl_for(%{})
    assert is_nil Sample.impl_for(hd(:erlang.ports))
    assert is_nil Sample.impl_for(make_ref)

    assert Sample.impl_for(%ImplStruct{}) ==
           Sample.Protocol.ConsolidationTest.ImplStruct
    assert Sample.impl_for(%NoImplStruct{}) ==
           nil
  end

  test "consolidated implementations with any and tuple fallback" do
    assert WithAny.impl_for(%NoImplStruct{})      == WithAny.Any
    assert WithAny.impl_for(%ImplStruct{})        == WithAny.Any # Derived
    assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
    assert WithAny.impl_for(%{})                  == WithAny.Map
    assert WithAny.impl_for(self)                 == WithAny.Any
  end

  test "consolidation keeps docs" do
    docs = Code.get_docs(Sample, :docs)
    assert {{:ok, 1}, _, :def, [{:thing, _, nil}], "Ok"} =
           List.keyfind(docs, {:ok, 1}, 0)
  end

  test "consolidated keeps callbacks" do
    callbacks = for {:callback, info} <- Sample.__info__(:attributes), do: hd(info)
    assert callbacks != []
  end

  test "consolidation errors on missing beams" do
    defprotocol NoBeam, do: nil
    assert Protocol.consolidate(String, [])  == {:error, :not_a_protocol}
    assert Protocol.consolidate(NoBeam, [])  == {:error, :no_beam_info}
  end

  test "consolidation updates attributes" do
    assert Sample.__protocol__(:consolidated?)
    assert WithAny.__protocol__(:consolidated?)
  end

  test "consolidation extracts protocols" do
    protos = Protocol.extract_protocols([:code.lib_dir(:elixir, :ebin)])
    assert Enumerable in protos
    assert Inspect in protos
  end

  test "consolidation extracts implementations" do
    protos = Protocol.extract_impls(Enumerable, [:code.lib_dir(:elixir, :ebin)])
    assert List in protos
    assert Function in protos
  end
end


Update bot_controller.ex

Update bot_controller.ex

defmodule PanWeb.BotController do
  require Logger
  use Pan.Web, :controller

  # To test using ngrok, call:
  #   $ ngrok http 4000

  def webhook(conn, %{ "hub.challenge" => challenge } ) do
    challenge = challenge
                |> String.to_integer()
    render conn, "webhook.json", challenge: challenge
  end

  def message(conn, %{"entry" => [%{"messaging" => [%{"message" => %{"text" => message}, "sender" => %{"id" => sender_id}}]}]}) do
    #Pan.Bot.setup_call_to_action()
    #Pan.Bot.set_greeting("Hey there, {{user_first_name}}! I'll send you podcasts related to whatever you tell me. Give it a try!")
    #Pan.Bot.mark_as_read(sender_id)
    Pan.Bot.turn_typing_indicator_on(sender_id)
    Pan.Bot.whitelist_urls()
    Pan.Bot.respond_to_message(message, sender_id)
    Pan.Bot.turn_typing_indicator_off(sender_id)
    conn
    |> send_resp(200, "ok")
  end

  def message(conn, %{"entry" => [%{"messaging" => [%{"postback" => %{"payload" => _payload}, "sender" => %{"id" => sender_id}}]}]}) do
    Pan.Bot.greet_user(sender_id)
    conn
    |> send_resp(200, "ok")
  end

  def message(conn, _params) do
    conn
    |> send_resp(200, "ok")
  end
end

defmodule Pan.BotController do
  require Logger
  use Pan.Web, :controller

  # To test using ngrok, call:
  #   $ ngrok http 4000

  def webhook(conn, %{ "hub.challenge" => challenge } ) do
    challenge = challenge
                |> String.to_integer()
    render conn, "webhook.json", challenge: challenge
  end

  def message(conn, %{"entry" => [%{"messaging" => [%{"message" => %{"text" => message}, "sender" => %{"id" => sender_id}}]}]}) do
    #Pan.Bot.setup_call_to_action()
    #Pan.Bot.set_greeting("Hey there, {{user_first_name}}! I'll send you podcasts related to whatever you tell me. Give it a try!")
    #Pan.Bot.mark_as_read(sender_id)
    Pan.Bot.turn_typing_indicator_on(sender_id)
    Pan.Bot.whitelist_urls()
    Pan.Bot.respond_to_message(message, sender_id)
    Pan.Bot.turn_typing_indicator_off(sender_id)
    conn
    |> send_resp(200, "ok")
  end

  def message(conn, %{"entry" => [%{"messaging" => [%{"postback" => %{"payload" => _payload}, "sender" => %{"id" => sender_id}}]}]}) do
    Pan.Bot.greet_user(sender_id)
    conn
    |> send_resp(200, "ok")
  end

  def message(conn, _params) do
    conn
    |> send_resp(200, "ok")
  end
end


If it's the first fetch, then reverse the order of messages

defmodule MessagesApiRouter do
  use Dynamo.Router
  use Ecto.Query
  import Cheko.RouterUtils

  prepare do
    authenticate_user!(conn)
  end

  get "/:room_id" do
    user_id = get_session(conn, :user_id)
    before_message_id = conn.params[:before]
    after_message_id  = conn.params[:after]
    room = Repo.get Room, binary_to_integer(conn.params[:room_id])

    query = cond do
      before_message_id ->
        before_message_id = binary_to_integer(before_message_id)
        from m in Message,
          order_by: [desc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id and m.id < ^before_message_id

      after_message_id ->
        after_message_id = binary_to_integer(after_message_id)
        from m in Message,
          order_by: [desc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id and m.id > ^after_message_id

      true ->
        from m in Message,
          order_by: [desc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id
    end

    room_state_query = from r in RoomUserState,
      where: r.room_id == ^room.id and r.user_id == ^user_id

    [room_state] = Repo.all room_state_query
    room_state.update([last_pinged_at: current_timestamp()])
    |> Repo.update()

    messages_attributes = lc message inlist Repo.all(query) do
      Dict.merge Message.public_attributes(message), [user: User.public_attributes(message.user.get)]
    end

    if !before_message_id && !after_message_id do
      messages_attributes = Enum.reverse(messages_attributes)
    end
    [messages: messages_attributes]
    |> json_response(conn)
  end

  post "/" do
    user_id = get_session(conn, :user_id)
    params = json_decode conn.req_body

    #TODO check if room with the room_id exists
    message_params = whitelist_params(params["message"], ["room_id", "body"])

    message = Message.new(
      body: message_params["body"],
      room_id: binary_to_integer(message_params["room_id"]),
      user_id: user_id,
      created_at: current_timestamp()
    ) |> Message.assign_message_type()

    case Message.validate(message) do
      [] ->
        saved_message = Repo.create(message)
        json_response [message: Message.public_attributes(saved_message)], conn
      errors ->
        json_response [errors: errors], conn
    end
  end

end

defmodule MessagesApiRouter do
  use Dynamo.Router
  use Ecto.Query
  import Cheko.RouterUtils

  prepare do
    authenticate_user!(conn)
  end

  get "/:room_id" do
    user_id = get_session(conn, :user_id)
    before_message_id = conn.params[:before]
    after_message_id  = conn.params[:after]
    room = Repo.get Room, binary_to_integer(conn.params[:room_id])

    query = cond do
      before_message_id ->
        before_message_id = binary_to_integer(before_message_id)
        from m in Message,
          order_by: [desc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id and m.id < ^before_message_id

      after_message_id ->
        after_message_id = binary_to_integer(after_message_id)
        from m in Message,
          order_by: [desc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id and m.id > ^after_message_id

      true ->
        from m in Message,
          order_by: [asc: m.created_at],
          limit: 20,
          preload: :user,
          where: m.room_id == ^room.id
    end

    room_state_query = from r in RoomUserState,
      where: r.room_id == ^room.id and r.user_id == ^user_id

    [room_state] = Repo.all room_state_query
    room_state.update([last_pinged_at: current_timestamp()])
    |> Repo.update()

    messages_attributes = lc message inlist Repo.all(query) do
      Dict.merge Message.public_attributes(message), [user: User.public_attributes(message.user.get)]
    end

    [messages: messages_attributes]
    |> json_response(conn)
  end

  post "/" do
    user_id = get_session(conn, :user_id)
    params = json_decode conn.req_body

    #TODO check if room with the room_id exists
    message_params = whitelist_params(params["message"], ["room_id", "body"])

    message = Message.new(
      body: message_params["body"],
      room_id: binary_to_integer(message_params["room_id"]),
      user_id: user_id,
      created_at: current_timestamp()
    ) |> Message.assign_message_type()

    case Message.validate(message) do
      [] ->
        saved_message = Repo.create(message)
        json_response [message: Message.public_attributes(saved_message)], conn
      errors ->
        json_response [errors: errors], conn
    end
  end

end


Exclude unsupported tests

Exclude unsupported tests

Code.require_file "support/test_repo.exs", __DIR__
ExUnit.start(exclude: [
  :composite_pk,
  :unique_constraint,
  :join,
  :foreign_key_constraint,
  :returning,
  :with_conflict_target,
  :without_conflict_target,
  :invalid_prefix,

])

# Configure Ecto for support and tests
Application.put_env(:ecto, :primary_key_type, :id)

# Load support files
Code.require_file "../deps/ecto/integration_test/support/file_helpers.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/migration.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/repo.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/schemas.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/types.exs", __DIR__

Application.put_env(:ecto, Ecto.Integration.TestRepo,
  adapter: Ecto.Adapters.Mnesia)

defmodule Ecto.Integration.TestRepo do
  use Ecto.Integration.Repo, otp_app: :ecto
end

Application.put_env(:ecto, Ecto.Integration.PoolRepo,
  adapter: Ecto.Adapters.Mnesia)

defmodule Ecto.Integration.PoolRepo do
  use Ecto.Integration.Repo, otp_app: :ecto

  def create_prefix(prefix) do
    "create schema #{prefix}"
  end

  def drop_prefix(prefix) do
    "drop schema #{prefix}"
  end
end

defmodule Ecto.Integration.Case do
  use ExUnit.CaseTemplate

  setup _ do
    :mnesia.clear_table(:posts)
    :mnesia.clear_table(:comments)
    :mnesia.clear_table(:permalinks)
    :mnesia.clear_table(:posts_users_pk)
    :mnesia.clear_table(:users)
    :mnesia.clear_table(:customs)
    :mnesia.clear_table(:barebones)
    :mnesia.clear_table(:tags)
    :mnesia.clear_table(:orders)
    :mnesia.clear_table(:composite_pk)
    :mnesia.clear_table(:posts_users_composite_pk)
    :ok
  end
end

{:ok, _} = Ecto.Adapters.Mnesia.ensure_all_started(Ecto.Integration.TestRepo, :temporary)

# Load up the repository, start it, and run migrations
_ = Ecto.Adapters.Mnesia.storage_down(Ecto.Integration.TestRepo.config)
_ = Ecto.Adapters.Mnesia.storage_up(Ecto.Integration.TestRepo.config)
_ = Ecto.Mnesia.Storage.storage_up(TestRepo.config)

{:ok, _pid} = Ecto.Integration.TestRepo.start_link
{:ok, _pid} = Ecto.Integration.PoolRepo.start_link
{:ok, _pid} = TestRepo.start_link()

Ecto.Migrator.up(Ecto.Integration.TestRepo, 0, Ecto.Integration.Migration, log: false)
Ecto.Migrator.up(TestRepo, 1, TestRepoMigrations)

Process.flag(:trap_exit, true)

Code.require_file "support/test_repo.exs", __DIR__
ExUnit.start()

# Configure Ecto for support and tests
Application.put_env(:ecto, :primary_key_type, :id)

# Load support files
Code.require_file "../deps/ecto/integration_test/support/file_helpers.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/migration.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/repo.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/schemas.exs", __DIR__
Code.require_file "../deps/ecto/integration_test/support/types.exs", __DIR__

Application.put_env(:ecto, Ecto.Integration.TestRepo,
  adapter: Ecto.Adapters.Mnesia)

defmodule Ecto.Integration.TestRepo do
  use Ecto.Integration.Repo, otp_app: :ecto
end

Application.put_env(:ecto, Ecto.Integration.PoolRepo,
  adapter: Ecto.Adapters.Mnesia)

defmodule Ecto.Integration.PoolRepo do
  use Ecto.Integration.Repo, otp_app: :ecto

  def create_prefix(prefix) do
    "create schema #{prefix}"
  end

  def drop_prefix(prefix) do
    "drop schema #{prefix}"
  end
end

defmodule Ecto.Integration.Case do
  use ExUnit.CaseTemplate

  setup _ do
    :mnesia.clear_table(:posts)
    :mnesia.clear_table(:comments)
    :mnesia.clear_table(:permalinks)
    :mnesia.clear_table(:posts_users_pk)
    :mnesia.clear_table(:users)
    :mnesia.clear_table(:customs)
    :mnesia.clear_table(:barebones)
    :mnesia.clear_table(:tags)
    :mnesia.clear_table(:orders)
    :mnesia.clear_table(:composite_pk)
    :mnesia.clear_table(:posts_users_composite_pk)
    :ok
  end
end

{:ok, _} = Ecto.Adapters.Mnesia.ensure_all_started(Ecto.Integration.TestRepo, :temporary)

# Load up the repository, start it, and run migrations
_ = Ecto.Adapters.Mnesia.storage_down(Ecto.Integration.TestRepo.config)
_ = Ecto.Adapters.Mnesia.storage_up(Ecto.Integration.TestRepo.config)
_ = Ecto.Mnesia.Storage.storage_up(TestRepo.config)

{:ok, _pid} = Ecto.Integration.TestRepo.start_link
{:ok, _pid} = Ecto.Integration.PoolRepo.start_link
{:ok, _pid} = TestRepo.start_link()

Ecto.Migrator.up(Ecto.Integration.TestRepo, 0, Ecto.Integration.Migration, log: false)
Ecto.Migrator.up(TestRepo, 1, TestRepoMigrations)

Process.flag(:trap_exit, true)


Fix missing assigns in Member/create form

defmodule Gatekeeper.MemberController do
  use Gatekeeper.Web, :controller

  alias Gatekeeper.Company
  alias Gatekeeper.Member
  alias Gatekeeper.DoorGroup
  alias Gatekeeper.DoorGroupMember

  plug :scrub_params, "member" when action in [:create, :update]

  def index(conn, %{"company_id" => company_id}) do
    company = Repo.get!(Company, company_id) |> Repo.preload(:members)
    render(conn, "index.html", company: company, members: company.members)
  end

  def new(conn, %{"company_id" => company_id}) do
    company = Repo.get!(Company, company_id)
    member = %Member{} |> Repo.preload([:door_groups])
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset member

    render(conn, "new.html", changeset: changeset, company: company, member: member, door_groups: door_groups)
  end

  def create(conn, %{"company_id" => company_id, "member" => member_params}) do
    company = Repo.get!(Company, company_id)
    member = %Member{} |> Repo.preload([:door_groups])
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset(%Member{company_id: String.to_integer(company_id)}, member_params)

    case Repo.insert(changeset) do
      {:ok, member} ->
        save_door_groups(member, member_params["door_groups"])
        conn
        |> put_flash(:info, "Member created successfully.")
        |> redirect(to: company_path(conn, :show, company))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset, company: company, member: member, door_groups: door_groups)
    end
  end

  def show(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload([:door_groups, :rfid_tokens, door_access_attempts: [:rfid_token, :door]])
    render(conn, "show.html", company: company, member: member)
  end

  def edit(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload([:door_groups])
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset(member)
    render(conn, "edit.html", company: company, member: member, changeset: changeset, door_groups: door_groups)
  end

  def update(conn, %{"company_id" => company_id, "id" => id, "member" => member_params}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload(:door_groups)
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset(member, member_params)

    case Repo.update(changeset) do
      {:ok, company} ->
        save_door_groups(member, member_params["door_groups"])
        conn
        |> put_flash(:info, "Member updated successfully.")
        |> redirect(to: company_member_path(conn, :show, company, member))
      {:error, changeset} ->
        render(conn, "edit.html", company: company, member: member, changeset: changeset, door_groups: door_groups)
    end
  end

  def delete(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id)

    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(member)

    conn
    |> put_flash(:info, "Member deleted successfully.")
    |> redirect(to: company_path(conn, :show, company))
  end

  def save_door_groups(member, new_door_group_ids) do
    # Remove all existing door <-> door group associations
    Ecto.Query.from(door_group_member in DoorGroupMember, where: door_group_member.member_id == ^member.id) |> Repo.delete_all

    # Insert new door <-> door group associations based on provided checkboxes
    if new_door_group_ids do # can be nil if no boxes were checked
      for {id, _val} <- new_door_group_ids do
        changeset = DoorGroupMember.changeset(%DoorGroupMember{}, %{member_id: member.id, door_group_id: id})
        {:ok, _} = Repo.insert(changeset)
      end
    end
  end
end

defmodule Gatekeeper.MemberController do
  use Gatekeeper.Web, :controller

  alias Gatekeeper.Company
  alias Gatekeeper.Member
  alias Gatekeeper.DoorGroup
  alias Gatekeeper.DoorGroupMember

  plug :scrub_params, "member" when action in [:create, :update]

  def index(conn, %{"company_id" => company_id}) do
    company = Repo.get!(Company, company_id) |> Repo.preload(:members)
    render(conn, "index.html", company: company, members: company.members)
  end

  def new(conn, %{"company_id" => company_id}) do
    company = Repo.get!(Company, company_id)
    member = %Member{} |> Repo.preload([:door_groups])
    changeset = Member.changeset member
    door_groups = Repo.all(DoorGroup)
    render(conn, "new.html", changeset: changeset, company: company, member: member, door_groups: door_groups)
  end

  def create(conn, %{"company_id" => company_id, "member" => member_params}) do
    company = Repo.get!(Company, company_id)
    changeset = Member.changeset(%Member{company_id: String.to_integer(company_id)}, member_params)

    case Repo.insert(changeset) do
      {:ok, member} ->
        save_door_groups(member, member_params["door_groups"])
        conn
        |> put_flash(:info, "Member created successfully.")
        |> redirect(to: company_path(conn, :show, company))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset, company: company)
    end
  end

  def show(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload([:door_groups, :rfid_tokens, door_access_attempts: [:rfid_token, :door]])
    render(conn, "show.html", company: company, member: member)
  end

  def edit(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload([:door_groups])
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset(member)
    render(conn, "edit.html", company: company, member: member, changeset: changeset, door_groups: door_groups)
  end

  def update(conn, %{"company_id" => company_id, "id" => id, "member" => member_params}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id) |> Repo.preload(:door_groups)
    door_groups = Repo.all(DoorGroup)
    changeset = Member.changeset(member, member_params)

    case Repo.update(changeset) do
      {:ok, company} ->
        save_door_groups(member, member_params["door_groups"])
        conn
        |> put_flash(:info, "Member updated successfully.")
        |> redirect(to: company_member_path(conn, :show, company, member))
      {:error, changeset} ->
        render(conn, "edit.html", company: company, member: member, changeset: changeset, door_groups: door_groups)
    end
  end

  def delete(conn, %{"company_id" => company_id, "id" => id}) do
    company = Repo.get!(Company, company_id)
    member = Repo.get!(Member, id)

    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(member)

    conn
    |> put_flash(:info, "Member deleted successfully.")
    |> redirect(to: company_path(conn, :show, company))
  end

  def save_door_groups(member, new_door_group_ids) do
    # Remove all existing door <-> door group associations
    Ecto.Query.from(door_group_member in DoorGroupMember, where: door_group_member.member_id == ^member.id) |> Repo.delete_all

    # Insert new door <-> door group associations based on provided checkboxes
    if new_door_group_ids do # can be nil if no boxes were checked
      for {id, _val} <- new_door_group_ids do
        changeset = DoorGroupMember.changeset(%DoorGroupMember{}, %{member_id: member.id, door_group_id: id})
        {:ok, _} = Repo.insert(changeset)
      end
    end
  end
end


fixes 'deprecated :microsecends' warning

defmodule Ello.Events.Sidekiq do

  @doc "Sidekiq queue to enqueue in"
  @callback queue() :: String.t

  @doc "Sidekiq worker to process the event"
  @callback worker() :: String.t

  @doc "List of arguments to pass into Sidekiq"
  @callback args(opts :: struct) :: [any()]

  defmacro __using__(_) do
    quote do
      @behaviour Ello.Events
      @behaviour Ello.Events.Sidekiq

      def handler, do: Ello.Events.Sidekiq
      def queue, do: "default"
      def worker, do: List.last(Module.split(__MODULE__))

      defoverridable [queue: 0, worker: 0, handler: 0]
    end
  end

  def publish(module, struct) do
    Task.async fn ->
      payload = module
                |> build_job(struct)
                |> Jason.encode!()

      redis(["LPUSH", "sidekiq:queue:#{module.queue()}", payload])
    end
  end

  @max_retries 10

  defp build_job(module, struct) do
    %{
      queue: module.queue,
      retry: @max_retries,
      class: module.worker,
      args: module.args(struct),
      jid: UUID.uuid4,
      enqueued_at: unix_now_float(),
    }
  end

  defp unix_now_float do
    DateTime.to_unix(DateTime.utc_now(), :microsecond) / 1_000_000
  end

  defp redis(args) do
    case Application.get_env(:ello_events, :redis, {Ello.Core.Redis, :command}) do
      {mod, fun}                   -> apply(mod, fun, [args])
      fun when is_function(fun, 1) -> fun.(args)
    end
  end

end

defmodule Ello.Events.Sidekiq do

  @doc "Sidekiq queue to enqueue in"
  @callback queue() :: String.t

  @doc "Sidekiq worker to process the event"
  @callback worker() :: String.t

  @doc "List of arguments to pass into Sidekiq"
  @callback args(opts :: struct) :: [any()]

  defmacro __using__(_) do
    quote do
      @behaviour Ello.Events
      @behaviour Ello.Events.Sidekiq

      def handler, do: Ello.Events.Sidekiq
      def queue, do: "default"
      def worker, do: List.last(Module.split(__MODULE__))

      defoverridable [queue: 0, worker: 0, handler: 0]
    end
  end

  def publish(module, struct) do
    Task.async fn ->
      payload = module
                |> build_job(struct)
                |> Jason.encode!()

      redis(["LPUSH", "sidekiq:queue:#{module.queue()}", payload])
    end
  end

  @max_retries 10

  defp build_job(module, struct) do
    %{
      queue: module.queue,
      retry: @max_retries,
      class: module.worker,
      args: module.args(struct),
      jid: UUID.uuid4,
      enqueued_at: unix_now_float(),
    }
  end

  defp unix_now_float do
    DateTime.to_unix(DateTime.utc_now(), :microseconds) / 1_000_000
  end

  defp redis(args) do
    case Application.get_env(:ello_events, :redis, {Ello.Core.Redis, :command}) do
      {mod, fun}                   -> apply(mod, fun, [args])
      fun when is_function(fun, 1) -> fun.(args)
    end
  end

end


Add `dict.keys`, `dict.values` and `dict.size` operations

defmodule Genomu.Module.Dict do
  use Genomu.Module, id: 3, name: :dict

  @nil_value MsgPack.pack(nil)

  @args 1
  def value(MsgPack.fix_map(len: 0), _key) do
    @nil_value
  end
  def value(MsgPack.fix_map(rest: rest), key) do
    value_(rest, key)
  end
  def value(MsgPack.map16(rest: rest), key) do
    value_(rest, key)
  end
  def value(MsgPack.map32(rest: rest), key) do
    value_(rest, key)
  end

  defp value_("", _key), do: @nil_value
  defp value_(bin, key) do
    sz = byte_size(key)
    case bin do
      << ^key :: [binary, size(sz)], rest :: binary >> ->
        {next, _rest} = MsgPack.next(rest)
        next
      _ ->
       {_key, bin} = MsgPack.next(bin)
       {_value, bin} = MsgPack.next(bin)
       value_(bin, key)
    end
  end

  @args 2
  @name :value
  def set(MsgPack.fix_map(len: 15, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map16(len: 16, rest: new)
    else
      MsgPack.fix_map(len: 15, rest: new)
    end
  end
  def set(MsgPack.fix_map(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.fix_map(len: len + 1, rest: new)
    else
      MsgPack.fix_map(len: len, rest: new)
    end
  end
  def set(MsgPack.map16(len: 0x10000, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map32(len: 0x10000 + 1, rest: new)
    else
      MsgPack.map16(len: 15, rest: new)
    end
  end
  def set(MsgPack.map16(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map16(len: len + 1, rest: new)
    else
      MsgPack.map16(len: len, rest: new)
    end
  end
  def set(MsgPack.map32(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map32(len: len + 1, rest: new)
    else
      MsgPack.map32(len: len, rest: new)
    end
  end
  def set(@nil_value, MsgPack.fix_array(len: 2, rest: pair)) do
    MsgPack.fix_map(len: 1, rest: pair)
  end

  defp set_(bin, key, new_val) do
    set_(bin, key, new_val, "")
  end

  defp set_("", key, new_val, acc) do
    {acc <> key <> new_val, true}
  end
  defp set_(bin, key, new_val, acc) do
    sz = byte_size(key)
    case bin do
      << ^key :: [binary, size(sz)], rest :: binary >> ->
        {_old_val, rest} = MsgPack.next(rest)
        {acc <> key <> new_val <> rest, false}
      _ ->
        {another_key, bin} = MsgPack.next(bin)
        {val, bin} = MsgPack.next(bin)
        set_(bin, key, new_val, acc <> another_key <> val)
    end
  end

  @args 0
  def keys(MsgPack.fix_map(rest: rest), _no_arg) do
    keys_(rest)
  end
  def keys(MsgPack.map16(rest: rest), _no_arg) do
    keys_(rest)
  end
  def keys(MsgPack.map32(rest: rest), _no_arg) do
    keys_(rest)
  end

  defp keys_(bin) do
    keys_(bin, 0, "")
  end

  defp keys_("", length, acc), do: arr_(length, acc)
  defp keys_(bin, length, acc) do
    {key, bin} = MsgPack.next(bin)
    {_val, bin} = MsgPack.next(bin)
    keys_(bin, length + 1, acc <> key)
  end

  @args 0
  def values(MsgPack.fix_map(rest: rest), _no_arg) do
    values_(rest)
  end
  def values(MsgPack.map16(rest: rest), _no_arg) do
    values_(rest)
  end
  def values(MsgPack.map32(rest: rest), _no_arg) do
    values_(rest)
  end

  defp values_(bin) do
    values_(bin, 0, "")
  end

  defp values_("", length, acc), do: arr_(length, acc)
  defp values_(bin, length, acc) do
    {_key, bin} = MsgPack.next(bin)
    {val, bin} = MsgPack.next(bin)
    values_(bin, length + 1, acc <> val)
  end

  @args 0
  def size(MsgPack.fix_map(len: len), _no_arg) do
    MsgPack.pack(len)
  end
  def size(MsgPack.map16(len: len), _no_arg) do
    MsgPack.pack(len)
  end
  def size(MsgPack.map32(len: len), _no_arg) do
    MsgPack.pack(len)
  end

  defp arr_(length, bin) when length < 16 do
    MsgPack.fix_array(len: length, rest: bin)
  end
  defp arr_(length, bin) when length < 0x10000 do
    MsgPack.array16(len: length, rest: bin)
  end
  defp arr_(length, bin) do
    MsgPack.array32(len: length, rest: bin)
  end

end

defmodule Genomu.Module.Dict do
  use Genomu.Module, id: 3, name: :dict

  @nil_value MsgPack.pack(nil)

  @args 1
  def value(MsgPack.fix_map(len: 0), _key) do
    @nil_value
  end
  def value(MsgPack.fix_map(rest: rest), key) do
    value_(rest, key)
  end
  def value(MsgPack.map16(rest: rest), key) do
    value_(rest, key)
  end
  def value(MsgPack.map32(rest: rest), key) do
    value_(rest, key)
  end

  defp value_("", _key), do: @nil_value
  defp value_(bin, key) do
    sz = byte_size(key)
    case bin do
      << ^key :: [binary, size(sz)], rest :: binary >> ->
        {next, _rest} = MsgPack.next(rest)
        next
      _ ->
       {_key, bin} = MsgPack.next(bin)
       {_value, bin} = MsgPack.next(bin)
       value_(bin, key)
    end
  end

  @args 2
  @name :value
  def set(MsgPack.fix_map(len: 15, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map16(len: 16, rest: new)
    else
      MsgPack.fix_map(len: 15, rest: new)
    end
  end
  def set(MsgPack.fix_map(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.fix_map(len: len + 1, rest: new)
    else
      MsgPack.fix_map(len: len, rest: new)
    end
  end
  def set(MsgPack.map16(len: 0x10000, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map32(len: 0x10000 + 1, rest: new)
    else
      MsgPack.map16(len: 15, rest: new)
    end
  end
  def set(MsgPack.map16(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map16(len: len + 1, rest: new)
    else
      MsgPack.map16(len: len, rest: new)
    end
  end
  def set(MsgPack.map32(len: len, rest: rest), MsgPack.fix_array(len: 2, rest: pair)) do
    {key, new_val} = MsgPack.next(pair)
    {new, appendp} = set_(rest, key, new_val)
    if appendp do
      MsgPack.map32(len: len + 1, rest: new)
    else
      MsgPack.map32(len: len, rest: new)
    end
  end
  def set(@nil_value, MsgPack.fix_array(len: 2, rest: pair)) do
    MsgPack.fix_map(len: 1, rest: pair)
  end

  defp set_(bin, key, new_val) do
    set_(bin, key, new_val, "")
  end

  defp set_("", key, new_val, acc) do
    {acc <> key <> new_val, true}
  end
  defp set_(bin, key, new_val, acc) do
    sz = byte_size(key)
    case bin do
      << ^key :: [binary, size(sz)], rest :: binary >> ->
        {_old_val, rest} = MsgPack.next(rest)
        {acc <> key <> new_val <> rest, false}
      _ ->
        {another_key, bin} = MsgPack.next(bin)
        {val, bin} = MsgPack.next(bin)
        set_(bin, key, new_val, acc <> another_key <> val)
    end
  end

end


Fix broken code from last commit

Fix broken code from last commit

defmodule ReleaseManager.Plugin do
  @moduledoc """
  This module provide a simple way to add additional steps to
  the release task.

  You can define your own plugins using the sample definition below. Note that
  the module namespace must be nested under `ReleaseManager.Plugin.*`.

      defmodule ReleaseManager.Plugin.Hello do
        use ReleaseManager.Plugin

        def before_release(%Config{} = config) do
          info "This is executed just prior to compiling the release"
        end

        def after_release(%Config{} = config) do
          info "This is executed just after compiling the release"
        end

        def after_package(%Config{} = config) do
          info "This is executed just after packaging the release"
        end

        def after_cleanup(_args) do
          info "This is executed just after running cleanup"
        end
      end

  A couple things are imported or aliased for you. Those things are:

    - The ReleaseManager.Config struct is aliased for you to just Config
    - `debug/1`, `info/1`, `warn/1`, `notice/1`, and `error/1` are imported for you.
      These should be used to do any output for the user.

  `before_release/1` and `after_release/1` are required callbacks, and will each be passed a
  `Config` struct, containing the configuration for the release task. You can choose
  to return the config struct modified or unmodified, or not at all. In the former case,
  any modifications you made will be passed on to the remaining plugins and the final
  release task. The required callback `after_cleanup/1` is passed the command line arguments.
  The return value is not used.

  All plugins are executed just prior, and just after compiling the release, as the name of
  the callbacks reflect. The `before_release/1` callback is called after some internal tasks,
  such as generating the sys.config and others.
  """
  use Behaviour

  @doc """
  A plugin needs to implement `before_release/1`, and `after_release/1`
  both of which receive a %ReleaseManager.Config struct, as well as `after_cleanup/1`, which
  receives the arguments given for the command as a list of strings.
  """
  @callback before_release(ReleaseManager.Config.t) :: any
  @callback after_release(ReleaseManager.Config.t) :: any
  @callback after_package(ReleaseManager.Config.t) :: any
  @callback after_cleanup([String.t]) :: any

  @doc false
  defmacro __using__(_opts) do
    quote do
      @behaviour ReleaseManager.Plugin
      alias  ReleaseManager.Config
      alias  ReleaseManager.Utils.Logger
      import Logger, only: [debug: 1, info: 1, warn: 1, notice: 1, error: 1]

      Module.register_attribute __MODULE__, :name, accumulate: false, persist: true
      Module.register_attribute __MODULE__, :moduledoc, accumulate: false, persist: true
      Module.register_attribute __MODULE__, :shortdoc, accumulate: false, persist: true
    end
  end

  @doc """
  Loads all plugins in all code paths.
  """
  @spec load_all() :: [] | [atom]
  def load_all, do: get_plugins(ReleaseManager.Plugin)

  # Loads all modules that extend a given module in the current code path.
  #
  # The convention is that it will fetch modules with the same root namespace,
  # and that are suffixed with the name of the module they are extending.
  @spec get_plugins(atom) :: [] | [atom]
  defp get_plugins(plugin_type) when is_atom(plugin_type) do
    available_modules(plugin_type) |> Enum.reduce([], &load_plugin/2)
  end

  defp load_plugin(module, modules) do
    if Code.ensure_loaded?(module), do: [module | modules], else: modules
  end

  defp available_modules(plugin_type) do
    # Ensure the current projects code path is loaded
    Mix.Task.run("loadpaths", [])
    # Fetch all .beam files
    Path.wildcard(Path.join([Mix.Project.build_path, "**/ebin/**/*.beam"]))
    # Parse the BEAM for behaviour implementations
    |> Stream.map(fn path ->
      case :beam_lib.chunks(path, [:attributes]) do
        {:ok, {mod, chunks}}  ->
          {mod, get_in(chunks, [:attributes, :behaviour])}
        _ ->
          :error
      end
    end)
    # Filter out behaviours we don't care about and duplicates
    |> Stream.filter(fn :error -> false; {_mod, behaviours} -> is_list(behaviours) && plugin_type in behaviours end)
    |> Enum.uniq
    |> Enum.map(fn {module, _} -> module end)
  end
end

defmodule ReleaseManager.Plugin do
  @moduledoc """
  This module provide a simple way to add additional steps to
  the release task.

  You can define your own plugins using the sample definition below. Note that
  the module namespace must be nested under `ReleaseManager.Plugin.*`.

      defmodule ReleaseManager.Plugin.Hello do
        use ReleaseManager.Plugin

        def before_release(%Config{} = config) do
          info "This is executed just prior to compiling the release"
        end

        def after_release(%Config{} = config) do
          info "This is executed just after compiling the release"
        end

        def after_package(%Config{} = config) do
          info "This is executed just after packaging the release"
        end

        def after_cleanup(_args) do
          info "This is executed just after running cleanup"
        end
      end

  A couple things are imported or aliased for you. Those things are:

    - The ReleaseManager.Config struct is aliased for you to just Config
    - `debug/1`, `info/1`, `warn/1`, `notice/1`, and `error/1` are imported for you.
      These should be used to do any output for the user.

  `before_release/1` and `after_release/1` are required callbacks, and will each be passed a
  `Config` struct, containing the configuration for the release task. You can choose
  to return the config struct modified or unmodified, or not at all. In the former case,
  any modifications you made will be passed on to the remaining plugins and the final
  release task. The required callback `after_cleanup/1` is passed the command line arguments.
  The return value is not used.

  All plugins are executed just prior, and just after compiling the release, as the name of
  the callbacks reflect. The `before_release/1` callback is called after some internal tasks,
  such as generating the sys.config and others.
  """
  use Behaviour

  @doc """
  A plugin needs to implement `before_release/1`, and `after_release/1`
  both of which receive a %ReleaseManager.Config struct, as well as `after_cleanup/1`, which
  receives the arguments given for the command as a list of strings.
  """
  @callback before_release(ReleaseManager.Config.t) :: any
  @callback after_release(ReleaseManager.Config.t) :: any
  @callback after_package(ReleaseManager.Config.t) :: any
  @callback after_cleanup([String.t]) :: any

  @doc false
  defmacro __using__(_opts) do
    quote do
      @behaviour ReleaseManager.Plugin
      alias  ReleaseManager.Config
      alias  ReleaseManager.Utils.Logger
      import Logger, only: [debug: 1, info: 1, warn: 1, notice: 1, error: 1]

      Module.register_attribute __MODULE__, :name, accumulate: false, persist: true
      Module.register_attribute __MODULE__, :moduledoc, accumulate: false, persist: true
      Module.register_attribute __MODULE__, :shortdoc, accumulate: false, persist: true
    end
  end

  @doc """
  Loads all plugins in all code paths.
  """
  @spec load_all() :: [] | [atom]
  def load_all, do: get_plugins(ReleaseManager.Plugin)

  # Loads all modules that extend a given module in the current code path.
  #
  # The convention is that it will fetch modules with the same root namespace,
  # and that are suffixed with the name of the module they are extending.
  @spec get_plugins(atom) :: [] | [atom]
  defp get_plugins(plugin_type) when is_atom(plugin_type) do
    available_modules(plugin_type) |> Enum.reduce([], &load_plugin/2)
  end

  defp load_plugin(module, modules) do
    if Code.ensure_loaded?(module), do: [module | modules], else: modules
  end

  defp available_modules(plugin_type) do
    # Ensure the current projects code path is loaded
    Mix.Task.run("loadpaths", [])
    # Fetch all .beam files
    Path.wildcard(Path.join([Mix.Project.build_path, "**/ebin/**/*.beam"]))
    |> Enum.map(fn {module, _beam_path} -> :code.get_object_code(module) end)
    # Parse the BEAM for behaviour implementations
    |> Stream.map(fn {_mod, beam_binary, _path} ->
      case :beam_lib.chunks(beam_binary, [:attributes]) do
        {:ok, {mod, chunks}}  ->
          {mod, get_in(chunks, [:attributes, :behaviour])}
        _ ->
          :error
      end
    end)
    # Filter out behaviours we don't care about and duplicates
    |> Stream.filter(fn :error -> false; {_mod, behaviours} -> is_list(behaviours) && plugin_type in behaviours end)
    |> Enum.uniq
    |> Enum.map(fn {module, _} -> module end)
  end
end


Update broken link to Notification API

Update broken link to Notification API

defmodule ExTwilio.Notification do
  @moduledoc """
  Represents an Notification resource in the Twilio API.

  - [Twilio docs](https://www.twilio.com/docs/notify/api/notification-resource)
  """

  defstruct sid: nil,
            date_created: nil,
            date_updated: nil,
            account_sid: nil,
            call_sid: nil,
            api_version: nil,
            log: nil,
            error_code: nil,
            more_info: nil,
            message_text: nil,
            message_date: nil,
            request_url: nil,
            request_method: nil,
            request_variables: nil,
            response_headers: nil,
            response_body: nil,
            uri: nil

  use ExTwilio.Resource, import: [:stream, :all, :find, :destroy]

  def parents, do: [:account, :call]
end

defmodule ExTwilio.Notification do
  @moduledoc """
  Represents an Notification resource in the Twilio API.

  - [Twilio docs](https://www.twilio.com/docs/api/rest/notifications)
  """

  defstruct sid: nil,
            date_created: nil,
            date_updated: nil,
            account_sid: nil,
            call_sid: nil,
            api_version: nil,
            log: nil,
            error_code: nil,
            more_info: nil,
            message_text: nil,
            message_date: nil,
            request_url: nil,
            request_method: nil,
            request_variables: nil,
            response_headers: nil,
            response_body: nil,
            uri: nil

  use ExTwilio.Resource, import: [:stream, :all, :find, :destroy]

  def parents, do: [:account, :call]
end


Fix warning from call to dynamically generated module

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

defmodule Mix.Tasks.ArchiveTest do
  use MixTest.Case

  defmodule ArchiveProject do
    def project do
      [app: :archive, version: "0.1.0", elixir: "~> 0.1.0"]
    end
  end

  defmodule ArchiveProject2 do
    def project do
      [app: :archive, version: "0.2.0"]
    end
  end

  setup do
    File.rm_rf!(tmp_path("userhome"))
    System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
    Mix.Project.push(ArchiveProject)

    on_exit(fn ->
      Mix.Local.remove_archives()
      System.delete_env("MIX_ARCHIVES")
    end)
  end

  test "archive build" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}
      assert File.regular?('archive-0.1.0.ez')

      assert_archive_content_default()
      refute has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
    end)
  end

  test "archive build with include-dot-files" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check", "--include-dot-files"])
      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}
      assert File.regular?('archive-0.1.0.ez')

      assert_archive_content_default()
      assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
    end)
  end

  def assert_archive_content_default() do
    assert File.regular?('archive-0.1.0.ez')
    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/.elixir')
    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/not_really_an.so')

    assert has_in_zip_file?(
             'archive-0.1.0.ez',
             'archive-0.1.0/ebin/Elixir.Mix.Tasks.Local.Sample.beam'
           )

    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/ebin/archive.app')
  end

  test "archive install" do
    in_fixture("archive", fn ->
      # Build and install archive
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))

      # Check that the version warning is printed after installation
      version_error =
        "warning: the archive archive-0.1.0 requires Elixir \"~> 0.1.0\" " <>
          "but you are running on v#{System.version()}"

      assert_received {:mix_shell, :error, [^version_error]}

      archive = tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin")
      assert to_charlist(archive) in :code.get_path()

      # Loading the archive should emit warning again
      Mix.Local.append_archives()
      assert_received {:mix_shell, :error, [^version_error]}

      # List archive
      Mix.Tasks.Local.run([])
      info = "mix local.sample # A local install sample"
      assert_received {:mix_shell, :info, [^info]}

      Mix.Tasks.Archive.run([])
      assert_received {:mix_shell, :info, ["* archive-0.1.0"]}

      # Run archived task
      Mix.Task.run("local.sample")
      assert_received {:mix_shell, :info, ["sample"]}
    end)
  end

  test "archive install invalid file" do
    in_fixture("archive", fn ->
      file_name = "invalid-archive-0.1.0.ez"
      assert File.regular?(file_name)

      send(self(), {:mix_shell_input, :yes?, true})

      assert_raise Mix.Error, ~r/invalid archive file/, fn ->
        Mix.Tasks.Archive.Install.run([file_name])
      end
    end)
  end

  test "archive install missing file" do
    message = ~r[Expected "./unlikely-to-exist-0.1.0.ez" to be a local file path]

    assert_raise Mix.Error, message, fn ->
      Mix.Tasks.Archive.Install.run(["./unlikely-to-exist-0.1.0.ez"])
    end
  end

  test "archive install --force" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      Mix.Tasks.Archive.Install.run(["--force"])

      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}

      Mix.Tasks.Archive.Uninstall.run(["archive-0.1.0", "--force"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  @compile {:no_warn_undefined, GitRepo.Archive}
  test "archive.install from Git" do
    in_fixture("git_repo", fn ->
      File.mkdir_p!("config")

      File.write!("config/config.exs", """
      import Config
      config :git_repo, :archive_config, true
      """)

      File.write!("lib/git_repo.ex", """
      require Application
      true = Application.compile_env!(:git_repo, :archive_config)

      defmodule GitRepo.Archive do
        def hello do
          "World"
        end
      end
      """)

      System.cmd("git", ~w[init])
      System.cmd("git", ~w[add .])
      System.cmd("git", ~w[commit -m first-commit])

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run(["git", File.cwd!()])
      assert GitRepo.Archive.hello() == "World"

      message = "Generated archive \"git_repo-0.1.0.ez\" with MIX_ENV=prod"
      assert_received {:mix_shell, :info, [^message]}

      refute File.regular?(tmp_path("userhome/.mix/archives/git_repo-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/git_repo-0.1.0/git_repo-0.1.0/ebin"))
    end)
  after
    purge([GitRepo.Archive, GitRepo.MixProject])
  end

  test "archive install, update, and uninstall life-cycle" do
    in_fixture("archive", fn ->
      # Install previous version
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      assert File.regular?("archive-0.1.0.ez")
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      assert_received {:mix_shell, :error, [_]}

      # Build new version
      Mix.Project.push(ArchiveProject2)
      Mix.Tasks.Archive.Build.run(["--no-compile"])
      assert File.regular?("archive-0.2.0.ez")

      message = "Generated archive \"archive-0.2.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}

      # Install new version
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Re-install current version should not change system
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Try to install a missing version does not remove archive
      assert_raise Mix.Error, fn ->
        Mix.Tasks.Archive.Install.run(["./archive-0.0.0.ez"])
      end

      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))

      # Load archive without warnings because there is no :elixir requirement in mix.exs
      Mix.Local.append_archives()
      refute_received {:mix_shell, :error, [_]}

      # Check uninstall confirmation
      send(self(), {:mix_shell_input, :yes?, false})
      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Remove it!
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Check old paths are unloaded
      paths = Enum.map(:code.get_path(), &List.to_string/1)
      refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
    end)
  end

  test "archive uninstall without version" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])

      send(self(), {:mix_shell_input, :yes?, false})
      Mix.Tasks.Archive.Uninstall.run(["archive"])
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Uninstall.run(["archive"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  test "archive checksum" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      assert File.regular?("archive-0.1.0.ez")
      send(self(), {:mix_shell_input, :yes?, true})

      # Install with wrong checksum
      assert_raise Mix.Error, ~r"Data does not match the given SHA-512 checksum", fn ->
        send(self(), {:mix_shell_input, :yes?, true})
        Mix.Tasks.Archive.Install.run(["--sha512", "wrong"])
      end

      # Install with correct checksum
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run(["--sha512", sha512("archive-0.1.0.ez")])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  test "archive check" do
    # Install the archive
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      Application.unload(:archive)
    end)

    assert_raise Mix.Error, ~r/Expected archive to be in the format/, fn ->
      archive_check([:archive])
    end

    assert_raise Mix.Error, ~r/Archive "archive" could not be found/, fn ->
      archive_check([{:archive, ">= 1.0.0"}])
    end

    # Load the archive
    Mix.Local.append_archives()

    message = ~r/Archive \"archive-0.1.0\" does not match requirement >= 1.0.0/

    assert_raise Mix.Error, message, fn ->
      archive_check([{:archive, ">= 1.0.0"}])
    end

    archive_check([{:archive, ">= 0.0.0"}])
  end

  defp archive_check(archives) do
    Mix.Project.pop()
    Mix.ProjectStack.post_config(archives: archives)
    Mix.Project.push(MixTest.Case.Sample)
    Mix.Tasks.Archive.Check.run([])
  end

  defp sha512(file) do
    Base.encode16(:crypto.hash(:sha512, File.read!(file)), case: :lower)
  end

  defp has_in_zip_file?(archive, name) do
    {:ok, files} = :zip.list_dir(archive)
    Enum.find(files, &match?({:zip_file, ^name, _, _, _, _}, &1))
  end
end

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

defmodule Mix.Tasks.ArchiveTest do
  use MixTest.Case

  defmodule ArchiveProject do
    def project do
      [app: :archive, version: "0.1.0", elixir: "~> 0.1.0"]
    end
  end

  defmodule ArchiveProject2 do
    def project do
      [app: :archive, version: "0.2.0"]
    end
  end

  setup do
    File.rm_rf!(tmp_path("userhome"))
    System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
    Mix.Project.push(ArchiveProject)

    on_exit(fn ->
      Mix.Local.remove_archives()
      System.delete_env("MIX_ARCHIVES")
    end)
  end

  test "archive build" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}
      assert File.regular?('archive-0.1.0.ez')

      assert_archive_content_default()
      refute has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
    end)
  end

  test "archive build with include-dot-files" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check", "--include-dot-files"])
      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}
      assert File.regular?('archive-0.1.0.ez')

      assert_archive_content_default()
      assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
    end)
  end

  def assert_archive_content_default() do
    assert File.regular?('archive-0.1.0.ez')
    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/.elixir')
    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/not_really_an.so')

    assert has_in_zip_file?(
             'archive-0.1.0.ez',
             'archive-0.1.0/ebin/Elixir.Mix.Tasks.Local.Sample.beam'
           )

    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/ebin/archive.app')
  end

  test "archive install" do
    in_fixture("archive", fn ->
      # Build and install archive
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))

      # Check that the version warning is printed after installation
      version_error =
        "warning: the archive archive-0.1.0 requires Elixir \"~> 0.1.0\" " <>
          "but you are running on v#{System.version()}"

      assert_received {:mix_shell, :error, [^version_error]}

      archive = tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin")
      assert to_charlist(archive) in :code.get_path()

      # Loading the archive should emit warning again
      Mix.Local.append_archives()
      assert_received {:mix_shell, :error, [^version_error]}

      # List archive
      Mix.Tasks.Local.run([])
      info = "mix local.sample # A local install sample"
      assert_received {:mix_shell, :info, [^info]}

      Mix.Tasks.Archive.run([])
      assert_received {:mix_shell, :info, ["* archive-0.1.0"]}

      # Run archived task
      Mix.Task.run("local.sample")
      assert_received {:mix_shell, :info, ["sample"]}
    end)
  end

  test "archive install invalid file" do
    in_fixture("archive", fn ->
      file_name = "invalid-archive-0.1.0.ez"
      assert File.regular?(file_name)

      send(self(), {:mix_shell_input, :yes?, true})

      assert_raise Mix.Error, ~r/invalid archive file/, fn ->
        Mix.Tasks.Archive.Install.run([file_name])
      end
    end)
  end

  test "archive install missing file" do
    message = ~r[Expected "./unlikely-to-exist-0.1.0.ez" to be a local file path]

    assert_raise Mix.Error, message, fn ->
      Mix.Tasks.Archive.Install.run(["./unlikely-to-exist-0.1.0.ez"])
    end
  end

  test "archive install --force" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      Mix.Tasks.Archive.Install.run(["--force"])

      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}

      Mix.Tasks.Archive.Uninstall.run(["archive-0.1.0", "--force"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  test "archive.install from Git" do
    in_fixture("git_repo", fn ->
      File.mkdir_p!("config")

      File.write!("config/config.exs", """
      import Config
      config :git_repo, :archive_config, true
      """)

      File.write!("lib/git_repo.ex", """
      require Application
      true = Application.compile_env!(:git_repo, :archive_config)

      defmodule GitRepo.Archive do
        def hello do
          "World"
        end
      end
      """)

      System.cmd("git", ~w[init])
      System.cmd("git", ~w[add .])
      System.cmd("git", ~w[commit -m first-commit])

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run(["git", File.cwd!()])
      assert GitRepo.Archive.hello() == "World"

      message = "Generated archive \"git_repo-0.1.0.ez\" with MIX_ENV=prod"
      assert_received {:mix_shell, :info, [^message]}

      refute File.regular?(tmp_path("userhome/.mix/archives/git_repo-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/git_repo-0.1.0/git_repo-0.1.0/ebin"))
    end)
  after
    purge([GitRepo.Archive, GitRepo.MixProject])
  end

  test "archive install, update, and uninstall life-cycle" do
    in_fixture("archive", fn ->
      # Install previous version
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      assert File.regular?("archive-0.1.0.ez")
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      assert_received {:mix_shell, :error, [_]}

      # Build new version
      Mix.Project.push(ArchiveProject2)
      Mix.Tasks.Archive.Build.run(["--no-compile"])
      assert File.regular?("archive-0.2.0.ez")

      message = "Generated archive \"archive-0.2.0.ez\" with MIX_ENV=dev"
      assert_received {:mix_shell, :info, [^message]}

      # Install new version
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Re-install current version should not change system
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Try to install a missing version does not remove archive
      assert_raise Mix.Error, fn ->
        Mix.Tasks.Archive.Install.run(["./archive-0.0.0.ez"])
      end

      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))

      # Load archive without warnings because there is no :elixir requirement in mix.exs
      Mix.Local.append_archives()
      refute_received {:mix_shell, :error, [_]}

      # Check uninstall confirmation
      send(self(), {:mix_shell_input, :yes?, false})
      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Remove it!
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))

      # Check old paths are unloaded
      paths = Enum.map(:code.get_path(), &List.to_string/1)
      refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
    end)
  end

  test "archive uninstall without version" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])

      send(self(), {:mix_shell_input, :yes?, false})
      Mix.Tasks.Archive.Uninstall.run(["archive"])
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))

      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Uninstall.run(["archive"])
      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  test "archive checksum" do
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      assert File.regular?("archive-0.1.0.ez")
      send(self(), {:mix_shell_input, :yes?, true})

      # Install with wrong checksum
      assert_raise Mix.Error, ~r"Data does not match the given SHA-512 checksum", fn ->
        send(self(), {:mix_shell_input, :yes?, true})
        Mix.Tasks.Archive.Install.run(["--sha512", "wrong"])
      end

      # Install with correct checksum
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run(["--sha512", sha512("archive-0.1.0.ez")])
      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
    end)
  end

  test "archive check" do
    # Install the archive
    in_fixture("archive", fn ->
      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
      send(self(), {:mix_shell_input, :yes?, true})
      Mix.Tasks.Archive.Install.run([])
      Application.unload(:archive)
    end)

    assert_raise Mix.Error, ~r/Expected archive to be in the format/, fn ->
      archive_check([:archive])
    end

    assert_raise Mix.Error, ~r/Archive "archive" could not be found/, fn ->
      archive_check([{:archive, ">= 1.0.0"}])
    end

    # Load the archive
    Mix.Local.append_archives()

    message = ~r/Archive \"archive-0.1.0\" does not match requirement >= 1.0.0/

    assert_raise Mix.Error, message, fn ->
      archive_check([{:archive, ">= 1.0.0"}])
    end

    archive_check([{:archive, ">= 0.0.0"}])
  end

  defp archive_check(archives) do
    Mix.Project.pop()
    Mix.ProjectStack.post_config(archives: archives)
    Mix.Project.push(MixTest.Case.Sample)
    Mix.Tasks.Archive.Check.run([])
  end

  defp sha512(file) do
    Base.encode16(:crypto.hash(:sha512, File.read!(file)), case: :lower)
  end

  defp has_in_zip_file?(archive, name) do
    {:ok, files} = :zip.list_dir(archive)
    Enum.find(files, &match?({:zip_file, ^name, _, _, _, _}, &1))
  end
end


Fix firing

Fix firing

defmodule Game.Server do

  @moduledoc """
  Game process. One process per running Game.
  Each object in the game is represented by a Process.
  The game tells the processes to update themselves,
  and they report their new state to the Game.

  To use, start a Game, and periodically send it tick messages:

      {:ok, game} = Game.Server.start_link
      Game.Server.tick(game)
      ...
      Game.Server.tick(game)
      ...

  To retrieve the game state, periodically send a message:

      game_state = Game.Server.state(game)

  The game state is an object containing these properties:

  * `a` - a list of Asteroids

  """

  use GenServer

  alias Asteroid.Server, as: Asteroid
  alias Bullet.Server, as: Bullet
  alias Ship.Server, as: Ship
  alias Game.Identifiers, as: Identifiers
  alias World.Clock, as: Clock

  @initial_asteroid_count 8
  @initial_ship_count     6

  def start_link(fps \\ 0) do
    GenServer.start_link(__MODULE__, {:ok, fps}, [])
  end

  def show(pid) do
    GenServer.cast(pid, :show)
  end

  @doc """
  Update the game state. Called by external process running at
  known FPS (frame-per-second)
  """
  def tick(pid) do
    GenServer.call(pid, :tick)
  end

  @doc """
  Retrieve the game state as a Map.
  """
  def state(pid) do
    GenServer.call(pid, :state)
  end

  def update_asteroid(pid, new_state) do
    GenServer.cast(pid, {:update_asteroid, new_state})
  end

  def update_ship(pid, new_state) do
    GenServer.cast(pid, {:update_ship, new_state})
  end

  def ship_fires_bullet(pid, ship_id) do
    GenServer.cast(pid, {:ship_fires_bullet, ship_id})
  end

  def update_bullet(pid, new_state) do
    GenServer.cast(pid, {:update_bullet, new_state})
  end

  def delete_bullet(pid, id) do
    GenServer.cast(pid, {:delete_bullet, id})
  end

  ## Initial state

  @doc """
  Generate n new asteroids and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_asteroids(ids, n) do
    Enum.reduce(1..n, %{}, fn(i, rocks) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Asteroid.start_link(id)
      Map.put(rocks, i, pid)
    end)
  end

  @doc """
  Generate n new ships and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_ships(ids, n) do
    Enum.reduce(1..n, %{}, fn(i, ships) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Ship.start_link(id)
      Map.put(ships, i, pid)
    end)
  end

  ## Server Callbacks

  def init({:ok, fps}) do
    {:ok, ids} = Identifiers.start_link
    rocks = generate_asteroids(ids, @initial_asteroid_count)
    ships = generate_ships(ids, @initial_ship_count)
    game_state = %{:ids => ids,
              :pids =>  %{:asteroids => rocks,
                          :bullets => %{},
                          :ships => ships},
              :state => %{:asteroids => %{},
                          :bullets => %{},
                          :ships => %{}},
              :clock_ms => Clock.now_ms}
    start_ticker(self(), fps)
    {:ok, game_state}
  end

  @doc """
  Echo the state of the game to the console.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)

  """
  def handle_cast(:show, game) do
    game
    |> Kernel.inspect(pretty: true)
    |> IO.puts
    {:noreply, game}
  end

  def handle_cast({:update_asteroid, asteroid_state}, game) do
    id = elem(asteroid_state, 0)
    new_game = put_in(game.state.asteroids[id], asteroid_state)
    {:noreply, new_game}
  end

  def handle_cast({:update_ship, ship_state}, game) do
    id = elem(ship_state, 0)
    new_game = put_in(game.state.ships[id], ship_state)
    {:noreply, new_game}
  end

  @doc """
  Ship fires a bullet in the direction it is facing.

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 1)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 2)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 3)
      Game.Server.show(game)

  """
  def handle_cast({:ship_fires_bullet, ship_id}, game) do
    id = Identifiers.next(game.ids)
    {ship_pos, theta} = Ship.nose(game.pids.ships[ship_id])
    {:ok, b} = Bullet.start_link(id, ship_pos, theta)
    new_game = put_in(game.pids.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:update_bullet, b}, game) do
    id = elem(b, 0)
    new_game = put_in(game.state.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:delete_bullet, id}, game) do
    new_game = update_in(game.state.bullets, &Map.delete(&1, id))
    {:noreply, new_game}
  end

  @doc """
  Update the game state and return the number of ms elpased since last tick.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)
      Game.Server.tick(game)
      Game.Server.show(game)
  """
  def handle_call(:tick, _from, game) do
    elapsed_ms = Clock.now_ms - game.clock_ms

    move_asteroids(game, elapsed_ms)
    move_ships(game, elapsed_ms)
    move_bullets(game, elapsed_ms)
    fire_bullets(game)

    {:reply, {:elapsed_ms, elapsed_ms}, Map.put(game, :clock_ms, Clock.now_ms)}
  end

  def handle_call(:state, _from, game) do
    game_state = %{
      :dim => Elixoids.Space.dimensions,
      :a => game.state.asteroids |> map_of_tuples_to_list,
      :s => game.state.ships |> map_of_tuples_to_list |> map_rest,
      :x => [],
      :b => game.state.bullets |> map_of_tuples_to_list
    }
    {:reply, game_state, game}
  end

  @doc """
  Convert a map of tuples into a list of lists
  """
  def map_of_tuples_to_list(m) do
    m
    |> Map.values
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Drop the head of each list in the given list.
  """
  def map_rest(m) do
    m
    |> Enum.map(fn([_h|t]) -> t end )
  end

  defp start_ticker(pid, fps) do
    if ((fps > 0) && (fps <= 60)) do
      Game.Ticker.start_link(pid, fps)
    end
  end

  defp move_asteroids(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.asteroids),
      fn(a) -> Asteroid.move(a, elapsed_ms, self()) end)
  end

  defp move_ships(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.ships),
      fn(s) -> Ship.move(s, elapsed_ms, self()) end)
  end

  defp move_bullets(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.bullets),
      fn(s) -> Bullet.move(s, elapsed_ms, self()) end)
  end

  defp fire_bullets(game) do
    if Map.has_key?(game.pids.ships, 1) do
      Game.Server.ship_fires_bullet(self(), 1)
    end
  end

end

defmodule Game.Server do

  @moduledoc """
  Game process. One process per running Game.
  Each object in the game is represented by a Process.
  The game tells the processes to update themselves,
  and they report their new state to the Game.

  To use, start a Game, and periodically send it tick messages:

      {:ok, game} = Game.Server.start_link
      Game.Server.tick(game)
      ...
      Game.Server.tick(game)
      ...

  To retrieve the game state, periodically send a message:

      game_state = Game.Server.state(game)

  The game state is an object containing these properties:

  * `a` - a list of Asteroids

  """

  use GenServer

  alias Asteroid.Server, as: Asteroid
  alias Bullet.Server, as: Bullet
  alias Ship.Server, as: Ship
  alias Game.Identifiers, as: Identifiers
  alias World.Clock, as: Clock

  @initial_asteroid_count 8
  @initial_ship_count     6

  def start_link(fps \\ 0) do
    GenServer.start_link(__MODULE__, {:ok, fps}, [])
  end

  def show(pid) do
    GenServer.cast(pid, :show)
  end

  @doc """
  Update the game state. Called by external process running at
  known FPS (frame-per-second)
  """
  def tick(pid) do
    GenServer.call(pid, :tick)
  end

  @doc """
  Retrieve the game state as a Map.
  """
  def state(pid) do
    GenServer.call(pid, :state)
  end

  def update_asteroid(pid, new_state) do
    GenServer.cast(pid, {:update_asteroid, new_state})
  end

  def update_ship(pid, new_state) do
    GenServer.cast(pid, {:update_ship, new_state})
  end

  def ship_fires_bullet(pid, ship_id) do
    GenServer.cast(pid, {:ship_fires_bullet, ship_id})
  end

  def update_bullet(pid, new_state) do
    GenServer.cast(pid, {:update_bullet, new_state})
  end

  def delete_bullet(pid, id) do
    GenServer.cast(pid, {:delete_bullet, id})
  end

  ## Initial state

  @doc """
  Generate n new asteroids and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_asteroids(ids, n) do
    Enum.reduce(1..n, %{}, fn(i, rocks) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Asteroid.start_link(id)
      Map.put(rocks, i, pid)
    end)
  end

  @doc """
  Generate n new ships and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_ships(ids, n) do
    Enum.reduce(1..n, %{}, fn(i, ships) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Ship.start_link(id)
      Map.put(ships, i, pid)
    end)
  end

  ## Server Callbacks

  def init({:ok, fps}) do
    {:ok, ids} = Identifiers.start_link
    rocks = generate_asteroids(ids, @initial_asteroid_count)
    ships = generate_ships(ids, @initial_ship_count)
    game_state = %{:ids => ids,
              :pids =>  %{:asteroids => rocks,
                          :bullets => %{},
                          :ships => ships},
              :state => %{:asteroids => %{},
                          :bullets => %{},
                          :ships => %{}},
              :clock_ms => Clock.now_ms}
    start_ticker(self(), fps)
    {:ok, game_state}
  end

  @doc """
  Echo the state of the game to the console.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)

  """
  def handle_cast(:show, game) do
    game
    |> Kernel.inspect(pretty: true)
    |> IO.puts
    {:noreply, game}
  end

  def handle_cast({:update_asteroid, asteroid_state}, game) do
    id = elem(asteroid_state, 0)
    new_game = put_in(game.state.asteroids[id], asteroid_state)
    {:noreply, new_game}
  end

  def handle_cast({:update_ship, ship_state}, game) do
    id = elem(ship_state, 0)
    new_game = put_in(game.state.ships[id], ship_state)
    {:noreply, new_game}
  end

  @doc """
  Ship fires a bullet in the direction it is facing.

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 1)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 2)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 3)
      Game.Server.show(game)

  """
  def handle_cast({:ship_fires_bullet, ship_id}, game) do
    id = Identifiers.next(game.ids)
    {ship_pos, theta} = Ship.nose(game.pids.ships[ship_id])
    {:ok, b} = Bullet.start_link(id, ship_pos, theta)
    new_game = put_in(game.pids.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:update_bullet, b}, game) do
    id = elem(b, 0)
    new_game = put_in(game.state.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:delete_bullet, id}, game) do
    new_game = update_in(game.state.bullets, &Map.delete(&1, id))
    {:noreply, new_game}
  end

  @doc """
  Update the game state and return the number of ms elpased since last tick.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)
      Game.Server.tick(game)
      Game.Server.show(game)
  """
  def handle_call(:tick, _from, game) do
    elapsed_ms = Clock.now_ms - game.clock_ms

    move_asteroids(game, elapsed_ms)
    move_ships(game, elapsed_ms)
    move_bullets(game, elapsed_ms)
    Game.Server.ship_fires_bullet(self(), 1)

    {:reply, {:elapsed_ms, elapsed_ms}, Map.put(game, :clock_ms, Clock.now_ms)}
  end

  def handle_call(:state, _from, game) do
    game_state = %{
      :dim => Elixoids.Space.dimensions,
      :a => game.state.asteroids |> map_of_tuples_to_list,
      :s => game.state.ships |> map_of_tuples_to_list |> map_rest,
      :x => [],
      :b => game.state.bullets |> map_of_tuples_to_list
    }
    {:reply, game_state, game}
  end

  @doc """
  Convert a map of tuples into a list of lists
  """
  def map_of_tuples_to_list(m) do
    m
    |> Map.values
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Drop the head of each list in the given list.
  """
  def map_rest(m) do
    m
    |> Enum.map(fn([_h|t]) -> t end )
  end

  defp start_ticker(pid, fps) do
    if ((fps > 0) && (fps <= 60)) do
      Game.Ticker.start_link(pid, fps)
    end
  end

  defp move_asteroids(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.asteroids),
      fn(a) -> Asteroid.move(a, elapsed_ms, self()) end)
  end

  defp move_ships(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.ships),
      fn(s) -> Ship.move(s, elapsed_ms, self()) end)
  end

  defp move_bullets(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.bullets),
      fn(s) -> Bullet.move(s, elapsed_ms, self()) end)
  end

end


Remove unused post assignment from Slack integration

defmodule Tilex.Slack do
  def notify(post, developer, channel, url) do
    "#{developer.username} created a new post <#{url}|#{post.title}> ##{channel.name}"
    |> send_slack_message
  end

  def notify_of_awards(%Tilex.Post{max_likes: max_likes, title: title}, developer, url) do
    appropriate_emoji = [ "🎉", "🎂", "✨", "💥", "❤", "🎈", "👑", "🎓", "🏆", "💯 "]
                        |> Enum.at(round((max_likes / 10) - 1), "😀")

    "#{developer.username}'s post has #{max_likes} likes! #{appropriate_emoji} - <#{url}|#{title}>"
    |> send_slack_message
  end

  defp send_slack_message(message) do
    endpoint = System.get_env("slack_post_endpoint")

    spawn(
      fn ->
        HTTPoison.post("https://hooks.slack.com" <> endpoint, "{\"text\": \"#{message}\"}")
      end
    )
  end
end

defmodule Tilex.Slack do
  def notify(post, developer, channel, url) do
    "#{developer.username} created a new post <#{url}|#{post.title}> ##{channel.name}"
    |> send_slack_message
  end

  def notify_of_awards(post = %Tilex.Post{max_likes: max_likes, title: title}, developer, url) do
    appropriate_emoji = [ "🎉", "🎂", "✨", "💥", "❤", "🎈", "👑", "🎓", "🏆", "💯 "]
                        |> Enum.at(round((max_likes / 10) - 1), "😀")

    "#{developer.username}'s post has #{max_likes} likes! #{appropriate_emoji} - <#{url}|#{title}>"
    |> send_slack_message
  end

  defp send_slack_message(message) do
    endpoint = System.get_env("slack_post_endpoint")

    spawn(
      fn ->
        HTTPoison.post("https://hooks.slack.com" <> endpoint, "{\"text\": \"#{message}\"}")
      end
    )
  end
end