Fixed doc typos in List

Fixed doc typos in List

defmodule List do
  @moduledoc """
  Implements functions that only make sense for lists
  and cannot be part of the Enum protocol. In general,
  favor using the Enum API instead of List.

  A decision was taken to delegate most functions to
  Erlang's standard library but follow Elixir's convention
  of receiving the target (in this case, a list) as the
  first argument.
  """

  @compile :inline_list_funcs

  @doc """
  Given a list of lists, concatenate the sublists into a single list.

  ## Examples

      iex> List.concat([[1, [2], 3], [4], [5, 6]])
      [1,[2],3,4,5,6]

  """
  def concat(list) when is_list(list) do
    :lists.append(list)
  end

  @doc """
  Concatenates the list on the right with the list on the left.

  This function produces the same result the `++` operator.

  ## Examples

      iex> List.concat([1, 2, 3], [4, 5, 6])
      [1,2,3,4,5,6]

  """
  def concat(list, elements) when is_list(list) and is_list(elements) do
    list ++ elements
  end

  @doc """
  Deletes the given item from the list. Returns a list without the item.
  If the item occurs more than once in the list, just the first occurrence
  is removed.

  ## Examples

      iex> List.delete([1, 2, 3], 1)
      [2,3]

      iex> List.delete([1, 2, 2, 3], 2)
      [1, 2, 3]

  """
  def delete(list, item) do
    :lists.delete(item, list)
  end

  @doc """
  Duplicates the given element `n` times in a list.

  ## Examples

      iex> List.duplicate("hello", 3)
      ["hello","hello","hello"]

      iex> List.duplicate([1, 2], 2)
      [[1,2],[1,2]]
  """
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

  @doc """
  Flattens the given `list` of nested lists.

  ## Examples

      iex> List.flatten([1, [[2], 3]])
      [1,2,3]

  """
  def flatten(list) do
    :lists.flatten(list)
  end

  @doc """
  Flattens the given `list` of nested lists.
  The list `tail` will be added at the end of
  the flattened list.

  ## Examples

      iex> List.flatten([1, [[2], 3]], [4, 5])
      [1,2,3,4,5]

  """
  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

  @doc """
  Folds (reduces) the given list to the left with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
      20

      iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      2

  """
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

  @doc """
  Folds (reduces) the given list to the right with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      -2

  """
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

  @doc """
  Returns the last element in `list` or `nil` if `list` is empty.

  ## Examples

      iex> List.last([])
      nil
      iex> List.last([1])
      1
      iex> List.last([1, 2, 3])
      3

  """
  def last([]), do: nil

  def last(list) do
    :lists.last(list)
  end

  @doc """
  Receives a list of tuples and returns the first tuple
  where the item at `position` in the tuple matches the
  given `item`.

  ## Examples

      iex> List.keyfind([a: 1, b: 2], :a, 0)
      { :a, 1 }

      iex> List.keyfind([a: 1, b: 2], 2, 1)
      { :b, 2 }

      iex> List.keyfind([a: 1, b: 2], :c, 0)
      nil

  """
  def keyfind(list, key, position, default // nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

  @doc """
  Receives a list of tuples and returns `true` if there is
  a tuple where the item at `position` in the tuple matches
  the given `item`.

  ## Examples

      iex> List.keymember?([a: 1, b: 2], :a, 0)
      true

      iex> List.keymember?([a: 1, b: 2], 2, 1)
      true

      iex> List.keymember?([a: 1, b: 2], :c, 0)
      false

  """
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position` if it exists.

  ## Examples

      iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
      [a: 3, b: 2]

  """
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and sorts the items
  at `position` of the tuples. The sort is stable.

  ## Examples

      iex> List.keysort([a: 5, b: 1, c: 3], 1)
      [b: 1, c: 3, a: 5]

      iex> List.keysort([a: 5, c: 1, b: 3], 0)
      [a: 5, b: 3, c: 1]

  """
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position`. If the item
  does not exist, it is added to the end of the list.

  ## Examples

      iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
      [a: 3, b: 2]

      iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
      [a: 1, b: 2, c: 3]

  """
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and deletes the first tuple
  where the item at `position` matches the
  given `item`. Returns the new list.

  ## Examples

      iex> List.keydelete([a: 1, b: 2], :a, 0)
      [{ :b, 2 }]

      iex> List.keydelete([a: 1, b: 2], 2, 1)
      [{ :a, 1 }]

      iex> List.keydelete([a: 1, b: 2], :c, 0)
      [{ :a, 1 }, { :b, 2 }]

  """
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Wraps the argument in a list.
  If the argument is already a list, returns the list.
  If the argument is `nil`, returns an empty list.

  ## Examples

      iex> List.wrap("hello")
      ["hello"]

      iex> List.wrap([1, 2, 3])
      [1,2,3]

      iex> List.wrap(nil)
      []

  """
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

  @doc """
  Zips corresponding elements from each list in `list_of_lists`.

  ## Examples

      iex> List.zip([[1, 2], [3, 4], [5, 6]])
      [{1, 3, 5}, {2, 4, 6}]

      iex> List.zip([[1, 2], [3], [5, 6]])
      [{1, 3, 5}]

  """
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

  @doc """
  Unzips the given list of lists or tuples into separate lists and returns a
  list of lists.

  ## Examples

      iex> List.unzip([{1, 2}, {3, 4}])
      [[1, 3], [2, 4]]

      iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
      [[1, 2, 3], [:a, :b, :c]]

  """
  def unzip(list) when is_list(list) do
    :lists.map tuple_to_list(&1), zip(list)
  end

  @doc """
  Returns a list with `value` inserted at the specified `index`. Note that `index`
  is capped at the list length. Negative indices indicate an offset from the
  end of the list.

  ## Examples

      iex> List.insert_at([1, 2, 3, 4], 2, 0)
      [1, 2, 0, 3, 4]

      iex> List.insert_at([1, 2, 3], 10, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -1, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -10, 0)
      [0, 1, 2, 3]

  """
  def insert_at(list, index, value) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

  @doc """
  Returns a list with a replaced value at the specified `index`. Negative indices
  indicate an offset from the end of the list. If `index` is out of bounds, the
  original `list` is returned.

  ## Examples

      iex> List.replace_at([1, 2, 3], 0, 0)
      [0, 2, 3]

      iex> List.replace_at([1, 2, 3], 10, 0)
      [1, 2, 3]

      iex> List.replace_at([1, 2, 3], -1, 0)
      [1, 2, 0]

      iex> List.replace_at([1, 2, 3], -10, 0)
      [1, 2, 3]

  """
  def replace_at(list, index, value) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

  ## Helpers

  # replace_at

  defp do_replace_at([], _index, _value) do
    []
  end

  defp do_replace_at(list, index, _value) when index < 0 do
    list
  end

  defp do_replace_at([_old|rest], 0, value) do
    [ value | rest ]
  end

  defp do_replace_at([h|t], index, value) do
    [ h | do_replace_at(t, index - 1, value) ]
  end

  # insert_at

  defp do_insert_at([], _index, value) do
    [ value ]
  end

  defp do_insert_at(list, index, value) when index <= 0 do
    [ value | list ]
  end

  defp do_insert_at([h|t], index, value) do
    [ h | do_insert_at(t, index - 1, value) ]
  end

  # zip

  defp do_zip(list, acc) do
    converter = fn x, acc -> do_zip_each(to_list(x), acc) end
    {mlist, heads} = :lists.mapfoldl converter, [], list

    case heads do
      nil -> :lists.reverse acc
      _   -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
    end
  end

  defp do_zip_each(_, nil) do
    { nil, nil }
  end

  defp do_zip_each([h|t], acc) do
    { t, [h|acc] }
  end

  defp do_zip_each([], _) do
    { nil, nil }
  end

  defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
  defp to_list(list)  when is_list(list),   do: list
end

defmodule List do
  @moduledoc """
  Implements functions that only make sense for lists
  and cannot be part of the Enum protocol. In general,
  favor using the Enum API instead of List.

  A decision was taken to delegate most functions to
  Erlang's standard lib but following Elixir's convention
  of receiving the target (in this case, a list) as the
  first argument.
  """

  @compile :inline_list_funcs

  @doc """
  Given a list of lists, concatenates the sublists into a single list.

  ## Examples

      iex> List.concat([[1, [2], 3], [4], [5, 6]])
      [1,[2],3,4,5,6]

  """
  def concat(list) when is_list(list) do
    :lists.append(list)
  end

  @doc """
  Concatenates the list on the right with the list on the left.

  This function produces the same result the `++` operator. The only difference
  is a minor optimization: when the first list contains only one element, we
  simply add it as a head to the second list.

  ## Examples

      iex> List.concat([1, 2, 3], [4, 5, 6])
      [1,2,3,4,5,6]

  """
  def concat(list, elements) when is_list(list) and is_list(elements) do
    list ++ elements
  end

  @doc """
  Deletes the given item from the list. Returns a list without the item.
  If the item occurs more than once in the list, just the first occurrence
  is removed.

  ## Examples

      iex> List.delete([1, 2, 3], 1)
      [2,3]

  """
  def delete(list, item) do
    :lists.delete(item, list)
  end

  @doc """
  Duplicates the given element n times in a list.

  ## Examples

      iex> List.duplicate("hello", 3)
      ["hello","hello","hello"]

      iex> List.duplicate([1, 2], 2)
      [[1,2],[1,2]]
  """
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

  @doc """
  Flattens the given `list` of nested lists. An optional
  tail can be given that will be added at the end of
  the flattened list.

  ## Examples

      iex> List.flatten([1, [[2], 3]])
      [1,2,3]

      iex> List.flatten([1, [[2], 3]], [4, 5])
      [1,2,3,4,5]

  """
  def flatten(list) do
    :lists.flatten(list)
  end

  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

  @doc """
  Folds (reduces) the given list to the left with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
      20

      iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      2

  """
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

  @doc """
  Folds (reduces) the given list to the right with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      -2

  """
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

  @doc """
  Returns the last element in `list` or nil if the `list` is empty.

  ## Examples

      iex> List.last([])
      nil
      iex> List.last([1])
      1
      iex> List.last([1, 2, 3])
      3

  """
  def last([]), do: nil

  def last(list) do
    :lists.last(list)
  end

  @doc """
  Receives a list of tuples and returns the first tuple
  where the item at position `position` matches with the
  given `item`.

  ## Examples

      iex> List.keyfind([a: 1, b: 2], :a, 0)
      { :a, 1 }

      iex> List.keyfind([a: 1, b: 2], 2, 1)
      { :b, 2 }

      iex> List.keyfind([a: 1, b: 2], :c, 0)
      nil

  """
  def keyfind(list, key, position, default // nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

  @doc """
  Receives a list of tuples and returns true if there is
  a tuple where the item at position `position` matches
  with the given `item`.

  ## Examples

      iex> List.keymember?([a: 1, b: 2], :a, 0)
      true

      iex> List.keymember?([a: 1, b: 2], 2, 1)
      true

      iex> List.keymember?([a: 1, b: 2], :c, 0)
      false

  """
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at position `pos` if it exists.

  ## Examples

      iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
      [a: 3, b: 2]

  """
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and sorts the items
  at position `pos` of the tuples. The sort is stable.

  ## Examples

      iex> List.keysort([a: 5, b: 1, c: 3], 1)
      [b: 1, c: 3, a: 5]

      iex> List.keysort([a: 5, c: 1, b: 3], 0)
      [a: 5, b: 3, c: 1]

  """
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at position `pos`. If the item
  does not exist, it is added to the end of the list.

  ## Examples

      iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
      [a: 3, b: 2]

  """
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and deletes the first tuple
  where the item at position `position` matches with the
  given `item`. Returns the new tuple.

  ## Examples

      iex> List.keydelete([a: 1, b: 2], :a, 0)
      [{ :b, 2 }]

      iex> List.keydelete([a: 1, b: 2], 2, 1)
      [{ :a, 1 }]

      iex> List.keydelete([a: 1, b: 2], :c, 0)
      [{ :a, 1 }, { :b, 2 }]

  """
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Wraps the argument in a list.
  If the argument is already a list, returns the list.
  If the argument is nil, returns an empty list.

  ## Examples

      iex> List.wrap([1, 2, 3])
      [1,2,3]

  """
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

  @doc """
  Zips corresponding elements from each list in `list_of_lists`.

  ## Examples

      iex> List.zip([[1, 2], [3, 4], [5, 6]])
      [{1, 3, 5}, {2, 4, 6}]

      iex> List.zip([[1, 2], [3], [5, 6]])
      [{1, 3, 5}]

  """
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

  @doc """
  Unzips the given list of lists or tuples into separate lists and returns a
  list of lists.

  ## Examples

      iex> List.unzip([{1, 2}, {3, 4}])
      [[1, 3], [2, 4]]

      iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
      [[1, 2, 3], [:a, :b, :c]]

  """
  def unzip(list) when is_list(list) do
    :lists.map tuple_to_list(&1), zip(list)
  end

  @doc """
  Returns a list with an inserted value at specified index. Note that the index
  is capped at the list length and that negative indices wraps around at the
  end of the list.

  ## Examples

      iex> List.insert_at([1, 2, 3, 4], 2, 0)
      [1, 2, 0, 3, 4]

      iex> List.insert_at([1, 2, 3], 10, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -1, 0)
      [1, 2, 3, 0]

  """
  def insert_at(list, index, value) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

  @doc """
  Returns a list with a replaced value at specified index. Negative indices
  wrap around at the end of the list. If the index is out of bounds, the
  original list is returned.

  ## Examples

      iex> List.replace_at([1, 2, 3], 0, 0)
      [0, 2, 3]

      iex> List.replace_at([1, 2, 3], 10, 0)
      [1, 2, 3]

      iex> List.replace_at([1, 2, 3], -1, 0)
      [1, 2, 0]

      iex> List.replace_at([1, 2, 3], -10, 0)
      [1, 2, 3]

  """
  def replace_at(list, index, value) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

  ## Helpers

  # replace_at

  def do_replace_at([], _index, _value) do
    []
  end

  def do_replace_at(list, index, _value) when index < 0 do
    list
  end

  def do_replace_at([_old|rest], 0, value) do
    [ value | rest ]
  end

  def do_replace_at([h|t], index, value) do
    [ h | do_replace_at(t, index - 1, value) ]
  end

  # insert_at

  defp do_insert_at([], _index, value) do
    [ value ]
  end

  defp do_insert_at(list, index, value) when index <= 0 do
    [ value | list ]
  end

  defp do_insert_at([h|t], index, value) do
    [ h | do_insert_at(t, index - 1, value) ]
  end

  # zip

  defp do_zip(list, acc) do
    converter = fn x, acc -> do_zip_each(to_list(x), acc) end
    {mlist, heads} = :lists.mapfoldl converter, [], list

    case heads do
      nil -> :lists.reverse acc
      _   -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
    end
  end

  defp do_zip_each(_, nil) do
    { nil, nil }
  end

  defp do_zip_each([h|t], acc) do
    { t, [h|acc] }
  end

  defp do_zip_each([], _) do
    { nil, nil }
  end

  defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
  defp to_list(list)  when is_list(list),   do: list
end


Unify naming and functions around associations

It was unclear which functions returned associations and which returned
just the keys for associations.

defmodule ExMachina.Ecto do
  defmacro __using__(opts) do
    verify_ecto_dep
    if repo = Keyword.get(opts, :repo) do
      quote do
        use ExMachina

        @repo unquote(repo)

        def fields_for(factory_name, attrs \\ %{}) do
          ExMachina.Ecto.fields_for(__MODULE__, factory_name, attrs)
        end

        def save_record(record) do
          ExMachina.Ecto.save_record(__MODULE__, @repo, record)
        end

        defp assoc(_, factory_name, _ \\ nil) do
          raise """
          assoc/3 has been removed. Please use build instead. Built records will be automatically saved when you call create.

            def factory(#{factory_name}) do
              %{
                ...
                some_assoc: build(:some_assoc)
                ...
              }
            end
          """
        end
      end
    else
      raise ArgumentError,
        """
        expected :repo to be given as an option. Example:

        use ExMachina.Ecto, repo: MyApp.Repo
        """
    end
  end

  defp verify_ecto_dep do
    unless Code.ensure_loaded?(Ecto) do
      raise "You tried to use ExMachina.Ecto, but the Ecto module is not loaded. " <>
        "Please add ecto to your dependencies."
    end
  end

  @doc """
  Builds a factory with the passed in factory_name and returns its fields

  This is only for use with Ecto models.

  Will return a map with the fields and virtual fields, but without the Ecto
  metadata and associations.

  ## Example

      def factory(:user) do
        %MyApp.User{name: "John Doe", admin: false}
      end

      # Returns %{name: "John Doe", admin: true}
      fields_for(:user, admin: true)
  """
  def fields_for(module, factory_name, attrs \\ %{}) do
    module.build(factory_name, attrs)
    |> drop_ecto_fields
  end

  defp drop_ecto_fields(record = %{__struct__: struct, __meta__: %{__struct__: Ecto.Schema.Metadata}}) do
    record
    |> Map.from_struct
    |> Map.delete(:__meta__)
    |> Map.drop(struct.__schema__(:associations))
  end
  defp drop_ecto_fields(record) do
    raise ArgumentError, "#{inspect record} is not an Ecto model. Use `build` instead."
  end

  defp get_assocs(%{__struct__: struct}) do
    for assoc <- struct.__schema__(:associations) do
      {assoc, struct.__schema__(:association, assoc)}
    end
  end

  defp assoc_keys(record) do
    for {key, _assoc} <- get_assocs(record), do: key
  end

  defp put_assoc_changes(changeset, record) do
    keys = assoc_keys(record) --
           belongs_to_assoc_keys(record) --
           not_loaded_assoc_keys(record)

    Enum.reduce(keys, changeset, fn(key, changes) ->
      Ecto.Changeset.put_assoc(changes, key, Map.get(record, key))
    end)
  end

  defp belongs_to_assoc_keys(model) do
    for {key, %{__struct__: Ecto.Association.BelongsTo}} <- get_assocs(model), do: key
  end

  defp not_loaded_assoc_keys(model) do
    for {key, %{__struct__: Ecto.Association.Has}} <- get_assocs(model),
      !Ecto.assoc_loaded?(Map.get(model, key)),
      do: key
  end

  defp restore_belongs_to_associations(target, source) do
    Enum.reduce(belongs_to_assoc_keys(target), target, fn(key, target) ->
      Map.put(target, key, Map.get(source, key))
    end)
  end

  defp get_embeds(%{__struct__: struct}) do
    for embed <- struct.__schema__(:embeds) do
      {embed, struct.__schema__(:embed, embed)}
    end
  end

  defp embed_keys(record) do
    for {key, _embed} <- get_embeds(record), do: key
  end

  defp put_embed_changes(changeset, record) do
    Enum.reduce(embed_keys(record), changeset, fn(key, changes) ->
      Ecto.Changeset.put_embed(changes, key, Map.get(record, key))
    end)
  end

  defp convert_to_changes(record) do
    record
    |> Map.from_struct
    |> Map.delete(:__meta__)
    |> Map.drop(assoc_keys(record))
    |> Map.drop(embed_keys(record))
  end

  @doc """
  Saves a record and all associated records using `Repo.insert!`

  Before inserting, changes are wrapped in a changeset. This means that
  has_many, has_one, embeds_one, and embeds_many associations will be saved
  correctly. Any belongs_to associations will also be saved.

      # Will save the article and list of comments
      create(:article, comments: [build(:comment)])

  """
  def save_record(module, repo, %{__struct__: model, __meta__: %{__struct__: Ecto.Schema.Metadata}} = record) do
    record = record |> persist_belongs_to_associations(module)
    changes = record |> convert_to_changes

    struct(model)
    |> Ecto.Changeset.change(changes)
    |> put_assoc_changes(record)
    |> put_embed_changes(record)
    |> repo.insert!
    |> restore_belongs_to_associations(record)
  end
  def save_record(_, _ , record) do
    raise ArgumentError, "#{inspect record} is not an Ecto model. Use `build` instead"
  end

  defp persist_belongs_to_associations(built_record, module) do
    association_names = belongs_to_assoc_keys(built_record)

    Enum.reduce association_names, built_record, fn(association_name, record) ->
      case association = Map.get(record, association_name) do
        %{__meta__: %{state: :built}} ->
          association = ExMachina.create(module, association)
          put_assoc(record, association_name, association)
        %{__meta__: %{state: :loaded}} ->
          put_assoc(record, association_name, association)
        %{__struct__: Ecto.Association.NotLoaded} -> record
        _ ->
          raise ArgumentError,
            "expected #{inspect(association_name)} to be an Ecto struct but got #{inspect(association)}"
      end
    end
  end

  defp get_owner_key(record, association_name) do
    record.__struct__.__schema__(:association, association_name).owner_key
  end

  defp put_assoc(record, association_name, association) do
    association_id = get_owner_key(record, association_name)
    record
    |> Map.put(association_id, association.id)
    |> Map.put(association_name, association)
  end
end

defmodule ExMachina.Ecto do
  defmacro __using__(opts) do
    verify_ecto_dep
    if repo = Keyword.get(opts, :repo) do
      quote do
        use ExMachina

        @repo unquote(repo)

        def fields_for(factory_name, attrs \\ %{}) do
          ExMachina.Ecto.fields_for(__MODULE__, factory_name, attrs)
        end

        def save_record(record) do
          ExMachina.Ecto.save_record(__MODULE__, @repo, record)
        end

        defp assoc(_, factory_name, _ \\ nil) do
          raise """
          assoc/3 has been removed. Please use build instead. Built records will be automatically saved when you call create.

            def factory(#{factory_name}) do
              %{
                ...
                some_assoc: build(:some_assoc)
                ...
              }
            end
          """
        end
      end
    else
      raise ArgumentError,
        """
        expected :repo to be given as an option. Example:

        use ExMachina.Ecto, repo: MyApp.Repo
        """
    end
  end

  defp verify_ecto_dep do
    unless Code.ensure_loaded?(Ecto) do
      raise "You tried to use ExMachina.Ecto, but the Ecto module is not loaded. " <>
        "Please add ecto to your dependencies."
    end
  end

  @doc """
  Builds a factory with the passed in factory_name and returns its fields

  This is only for use with Ecto models.

  Will return a map with the fields and virtual fields, but without the Ecto
  metadata and associations.

  ## Example

      def factory(:user) do
        %MyApp.User{name: "John Doe", admin: false}
      end

      # Returns %{name: "John Doe", admin: true}
      fields_for(:user, admin: true)
  """
  def fields_for(module, factory_name, attrs \\ %{}) do
    module.build(factory_name, attrs)
    |> drop_ecto_fields
  end

  defp drop_ecto_fields(record = %{__struct__: struct, __meta__: %{__struct__: Ecto.Schema.Metadata}}) do
    record
    |> Map.from_struct
    |> Map.delete(:__meta__)
    |> Map.drop(struct.__schema__(:associations))
  end
  defp drop_ecto_fields(record) do
    raise ArgumentError, "#{inspect record} is not an Ecto model. Use `build` instead."
  end

  defp get_assocs(%{__struct__: struct}) do
    for a <- struct.__schema__(:associations) do
      {a, struct.__schema__(:association, a)}
    end
  end

  defp assoc_keys(record) do
    for {key, _assoc} <- get_assocs(record), do: key
  end

  defp put_assoc_changes(changeset, record) do
    keys = assoc_keys(record) --
           belongs_to_assocs(record) --
           not_loaded_assocs(record)

    Enum.reduce(keys, changeset, fn(key, changes) ->
      Ecto.Changeset.put_assoc(changes, key, Map.get(record, key))
    end)
  end

  defp belongs_to_assocs(model) do
    for {a, %{__struct__: Ecto.Association.BelongsTo}} <- get_assocs(model), do: a
  end

  defp not_loaded_assocs(model) do
    for {a, %{__struct__: Ecto.Association.Has}} <- get_assocs(model),
      !Ecto.assoc_loaded?(Map.get(model, a)),
      do: a
  end

  defp restore_belongs_to_associations(target, source) do
    target
      |> belongs_to_assocs
      |> Enum.reduce(target, fn(a, target) -> Map.put(target, a, Map.get(source, a)) end)
  end

  defp get_embeds(%{__struct__: struct}) do
    for e <- struct.__schema__(:embeds) do
      {e, struct.__schema__(:embed, e)}
    end
  end

  defp embed_keys(record) do
    for {key, _embed} <- get_embeds(record), do: key
  end

  defp put_embed_changes(changeset, record) do
    Enum.reduce(embed_keys(record), changeset, fn(key, changes) ->
      Ecto.Changeset.put_embed(changes, key, Map.get(record, key))
    end)
  end

  defp convert_to_changes(record) do
    record
    |> Map.from_struct
    |> Map.delete(:__meta__)
    |> Map.drop(assoc_keys(record))
    |> Map.drop(embed_keys(record))
  end

  @doc """
  Saves a record and all associated records using `Repo.insert!`

  Before inserting, changes are wrapped in a changeset. This means that
  has_many, has_one, embeds_one, and embeds_many associations will be saved
  correctly. Any belongs_to associations will also be saved.

      # Will save the article and list of comments
      create(:article, comments: [build(:comment)])

  """
  def save_record(module, repo, %{__struct__: model, __meta__: %{__struct__: Ecto.Schema.Metadata}} = record) do
    record = record |> persist_belongs_to_associations(module)
    changes = record |> convert_to_changes

    struct(model)
    |> Ecto.Changeset.change(changes)
    |> put_assoc_changes(record)
    |> put_embed_changes(record)
    |> repo.insert!
    |> restore_belongs_to_associations(record)
  end
  def save_record(_, _ , record) do
    raise ArgumentError, "#{inspect record} is not an Ecto model. Use `build` instead"
  end

  defp persist_belongs_to_associations(built_record, module) do
    association_names = belongs_to_assocs(built_record)

    Enum.reduce association_names, built_record, fn(association_name, record) ->
      case association = Map.get(record, association_name) do
        %{__meta__: %{state: :built}} ->
          association = ExMachina.create(module, association)
          put_assoc(record, association_name, association)
        %{__meta__: %{state: :loaded}} ->
          put_assoc(record, association_name, association)
        %{__struct__: Ecto.Association.NotLoaded} -> record
        _ ->
          raise ArgumentError,
            "expected #{inspect(association_name)} to be an Ecto struct but got #{inspect(association)}"
      end
    end
  end

  defp get_owner_key(record, association_name) do
    record.__struct__.__schema__(:association, association_name).owner_key
  end

  defp put_assoc(record, association_name, association) do
    association_id = get_owner_key(record, association_name)
    record
    |> Map.put(association_id, association.id)
    |> Map.put(association_name, association)
  end
end


Remove 'mix -help' command option

Remove 'mix -help' command option

To make it consistent with `elixir` and `iex` which does not support it (only `--help`)

defmodule Mix.CLI do
  @moduledoc false

  @doc """
  Runs Mix according to the command line arguments.
  """
  def main(args \\ System.argv) do
    Mix.Local.append_archives
    Mix.Local.append_paths

    if env_variable_activated?("MIX_QUIET"), do: Mix.shell(Mix.Shell.Quiet)
    if env_variable_activated?("MIX_DEBUG"), do: Mix.debug(true)

    case check_for_shortcuts(args) do
      :help ->
        proceed(["help"])
      :version ->
        display_version()
      nil ->
        proceed(args)
    end
  end

  defp proceed(args) do
    load_dot_config()
    load_mixfile()
    {task, args} = get_task(args)
    ensure_hex(task)
    change_env(task)
    run_task(task, args)
  end

  defp load_mixfile() do
    file = System.get_env("MIX_EXS") || "mix.exs"
    _ = if File.regular?(file) do
      Code.load_file(file)
    end
  end

  defp get_task(["-" <> _|_]) do
    Mix.shell.error "** (Mix) Cannot implicitly pass flags to default Mix task, " <>
                    "please invoke instead \"mix #{Mix.Project.config[:default_task]}\""
    exit({:shutdown, 1})
  end

  defp get_task([h|t]) do
    {h, t}
  end

  defp get_task([]) do
    {Mix.Project.config[:default_task], []}
  end

  defp run_task(name, args) do
    try do
      ensure_no_slashes(name)
      Mix.Task.run "loadconfig"
      Mix.Task.run name, args
    rescue
      # We only rescue exceptions in the Mix namespace, all
      # others pass through and will explode on the users face
      exception ->
        stacktrace = System.stacktrace

        if Map.get(exception, :mix) do
          mod = exception.__struct__ |> Module.split() |> Enum.at(0, "Mix")
          Mix.shell.error "** (#{mod}) #{Exception.message(exception)}"
          exit({:shutdown, 1})
        else
          reraise exception, stacktrace
        end
    end
  end

  defp env_variable_activated?(name) do
    System.get_env(name) in ~w(1 true)
  end

  defp ensure_hex("local.hex"),
    do: :ok
  defp ensure_hex(_task),
    do: Mix.Hex.ensure_updated?()

  defp ensure_no_slashes(task) do
    if String.contains?(task, "/") do
      Mix.raise Mix.NoTaskError, task: task
    end
  end

  defp change_env(task) do
    if env = preferred_cli_env(task) do
      Mix.env(env)
      if project = Mix.Project.pop do
        %{name: name, file: file} = project
        Mix.Project.push name, file
      end
    end
  end

  defp preferred_cli_env(task) do
    if System.get_env("MIX_ENV") do
      nil
    else
      task = String.to_atom(task)
      Mix.Project.config[:preferred_cli_env][task] || Mix.Task.preferred_cli_env(task)
    end
  end

  defp load_dot_config do
    path = Path.join(Mix.Utils.mix_home, "config.exs")
    if File.regular?(path) do
      Mix.Task.run "loadconfig", [path]
    end
  end

  defp display_version() do
    IO.puts "Mix #{System.version}"
  end

  # Check for --help or --version in the args
  defp check_for_shortcuts([first_arg|_]) when first_arg in
      ["--help", "-h"], do: :help

  defp check_for_shortcuts([first_arg|_]) when first_arg in
      ["--version", "-v"], do: :version

  defp check_for_shortcuts(_), do: nil
end

defmodule Mix.CLI do
  @moduledoc false

  @doc """
  Runs Mix according to the command line arguments.
  """
  def main(args \\ System.argv) do
    Mix.Local.append_archives
    Mix.Local.append_paths

    if env_variable_activated?("MIX_QUIET"), do: Mix.shell(Mix.Shell.Quiet)
    if env_variable_activated?("MIX_DEBUG"), do: Mix.debug(true)

    case check_for_shortcuts(args) do
      :help ->
        proceed(["help"])
      :version ->
        display_version()
      nil ->
        proceed(args)
    end
  end

  defp proceed(args) do
    load_dot_config()
    load_mixfile()
    {task, args} = get_task(args)
    ensure_hex(task)
    change_env(task)
    run_task(task, args)
  end

  defp load_mixfile() do
    file = System.get_env("MIX_EXS") || "mix.exs"
    _ = if File.regular?(file) do
      Code.load_file(file)
    end
  end

  defp get_task(["-" <> _|_]) do
    Mix.shell.error "** (Mix) Cannot implicitly pass flags to default Mix task, " <>
                    "please invoke instead \"mix #{Mix.Project.config[:default_task]}\""
    exit({:shutdown, 1})
  end

  defp get_task([h|t]) do
    {h, t}
  end

  defp get_task([]) do
    {Mix.Project.config[:default_task], []}
  end

  defp run_task(name, args) do
    try do
      ensure_no_slashes(name)
      Mix.Task.run "loadconfig"
      Mix.Task.run name, args
    rescue
      # We only rescue exceptions in the Mix namespace, all
      # others pass through and will explode on the users face
      exception ->
        stacktrace = System.stacktrace

        if Map.get(exception, :mix) do
          mod = exception.__struct__ |> Module.split() |> Enum.at(0, "Mix")
          Mix.shell.error "** (#{mod}) #{Exception.message(exception)}"
          exit({:shutdown, 1})
        else
          reraise exception, stacktrace
        end
    end
  end

  defp env_variable_activated?(name) do
    System.get_env(name) in ~w(1 true)
  end

  defp ensure_hex("local.hex"),
    do: :ok
  defp ensure_hex(_task),
    do: Mix.Hex.ensure_updated?()

  defp ensure_no_slashes(task) do
    if String.contains?(task, "/") do
      Mix.raise Mix.NoTaskError, task: task
    end
  end

  defp change_env(task) do
    if env = preferred_cli_env(task) do
      Mix.env(env)
      if project = Mix.Project.pop do
        %{name: name, file: file} = project
        Mix.Project.push name, file
      end
    end
  end

  defp preferred_cli_env(task) do
    if System.get_env("MIX_ENV") do
      nil
    else
      task = String.to_atom(task)
      Mix.Project.config[:preferred_cli_env][task] || Mix.Task.preferred_cli_env(task)
    end
  end

  defp load_dot_config do
    path = Path.join(Mix.Utils.mix_home, "config.exs")
    if File.regular?(path) do
      Mix.Task.run "loadconfig", [path]
    end
  end

  defp display_version() do
    IO.puts "Mix #{System.version}"
  end

  # Check for --help or --version in the args
  defp check_for_shortcuts([first_arg|_]) when first_arg in
      ["--help", "-h", "-help"], do: :help

  defp check_for_shortcuts([first_arg|_]) when first_arg in
      ["--version", "-v"], do: :version

  defp check_for_shortcuts(_), do: nil
end


~ instead %

~ instead %

defmodule Mix.Tasks.Weber do

  @moduledoc """

    Create a new weber project template.

    Usage:

      mix weber.new /home/user/myWebApp - Creates myWebApp directory with weber project skeleton.
      mix weber --version - Prints weber version.
      mix weber --help - Prints help.
  """

  @shortdoc "Create a new weber project"

  use Mix.Task

  @version Weber.Mixfile.project[:version]

  def run([]) do
    usage
  end

  def run(["--help"]) do
    usage
  end

  def run(["--version"]) do
    Mix.shell.info "Weber v#{@version}"
  end

  def usage do
    Mix.shell.info """
    Usage:

      mix weber.new /home/user/testWebApp -- creates new weber web application
      mix weber --version -- shows weber version
      mix weber --help    -- shows weber help
    """
  end

  defmodule New do

    @doc """
    Create default weber based project.
    """
    def run([directoryName]) do
      # create project directory
      case File.mkdir(directoryName) do
        :ok -> :ok
        err -> :io.format("[Weber error] Can't create project directory. Error: ~p~n", [err])
      end

      #
      # create project skeleton
      #
      path = Path.expand directoryName
      baseName = Path.basename directoryName

      vars = HashDict.new [
        {"path", path},
        {"projectNamespace", Mix.Utils.camelize(baseName)},
        {"projectName", baseName}
      ]

      template = "default"
      skelRoot = File.cwd! <> "/templates/" <> template
      File.cd skelRoot
      skelFiles = Weber.Utils.get_all_files(".")
      lc file inlist skelFiles do
        baseFile = String.slice(file, 1, 1024)
        destination = path <> replace(baseFile, vars)
        dir = :filename.dirname(destination)
        if !:filelib.is_dir(dir), do: Mix.Generator.create_directory dir
        File.cp_r file, destination, fn _, _ -> true end
        {:ok, origin} = File.read destination
        compiled = replace(origin, vars)
        File.write destination, compiled, []
      end
    end

    defp replace(text, vars) do
      case Enum.uniq(Regex.scan(~r/\#\{([^\}]+)\}/, text)) do
        []   -> text
        data -> replace_act(text, vars, data)
      end
    end

    defp replace_act(text, _vars, []) do
      text
    end

    defp replace_act(text, vars, [[entry, key] | tail]) do
      replace_act(String.replace(text, entry, HashDict.get(vars, key), []), vars, tail)
    end

  end
end

defmodule Mix.Tasks.Weber do

  @moduledoc """

    Create a new weber project template.

    Usage:

      mix weber.new /home/user/myWebApp - Creates myWebApp directory with weber project skeleton.
      mix weber --version - Prints weber version.
      mix weber --help - Prints help.
  """

  @shortdoc "Create a new weber project"

  use Mix.Task

  @version Weber.Mixfile.project[:version]

  def run([]) do
    usage
  end

  def run(["--help"]) do
    usage
  end

  def run(["--version"]) do
    Mix.shell.info "Weber v#{@version}"
  end

  def usage do
    Mix.shell.info """
    Usage:

      mix weber.new /home/user/testWebApp -- creates new weber web application
      mix weber --version -- shows weber version
      mix weber --help    -- shows weber help
    """
  end

  defmodule New do

    @doc """
    Create default weber based project.
    """
    def run([directoryName]) do
      # create project directory
      case File.mkdir(directoryName) do
        :ok -> :ok
        err -> :io.format("[Weber error] Can't create project directory. Error: ~p~n", [err])
      end

      #
      # create project skeleton
      #
      path = Path.expand directoryName
      baseName = Path.basename directoryName

      vars = HashDict.new [
        {"path", path},
        {"projectNamespace", Mix.Utils.camelize(baseName)},
        {"projectName", baseName}
      ]

      template = "default"
      skelRoot = File.cwd! <> "/templates/" <> template
      File.cd skelRoot
      skelFiles = Weber.Utils.get_all_files(".")
      lc file inlist skelFiles do
        baseFile = String.slice(file, 1, 1024)
        destination = path <> replace(baseFile, vars)
        dir = :filename.dirname(destination)
        if !:filelib.is_dir(dir), do: Mix.Generator.create_directory dir
        File.cp_r file, destination, fn _, _ -> true end
        {:ok, origin} = File.read destination
        compiled = replace(origin, vars)
        File.write destination, compiled, []
      end
    end

    defp replace(text, vars) do
      case Enum.uniq(Regex.scan(%r/\#\{([^\}]+)\}/, text)) do
        []   -> text
        data -> replace_act(text, vars, data)
      end
    end

    defp replace_act(text, _vars, []) do
      text
    end

    defp replace_act(text, vars, [[entry, key] | tail]) do
      replace_act(String.replace(text, entry, HashDict.get(vars, key), []), vars, tail)
    end

  end
end


change site expander to use download_name

defmodule Nerves.Artifact do
  @moduledoc """
  Package artifacts are the product of compiling a package with a
  specific toolchain.

  """
  alias Nerves.Artifact.{Cache, Providers}

  @base_dir Path.expand("~/.nerves/artifacts")

  @doc """
  Builds the package and produces an  See Nerves.Artifact
  for more information.
  """
  @spec build(Nerves.Package.t, Nerves.Package.t) :: :ok
  def build(pkg, toolchain) do
    case pkg.provider do
      {provider, opts} ->
        case provider.build(pkg, toolchain, opts) do
          {:ok, path} ->
            Cache.put(pkg, path)
          {:error, error} ->
            Mix.raise """
            Nerves encountered an error while constructing the artifact
            #{error}
            """
        end
      :noop -> :ok
    end
  end

  @doc """
  Produces an artifact which can be fetched when calling `nerves.artifact.get`.
  """
  def archive(%{type: type} = pkg, toolchain, opts) when type in [:toolchain, :system] do
    Mix.shell.info("Creating Artifact Archive")
    opts = default_archive_opts(pkg, opts)
    case pkg.provider do
      {provider, _opts} ->
        {:ok, archive_path} = provider.archive(pkg, toolchain, opts)

        if opts[:path] != archive_path do
          File.cp!(archive_path, opts[:path])
        end
        {:ok, archive_path}
      _ ->
        Mix.shell.info("No provider specified for #{pkg.app}")
        :noop
    end

  end
  def archive(%{type: type}, _toolchain, _opts) do
    Mix.raise """
    mix artifact
    Has not been implemented for #{inspect type} packages.
    """
  end

  @doc """
  Cleans the artifacts for the package providers of all packages.
  """
  @spec clean(Nerves.Package.t) :: :ok | {:error, term}
  def clean(pkg) do
    Mix.shell.info("Cleaning Nerves Package #{pkg.app}")
    case pkg.provider do
      {provider, _opts} ->
        provider.clean(pkg)
      _ ->
        Mix.shell.info("No provider specified for #{pkg.app}")
        :noop
    end
  end

  @doc """
  Determines if the artifact for a package is stale and needs to be rebuilt.
  """
  @spec stale?(Nerves.Package.t) :: boolean
  def stale?(pkg) do
    if env_var?(pkg) do
      false
    else
      !Cache.valid?(pkg)
    end
  end

  @doc """
  Get the artifact name
  """
  @spec name(Nerves.Package.t) :: String.t
  def name(pkg) do
    "#{pkg.app}-#{host_tuple(pkg)}-#{pkg.version}"
  end

  @doc """
  Get the artifact download name
  """
  @spec download_name(Nerves.Package.t) :: String.t
  def download_name(pkg) do
    "#{pkg.app}-#{host_tuple(pkg)}-#{pkg.version}-#{checksum(pkg)}"
  end

  def parse_download_name(name) when is_binary(name) do
    name = Regex.run(~r/(.*)-([^-]*)-(.*)-([^-]*)/, name)
    case name do
      [_, app, host_tuple, version, checksum] ->
        {:ok, %{
          app: app,
          host_tuple: host_tuple,
          checksum: checksum,
          version: version
        }}
      _->
        {:error, "Unable to parse artifact name #{name}"}
    end
  end

  @doc """
  Get the base dir for where an artifact for a package should be stored.

  The base dir for an artifact will point
  to the NERVES_ARTIFACT_DIR or if undefined, `~/.nerves/artifacts`
  """
  @spec base_dir() :: String.t
  def base_dir() do
    System.get_env("NERVES_ARTIFACTS_DIR") || @base_dir
  end

  def build_path(pkg) do
    pkg.path
    |> Path.join(".nerves")
    |> Path.join("artifacts")
    |> Path.join(name(pkg))
  end

  @doc """
  Produce a base16 encoded checksum for the package from the list of files
  and expanded folders listed in the checksum config key.
  """
  @spec checksum(Nerves.Package.t) :: String.t
  def checksum(pkg) do
    blob =
      (pkg.config[:checksum] || [])
      |> expand_paths(pkg.path)
      |> Enum.map(& File.read!/1)
      |> Enum.map(& :crypto.hash(:sha256, &1))
      |> Enum.join
    :crypto.hash(:sha256, blob)
    |> Base.encode16
  end

  @doc """
  The full path to the artifact.
  """
  @spec dir(Nerves.Package.t) :: String.t
  def dir(pkg) do
    if env_var?(pkg) do
      System.get_env(env_var(pkg)) |> Path.expand
    else
      base_dir()
      |> Path.join(name(pkg))
    end
  end

  @doc """
  Check to see if the artifact path is being set from the system env.
  """
  @spec env_var?(Nerves.Package.t) :: boolean
  def env_var?(pkg) do
    name = env_var(pkg)
    dir = System.get_env(name)
    dir != nil and File.dir?(dir)
  end

  @doc """
  Determine the environment variable which would be set to override the path.
  """
  @spec env_var(Nerves.Package.t) :: String.t
  def env_var(pkg) do
    case pkg.type do
      :toolchain -> "NERVES_TOOLCHAIN"
      :system -> "NERVES_SYSTEM"
      _ ->
        pkg.name
        |> Atom.to_string
        |> String.upcase
    end
  end

  def expand_sites(pkg) do
    case pkg.config[:artifact_url] do
      nil ->
        Keyword.get(pkg.config, :artifact_sites, [])
        |> Enum.map(&expand_site(&1, pkg))
      urls when is_list(urls) ->
        urls
      _invalid ->
        Mix.raise "Invalid artifact_url. Please use artifact_sites instead"
    end
  end

  def download_path(pkg) do
    name = name(pkg) <> ext(pkg)
    Nerves.Env.download_dir()
    |> Path.join(name)
    |> Path.expand
  end

  def host_tuple(%{type: :toolchain}) do
    Nerves.Env.host_os <> "_" <>
    Nerves.Env.host_arch
  end
  def host_tuple(_pkg) do
    "portable"
  end

  @doc """
  Determines the extension for an artifact based off its type.
  Toolchains use xz compression.
  """
  @spec ext(Nerves.Package.t) :: String.t
  def ext(%{type: :toolchain}), do: ".tar.xz"
  def ext(_), do: ".tar.gz"

  def provider(config) do
    case config[:nerves_package][:provider] do
      nil -> provider_type(config[:nerves_package][:type])
      provider ->
        provider_opts = config[:nerves_package][:provider_opts] || []
        {provider, provider_opts}
    end
  end

  defp provider_type(:system_platform), do: nil
  defp provider_type(:toolchain_platform), do: nil
  defp provider_type(:toolchain), do: {Providers.Local, []}

  defp provider_type(_) do
    mod =
      case :os.type do
        {_, :linux} -> Providers.Local
        _ -> Providers.Docker
      end
    {mod, []}
  end

  defp expand_paths(paths, dir) do
    expand_dir = Path.expand(dir)

    paths
    |> Enum.map(&Path.join(dir, &1))
    |> Enum.flat_map(&Path.wildcard/1)
    |> Enum.flat_map(&dir_files/1)
    |> Enum.map(&Path.expand/1)
    |> Enum.filter(&File.regular?/1)
    |> Enum.uniq

  end

  defp dir_files(path) do
    if File.dir?(path) do
      Path.wildcard(Path.join(path, "**"))
    else
      [path]
    end
  end

  defp default_archive_opts(pkg, opts) do
    name = download_name(pkg) <> ext(pkg)
    opts
    |> Keyword.put_new(:name, name)
    |> Keyword.put_new(:path, Path.join(File.cwd!(), name))
  end

  defp expand_site({:github_releases, org_proj}, pkg) do
    "https://github.com/#{org_proj}/releases/download/v#{pkg.version}/#{download_name(pkg)}#{ext(pkg)}"
  end
  defp expand_site(loc, _pkg) when is_binary(loc), do: loc

end

defmodule Nerves.Artifact do
  @moduledoc """
  Package artifacts are the product of compiling a package with a
  specific toolchain.

  """
  alias Nerves.Artifact.{Cache, Providers}

  @base_dir Path.expand("~/.nerves/artifacts")

  @doc """
  Builds the package and produces an  See Nerves.Artifact
  for more information.
  """
  @spec build(Nerves.Package.t, Nerves.Package.t) :: :ok
  def build(pkg, toolchain) do
    case pkg.provider do
      {provider, opts} ->
        case provider.build(pkg, toolchain, opts) do
          {:ok, path} ->
            Cache.put(pkg, path)
          {:error, error} ->
            Mix.raise """
            Nerves encountered an error while constructing the artifact
            #{error}
            """
        end
      :noop -> :ok
    end
  end

  @doc """
  Produces an artifact which can be fetched when calling `nerves.artifact.get`.
  """
  def archive(%{type: type} = pkg, toolchain, opts) when type in [:toolchain, :system] do
    Mix.shell.info("Creating Artifact Archive")
    opts = default_archive_opts(pkg, opts)
    case pkg.provider do
      {provider, _opts} ->
        {:ok, archive_path} = provider.archive(pkg, toolchain, opts)

        if opts[:path] != archive_path do
          File.cp!(archive_path, opts[:path])
        end
        {:ok, archive_path}
      _ ->
        Mix.shell.info("No provider specified for #{pkg.app}")
        :noop
    end

  end
  def archive(%{type: type}, _toolchain, _opts) do
    Mix.raise """
    mix artifact
    Has not been implemented for #{inspect type} packages.
    """
  end

  @doc """
  Cleans the artifacts for the package providers of all packages.
  """
  @spec clean(Nerves.Package.t) :: :ok | {:error, term}
  def clean(pkg) do
    Mix.shell.info("Cleaning Nerves Package #{pkg.app}")
    case pkg.provider do
      {provider, _opts} ->
        provider.clean(pkg)
      _ ->
        Mix.shell.info("No provider specified for #{pkg.app}")
        :noop
    end
  end

  @doc """
  Determines if the artifact for a package is stale and needs to be rebuilt.
  """
  @spec stale?(Nerves.Package.t) :: boolean
  def stale?(pkg) do
    if env_var?(pkg) do
      false
    else
      !Cache.valid?(pkg)
    end
  end

  @doc """
  Get the artifact name
  """
  @spec name(Nerves.Package.t) :: String.t
  def name(pkg) do
    "#{pkg.app}-#{host_tuple(pkg)}-#{pkg.version}"
  end

  @doc """
  Get the artifact download name
  """
  @spec download_name(Nerves.Package.t) :: String.t
  def download_name(pkg) do
    "#{pkg.app}-#{host_tuple(pkg)}-#{pkg.version}-#{checksum(pkg)}"
  end

  def parse_download_name(name) when is_binary(name) do
    name = Regex.run(~r/(.*)-([^-]*)-(.*)-([^-]*)/, name)
    case name do
      [_, app, host_tuple, version, checksum] ->
        {:ok, %{
          app: app,
          host_tuple: host_tuple,
          checksum: checksum,
          version: version
        }}
      _->
        {:error, "Unable to parse artifact name #{name}"}
    end
  end

  @doc """
  Get the base dir for where an artifact for a package should be stored.

  The base dir for an artifact will point
  to the NERVES_ARTIFACT_DIR or if undefined, `~/.nerves/artifacts`
  """
  @spec base_dir() :: String.t
  def base_dir() do
    System.get_env("NERVES_ARTIFACTS_DIR") || @base_dir
  end

  def build_path(pkg) do
    pkg.path
    |> Path.join(".nerves")
    |> Path.join("artifacts")
    |> Path.join(name(pkg))
  end

  @doc """
  Produce a base16 encoded checksum for the package from the list of files
  and expanded folders listed in the checksum config key.
  """
  @spec checksum(Nerves.Package.t) :: String.t
  def checksum(pkg) do
    blob =
      (pkg.config[:checksum] || [])
      |> expand_paths(pkg.path)
      |> Enum.map(& File.read!/1)
      |> Enum.map(& :crypto.hash(:sha256, &1))
      |> Enum.join
    :crypto.hash(:sha256, blob)
    |> Base.encode16
  end

  @doc """
  The full path to the artifact.
  """
  @spec dir(Nerves.Package.t) :: String.t
  def dir(pkg) do
    if env_var?(pkg) do
      System.get_env(env_var(pkg)) |> Path.expand
    else
      base_dir()
      |> Path.join(name(pkg))
    end
  end

  @doc """
  Check to see if the artifact path is being set from the system env.
  """
  @spec env_var?(Nerves.Package.t) :: boolean
  def env_var?(pkg) do
    name = env_var(pkg)
    dir = System.get_env(name)
    dir != nil and File.dir?(dir)
  end

  @doc """
  Determine the environment variable which would be set to override the path.
  """
  @spec env_var(Nerves.Package.t) :: String.t
  def env_var(pkg) do
    case pkg.type do
      :toolchain -> "NERVES_TOOLCHAIN"
      :system -> "NERVES_SYSTEM"
      _ ->
        pkg.name
        |> Atom.to_string
        |> String.upcase
    end
  end

  def expand_sites(pkg) do
    case pkg.config[:artifact_url] do
      nil ->
        Keyword.get(pkg.config, :artifact_sites, [])
        |> Enum.map(&expand_site(&1, pkg))
      urls when is_list(urls) ->
        urls
      _invalid ->
        Mix.raise "Invalid artifact_url. Please use artifact_sites instead"
    end
  end

  def download_path(pkg) do
    name = name(pkg) <> ext(pkg)
    Nerves.Env.download_dir()
    |> Path.join(name)
    |> Path.expand
  end

  def host_tuple(%{type: :toolchain}) do
    Nerves.Env.host_os <> "_" <>
    Nerves.Env.host_arch
  end
  def host_tuple(_pkg) do
    "portable"
  end

  @doc """
  Determines the extension for an artifact based off its type.
  Toolchains use xz compression.
  """
  @spec ext(Nerves.Package.t) :: String.t
  def ext(%{type: :toolchain}), do: ".tar.xz"
  def ext(_), do: ".tar.gz"

  def provider(config) do
    case config[:nerves_package][:provider] do
      nil -> provider_type(config[:nerves_package][:type])
      provider ->
        provider_opts = config[:nerves_package][:provider_opts] || []
        {provider, provider_opts}
    end
  end

  defp provider_type(:system_platform), do: nil
  defp provider_type(:toolchain_platform), do: nil
  defp provider_type(:toolchain), do: {Providers.Local, []}

  defp provider_type(_) do
    mod =
      case :os.type do
        {_, :linux} -> Providers.Local
        _ -> Providers.Docker
      end
    {mod, []}
  end

  defp expand_paths(paths, dir) do
    expand_dir = Path.expand(dir)

    paths
    |> Enum.map(&Path.join(dir, &1))
    |> Enum.flat_map(&Path.wildcard/1)
    |> Enum.flat_map(&dir_files/1)
    |> Enum.map(&Path.expand/1)
    |> Enum.filter(&File.regular?/1)
    |> Enum.uniq

  end

  defp dir_files(path) do
    if File.dir?(path) do
      Path.wildcard(Path.join(path, "**"))
    else
      [path]
    end
  end

  defp default_archive_opts(pkg, opts) do
    name = download_name(pkg) <> ext(pkg)
    opts
    |> Keyword.put_new(:name, name)
    |> Keyword.put_new(:path, Path.join(File.cwd!(), name))
  end

  defp expand_site({:github_releases, org_proj}, pkg) do
    "https://github.com/#{org_proj}/releases/download/v#{pkg.version}/#{name(pkg)}"
  end
  defp expand_site(loc, _pkg) when is_binary(loc), do: loc

end


Fixing syntax

Fixing syntax

defmodule Phoenix.Channel do
  @moduledoc """
  Defines a Phoenix Channel.

  Channels provide a means for bidirectional communication from clients that
  integrate with the `Phoenix.PubSub` layer for soft-realtime functionality.

  ## Topics & Callbacks

  Everytime you join a channel, you need to choose which particular topic you
  want to listen to. The topic is just an identifier, but by convention it is
  often made of two parts: `"topic:subtopic"`. Using the `"topic:subtopic"`
  approach pairs nicely with the `Phoenix.Socket.channel/2` allowing you to
  match on all topics starting with a given prefix:

      channel "rooms:*", MyApp.RoomChannel

  Any topic coming into the router with the `"rooms:"` prefix would dispatch
  to `MyApp.RoomChannel` in the above example. Topics can also be pattern
  matched in your channels' `join/3` callback to pluck out the scoped pattern:

      # handles the special `"lobby"` subtopic
      def join("rooms:lobby", _auth_message, socket) do
        {:ok, socket}
      end

      # handles any other subtopic as the room ID, for example `"rooms:12"`, `"rooms:34"`
      def join("rooms:" <> room_id, auth_message, socket) do
        {:ok, socket}
      end

  ## Authorization

  Clients must join a channel to send and receive PubSub events on that channel.
  Your channels must implement a `join/3` callback that authorizes the socket
  for the given topic. For example, you could check if the user is allowed to
  join that particular room.

  To authorize a socket in `join/3`, return `{:ok, socket}`.
  To refuse authorization in `join/3`, return `{:error, reply}`.

  ## Incoming Events

  After a client has successfully joined a channel, incoming events from the
  client are routed through the channel's `handle_in/3` callbacks. Within these
  callbacks, you can perform any action. Typically you'll either forward a
  message to all listeners with `broadcast!/3`, or push a message directly down
  the socket with `push/3`. Incoming callbacks must return the `socket` to
  maintain ephemeral state.

  Here's an example of receiving an incoming `"new_msg"` event from one client,
  and broadcasting the message to all topic subscribers for this socket.

      def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
        broadcast! socket, "new_msg", %{uid: uid, body: body}
        {:noreply, socket}
      end

  You can also push a message directly down the socket:

      # client asks for their current rank, push sent directly as a new event.
      def handle_in("current_rank", socket) do
        push socket, "current_rank", %{val: Game.get_rank(socket.assigns[:user])}
        {:noreply, socket}
      end

  ## Replies

  In addition to pushing messages out when you receive a `handle_in` event,
  you can also reply directly to a client event for request/response style
  messaging. This is useful when a client must know the result of an operation
  or to simply ack messages.

  For example, imagine creating a resource and replying with the created record:

      def handle_in("create:post", attrs, socket) do
        changeset = Post.changeset(%Post{}, attrs)

        if changeset.valid? do
          Repo.insert!(changeset)
          {:reply, {:ok, changeset}, socket}
        else
          {:reply, {:error, changeset.errors}, socket}
        end
      end

  Alternatively, you may just want to ack the status of the operation:

      def handle_in("create:post", attrs, socket) do
        changeset = Post.changeset(%Post{}, attrs)

        if changeset.valid? do
          Repo.insert!(changeset)
          {:reply, :ok, socket}
        else
          {:reply, :error, socket}
        end
      end

  ## Intercepting Outgoing Events

  When an event is broadcasted with `broadcast/3`, each channel subscriber can
  choose to intercept the event and have their `handle_out/3` callback triggered.
  This allows the event's payload to be customized on a socket by socket basis
  to append extra information, or conditionally filter the message from being
  delivered. If the event is not intercepted with `Phoenix.Channel.intercept/1`,
  then the message is pushed directly to the client:

      intercept ["new_msg", "user_joined"]

      # for every socket subscribing to this topic, append an `is_editable`
      # value for client metadata.
      def handle_out("new_msg", msg, socket) do
        push socket, "new_msg", Map.merge(msg,
          %{is_editable: User.can_edit_message?(socket.assigns[:user], msg)}
        )
        {:noreply, socket}
      end

      # do not send broadcasted `"user_joined"` events if this socket's user
      # is ignoring the user who joined.
      def handle_out("user_joined", msg, socket) do
        unless User.ignoring?(socket.assigns[:user], msg.user_id) do
          push socket, "user_joined", msg
        end
        {:noreply, socket}
      end

  ## Broadcasting to an external topic

  In some cases, you will want to broadcast messages without the context of
  a `socket`. This could be for broadcasting from within your channel to an
  external topic, or broadcasting from elsewhere in your application like a
  controller or another process. Such can be done via your endpoint:

      # within channel
      def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
        ...
        broadcast_from! socket, "new_msg", %{uid: uid, body: body}
        MyApp.Endpoint.broadcast_from! self(), "rooms:superadmin",
          "new_msg", %{uid: uid, body: body}
        {:noreply, socket}
      end

      # within controller
      def create(conn, params) do
        ...
        MyApp.Endpoint.broadcast! "rooms:" <> rid, "new_msg", %{uid: uid, body: body}
        MyApp.Endpoint.broadcast! "rooms:superadmin", "new_msg", %{uid: uid, body: body}
        redirect conn, to: "/"
      end

  ## Terminate

  On termination, the channel callback `terminate/2` will be invoked with
  the error reason and the socket.

  If we are terminating because the client left, the reason will be
  `{:shutdown, :left}`. Similarly, if we are terminating because the
  client connection was closed, the reason will be `{:shutdown, :closed}`.

  If any of the callbacks return a `:stop` tuple, it will also
  trigger terminate with the reason given in the tuple.

  `terminate/2`, however, won't be invoked in case of errors nor in
  case of exits. This is the same behaviour as you find in Elixir
  abstractions like `GenServer` and others. Typically speaking, if you
  want to clean something up, it is better to monitor your channel
  process and do the clean up from another process.  Similar to GenServer,
  it would also be possible `:trap_exit` to guarantee that `terminate/2`
  is invoked. This practice is not encouraged though.
  """

  use Behaviour
  alias Phoenix.Socket
  alias Phoenix.Channel.Server

  @type reply :: status :: atom | {status :: atom, response :: map}
  @type socket_ref :: {transport_pid :: Pid, serializer :: Module.t,
                       topic :: binary, ref :: binary}

  defcallback code_change(old_vsn, Socket.t, extra :: term) ::
              {:ok, Socket.t} |
              {:error, reason :: term} when old_vsn: term | {:down, term}

  defcallback join(topic :: binary, auth_msg :: map, Socket.t) ::
              {:ok, Socket.t} |
              {:ok, map, Socket.t} |
              {:error, map}

  defcallback handle_in(event :: String.t, msg :: map, Socket.t) ::
              {:noreply, Socket.t} |
              {:reply, reply, Socket.t} |
              {:stop, reason :: term, Socket.t} |
              {:stop, reason :: term, reply, Socket.t}

  defcallback handle_info(term, Socket.t) ::
              {:noreply, Socket.t} |
              {:stop, reason :: term, Socket.t}

  defcallback terminate(msg :: map, Socket.t) ::
              {:shutdown, :left | :closed} |
              term

  defmacro __using__(_) do
    quote do
      @behaviour unquote(__MODULE__)
      @on_definition unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
      @phoenix_intercepts []

      import unquote(__MODULE__)
      import Phoenix.Socket, only: [assign: 3]

      def code_change(_old, socket, _extra), do: {:ok, socket}

      def handle_in(_event, _message, socket) do
        {:noreply, socket}
      end

      def handle_info(_message, socket), do: {:noreply, socket}

      def terminate(_reason, _socket), do: :ok

      defoverridable code_change: 3, handle_info: 2, handle_in: 3, terminate: 2
    end
  end

  defmacro __before_compile__(_) do
    quote do
      def __intercepts__, do: @phoenix_intercepts
    end
  end

  @doc """
  Defines which Channel events to intercept for `handle_out/3` callbacks.

  By default, broadcasted events are pushed directly to the client, but
  intercepting events gives your channel a chance to customize the event
  for the client to append extra information or filter the message from being
  delivered.

  *Note*: intercepting events can introduce significantly more overhead if a
  large number of subscribers must customize a message since the broadcast will
  be encoded N times instead of a single shared encoding across all subscribers.

  ## Examples

      intercept ["new_msg"]

      def handle_out("new_msg", payload, socket) do
        push socket, "new_msg", Map.merge(payload,
          is_editable: User.can_edit_message?(socket.assigns[:user], payload)
        )
        {:noreply, socket}
      end

  `handle_out/3` callbacks must return one of:

      {:noreply, Socket.t} |
      {:stop, reason :: term, Socket.t}

  """
  defmacro intercept(events) do
    quote do
      @phoenix_intercepts unquote(events)
    end
  end

  @doc false
  def __on_definition__(env, :def, :handle_out, [event, _payload, _socket], _, _)
    when is_binary(event) do

    unless event in Module.get_attribute(env.module, :phoenix_intercepts) do
      IO.write "#{Path.relative_to(env.file, File.cwd!)}:#{env.line}: [warning] " <>
               "An intercept for event \"#{event}\" has not yet been defined in #{env.module}.handle_out/3. " <>
               "Add \"#{event}\" to your list of intercepted events with intercept/1"
    end
  end
  def __on_definition__(_env, _kind, _name, _args, _guards, _body) do
  end

  @doc """
  Broadcast an event to all subscribers of the socket topic.

  The event's message must be a serializable map.

  ## Examples

      iex> broadcast socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def broadcast(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
    Server.broadcast pubsub_server, topic, event, message
  end

  @doc """
  Same as `broadcast/3` but raises if broadcast fails.
  """
  def broadcast!(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
    Server.broadcast! pubsub_server, topic, event, message
  end

  @doc """
  Broadcast event from pid to all subscribers of the socket topic.

  The channel that owns the socket will not receive the published
  message. The event's message must be a serializable map.

  ## Examples

      iex> broadcast_from socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def broadcast_from(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} = assert_joined!(socket)
    Server.broadcast_from pubsub_server, channel_pid, topic, event, message
  end

  @doc """
  Same as `broadcast_from/3` but raises if broadcast fails.
  """
  def broadcast_from!(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} = assert_joined!(socket)
    Server.broadcast_from! pubsub_server, channel_pid, topic, event, message
  end

  @doc """
  Sends event to the socket.

  The event's message must be a serializable map.

  ## Examples

      iex> push socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def push(socket, event, message) do
    %{transport_pid: transport_pid, topic: topic} = assert_joined!(socket)
    Server.push(transport_pid, topic, event, message, socket.serializer)
  end

  @doc """
  Replies asynchronously to a socket push.

  Useful when you need to reply to a push that can't otherwise be handled using
  the `{:reply, {status, payload}, socket}` return from your `handle_in`
  callbacks. `reply/3` will be used in the rare cases you need to perform work in
  another process and reply when finished by generating a reference to the push
  with `socket_ref/1`.

  *Note*: In such cases, a `socket_ref` should be generated and
  passed to the external process, so the `socket` itself is not leaked outside
  the channel. The `socket` holds information such as assigns and transport
  configuration, so it's important to not copy this information outside of the
  channel that owns it.

  ## Examples

      def handle_in("work", payload, socket) do
        Worker.perform(payload, socket_ref(socket))
        {:noreply, socket}
      end

      def handle_info({:work_complete, result, ref}, socket) do
        reply ref, {:ok, result}
        {:noreply, socket}
      end

  """
  @spec reply(socket_ref, reply) :: :ok
  def reply({transport_pid, serializer, topic, ref}, {status, payload}) do
    Server.reply(transport_pid, ref, topic, {status, payload}, serializer)
  end

  @doc """
  Generates a `socket_ref` for an async reply.

  See `reply/2` for example usage.
  """
  @spec socket_ref(Socket.t) :: socket_ref
  def socket_ref(%Socket{joined: true, ref: ref} = socket) when not is_nil(ref) do
    {socket.transport_pid, socket.serializer, socket.topic, ref}
  end
  def socket_ref(_socket) do
    raise ArgumentError, """
    Socket refs can only be generated for a socket that has joined with a push ref
    """
  end

  defp assert_joined!(%Socket{joined: true} = socket) do
    socket
  end

  defp assert_joined!(%Socket{joined: false}) do
    raise """
    `push`, `reply`, and `broadcast` can only be called after the socket has finished joining.
    To push a message on join, send to self and handle in handle_info/2, ie:

        def join(topic, auth_msg, socket) do
          ...
          send(self, :after_join)
          {:ok, socket}
        end

        def handle_info(:after_join, socket) do
          push socket, "feed", %{list: feed_items(socket)}
          {:noreply, socket}
        end
    """
  end
end

defmodule Phoenix.Channel do
  @moduledoc """
  Defines a Phoenix Channel.

  Channels provide a means for bidirectional communication from clients that
  integrate with the `Phoenix.PubSub` layer for soft-realtime functionality.

  ## Topics & Callbacks

  Everytime you join a channel, you need to choose which particular topic you
  want to listen to. The topic is just an identifier, but by convention it is
  often made of two parts: `"topic:subtopic"`. Using the `"topic:subtopic"`
  approach pairs nicely with the `Phoenix.Socket.channel/2` allowing you to
  match on all topics starting with a given prefix:

      channel "rooms:*", MyApp.RoomChannel

  Any topic coming into the router with the `"rooms:"` prefix would dispatch
  to `MyApp.RoomChannel` in the above example. Topics can also be pattern
  matched in your channels' `join/3` callback to pluck out the scoped pattern:

      # handles the special `"lobby"` subtopic
      def join("rooms:lobby", _auth_message, socket) do
        {:ok, socket}
      end

      # handles any other subtopic as the room ID, for example `"rooms:12"`, `"rooms:34"`
      def join("rooms:" <> room_id, auth_message, socket) do
        {:ok, socket}
      end

  ## Authorization

  Clients must join a channel to send and receive PubSub events on that channel.
  Your channels must implement a `join/3` callback that authorizes the socket
  for the given topic. For example, you could check if the user is allowed to
  join that particular room.

  To authorize a socket in `join/3`, return `{:ok, socket}`.
  To refuse authorization in `join/3`, return `{:error, reply}`.

  ## Incoming Events

  After a client has successfully joined a channel, incoming events from the
  client are routed through the channel's `handle_in/3` callbacks. Within these
  callbacks, you can perform any action. Typically you'll either forward a
  message to all listeners with `broadcast!/3`, or push a message directly down
  the socket with `push/3`. Incoming callbacks must return the `socket` to
  maintain ephemeral state.

  Here's an example of receiving an incoming `"new_msg"` event from one client,
  and broadcasting the message to all topic subscribers for this socket.

      def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
        broadcast! socket, "new_msg", %{uid: uid, body: body}
        {:noreply, socket}
      end

  You can also push a message directly down the socket:

      # client asks for their current rank, push sent directly as a new event.
      def handle_in("current_rank", socket) do
        push socket, "current_rank", %{val: Game.get_rank(socket.assigns[:user])}
        {:noreply, socket}
      end

  ## Replies

  In addition to pushing messages out when you receive a `handle_in` event,
  you can also reply directly to a client event for request/response style
  messaging. This is useful when a client must know the result of an operation
  or to simply ack messages.

  For example, imagine creating a resource and replying with the created record:

      def handle_in("create:post", attrs, socket) do
        changeset = Post.changeset(%Post{}, attrs)

        if changeset.valid? do
          Repo.insert!(changeset)
          {:reply, {:ok, changeset}, socket}
        else
          {:reply, {:error, changeset.errors}, socket}
        end
      end

  Alternatively, you may just want to ack the status of the operation:

      def handle_in("create:post", attrs, socket) do
        changeset = Post.changeset(%Post{}, attrs)

        if changeset.valid? do
          Repo.insert!(changeset)
          {:reply, :ok, socket}
        else
          {:reply, :error, socket}
        end
      end

  ## Intercepting Outgoing Events

  When an event is broadcasted with `broadcast/3`, each channel subscriber can
  choose to intercept the event and have their `handle_out/3` callback triggered.
  This allows the event's payload to be customized on a socket by socket basis
  to append extra information, or conditionally filter the message from being
  delivered. If the event is not intercepted with `Phoenix.Channel.intercept/1`,
  then the message is pushed directly to the client:

      intercept ["new_msg", "user_joined"]

      # for every socket subscribing to this topic, append an `is_editable`
      # value for client metadata.
      def handle_out("new_msg", msg, socket) do
        push socket, "new_msg", Map.merge(msg,
          is_editable: User.can_edit_message?(socket.assigns[:user], msg)
        )
        {:noreply, socket}
      end

      # do not send broadcasted `"user_joined"` events if this socket's user
      # is ignoring the user who joined.
      def handle_out("user_joined", msg, socket) do
        unless User.ignoring?(socket.assigns[:user], msg.user_id) do
          push socket, "user_joined", msg
        end
        {:noreply, socket}
      end

  ## Broadcasting to an external topic

  In some cases, you will want to broadcast messages without the context of
  a `socket`. This could be for broadcasting from within your channel to an
  external topic, or broadcasting from elsewhere in your application like a
  controller or another process. Such can be done via your endpoint:

      # within channel
      def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
        ...
        broadcast_from! socket, "new_msg", %{uid: uid, body: body}
        MyApp.Endpoint.broadcast_from! self(), "rooms:superadmin",
          "new_msg", %{uid: uid, body: body}
        {:noreply, socket}
      end

      # within controller
      def create(conn, params) do
        ...
        MyApp.Endpoint.broadcast! "rooms:" <> rid, "new_msg", %{uid: uid, body: body}
        MyApp.Endpoint.broadcast! "rooms:superadmin", "new_msg", %{uid: uid, body: body}
        redirect conn, to: "/"
      end

  ## Terminate

  On termination, the channel callback `terminate/2` will be invoked with
  the error reason and the socket.

  If we are terminating because the client left, the reason will be
  `{:shutdown, :left}`. Similarly, if we are terminating because the
  client connection was closed, the reason will be `{:shutdown, :closed}`.

  If any of the callbacks return a `:stop` tuple, it will also
  trigger terminate with the reason given in the tuple.

  `terminate/2`, however, won't be invoked in case of errors nor in
  case of exits. This is the same behaviour as you find in Elixir
  abstractions like `GenServer` and others. Typically speaking, if you
  want to clean something up, it is better to monitor your channel
  process and do the clean up from another process.  Similar to GenServer,
  it would also be possible `:trap_exit` to guarantee that `terminate/2`
  is invoked. This practice is not encouraged though.
  """

  use Behaviour
  alias Phoenix.Socket
  alias Phoenix.Channel.Server

  @type reply :: status :: atom | {status :: atom, response :: map}
  @type socket_ref :: {transport_pid :: Pid, serializer :: Module.t,
                       topic :: binary, ref :: binary}

  defcallback code_change(old_vsn, Socket.t, extra :: term) ::
              {:ok, Socket.t} |
              {:error, reason :: term} when old_vsn: term | {:down, term}

  defcallback join(topic :: binary, auth_msg :: map, Socket.t) ::
              {:ok, Socket.t} |
              {:ok, map, Socket.t} |
              {:error, map}

  defcallback handle_in(event :: String.t, msg :: map, Socket.t) ::
              {:noreply, Socket.t} |
              {:reply, reply, Socket.t} |
              {:stop, reason :: term, Socket.t} |
              {:stop, reason :: term, reply, Socket.t}

  defcallback handle_info(term, Socket.t) ::
              {:noreply, Socket.t} |
              {:stop, reason :: term, Socket.t}

  defcallback terminate(msg :: map, Socket.t) ::
              {:shutdown, :left | :closed} |
              term

  defmacro __using__(_) do
    quote do
      @behaviour unquote(__MODULE__)
      @on_definition unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
      @phoenix_intercepts []

      import unquote(__MODULE__)
      import Phoenix.Socket, only: [assign: 3]

      def code_change(_old, socket, _extra), do: {:ok, socket}

      def handle_in(_event, _message, socket) do
        {:noreply, socket}
      end

      def handle_info(_message, socket), do: {:noreply, socket}

      def terminate(_reason, _socket), do: :ok

      defoverridable code_change: 3, handle_info: 2, handle_in: 3, terminate: 2
    end
  end

  defmacro __before_compile__(_) do
    quote do
      def __intercepts__, do: @phoenix_intercepts
    end
  end

  @doc """
  Defines which Channel events to intercept for `handle_out/3` callbacks.

  By default, broadcasted events are pushed directly to the client, but
  intercepting events gives your channel a chance to customize the event
  for the client to append extra information or filter the message from being
  delivered.

  *Note*: intercepting events can introduce significantly more overhead if a
  large number of subscribers must customize a message since the broadcast will
  be encoded N times instead of a single shared encoding across all subscribers.

  ## Examples

      intercept ["new_msg"]

      def handle_out("new_msg", payload, socket) do
        push socket, "new_msg", Map.merge(payload,
          is_editable: User.can_edit_message?(socket.assigns[:user], payload)
        )
        {:noreply, socket}
      end

  `handle_out/3` callbacks must return one of:

      {:noreply, Socket.t} |
      {:stop, reason :: term, Socket.t}

  """
  defmacro intercept(events) do
    quote do
      @phoenix_intercepts unquote(events)
    end
  end

  @doc false
  def __on_definition__(env, :def, :handle_out, [event, _payload, _socket], _, _)
    when is_binary(event) do

    unless event in Module.get_attribute(env.module, :phoenix_intercepts) do
      IO.write "#{Path.relative_to(env.file, File.cwd!)}:#{env.line}: [warning] " <>
               "An intercept for event \"#{event}\" has not yet been defined in #{env.module}.handle_out/3. " <>
               "Add \"#{event}\" to your list of intercepted events with intercept/1"
    end
  end
  def __on_definition__(_env, _kind, _name, _args, _guards, _body) do
  end

  @doc """
  Broadcast an event to all subscribers of the socket topic.

  The event's message must be a serializable map.

  ## Examples

      iex> broadcast socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def broadcast(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
    Server.broadcast pubsub_server, topic, event, message
  end

  @doc """
  Same as `broadcast/3` but raises if broadcast fails.
  """
  def broadcast!(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
    Server.broadcast! pubsub_server, topic, event, message
  end

  @doc """
  Broadcast event from pid to all subscribers of the socket topic.

  The channel that owns the socket will not receive the published
  message. The event's message must be a serializable map.

  ## Examples

      iex> broadcast_from socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def broadcast_from(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} = assert_joined!(socket)
    Server.broadcast_from pubsub_server, channel_pid, topic, event, message
  end

  @doc """
  Same as `broadcast_from/3` but raises if broadcast fails.
  """
  def broadcast_from!(socket, event, message) do
    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} = assert_joined!(socket)
    Server.broadcast_from! pubsub_server, channel_pid, topic, event, message
  end

  @doc """
  Sends event to the socket.

  The event's message must be a serializable map.

  ## Examples

      iex> push socket, "new_message", %{id: 1, content: "hello"}
      :ok

  """
  def push(socket, event, message) do
    %{transport_pid: transport_pid, topic: topic} = assert_joined!(socket)
    Server.push(transport_pid, topic, event, message, socket.serializer)
  end

  @doc """
  Replies asynchronously to a socket push.

  Useful when you need to reply to a push that can't otherwise be handled using
  the `{:reply, {status, payload}, socket}` return from your `handle_in`
  callbacks. `reply/3` will be used in the rare cases you need to perform work in
  another process and reply when finished by generating a reference to the push
  with `socket_ref/1`.

  *Note*: In such cases, a `socket_ref` should be generated and
  passed to the external process, so the `socket` itself is not leaked outside
  the channel. The `socket` holds information such as assigns and transport
  configuration, so it's important to not copy this information outside of the
  channel that owns it.

  ## Examples

      def handle_in("work", payload, socket) do
        Worker.perform(payload, socket_ref(socket))
        {:noreply, socket}
      end

      def handle_info({:work_complete, result, ref}, socket) do
        reply ref, {:ok, result}
        {:noreply, socket}
      end

  """
  @spec reply(socket_ref, reply) :: :ok
  def reply({transport_pid, serializer, topic, ref}, {status, payload}) do
    Server.reply(transport_pid, ref, topic, {status, payload}, serializer)
  end

  @doc """
  Generates a `socket_ref` for an async reply.

  See `reply/2` for example usage.
  """
  @spec socket_ref(Socket.t) :: socket_ref
  def socket_ref(%Socket{joined: true, ref: ref} = socket) when not is_nil(ref) do
    {socket.transport_pid, socket.serializer, socket.topic, ref}
  end
  def socket_ref(_socket) do
    raise ArgumentError, """
    Socket refs can only be generated for a socket that has joined with a push ref
    """
  end

  defp assert_joined!(%Socket{joined: true} = socket) do
    socket
  end

  defp assert_joined!(%Socket{joined: false}) do
    raise """
    `push`, `reply`, and `broadcast` can only be called after the socket has finished joining.
    To push a message on join, send to self and handle in handle_info/2, ie:

        def join(topic, auth_msg, socket) do
          ...
          send(self, :after_join)
          {:ok, socket}
        end

        def handle_info(:after_join, socket) do
          push socket, "feed", %{list: feed_items(socket)}
          {:noreply, socket}
        end
    """
  end
end


Fix last test!

Fix last test!

I needed to update the test on this one. This should all be updated now
to use `Scenario`s and unblocked for further development!

Yay - all formatters are updated!

defmodule Benchee.Formatters.HTMLTest do
  use ExUnit.Case
  alias Benchee.Formatters.HTML
  import ExUnit.CaptureIO
  doctest Benchee.Formatters.HTML

  @test_directory "test_output"
  @filename "#{@test_directory}/my.html"
  @expected_filename "#{@test_directory}/my_some_input_comparison.html"
  @sample_suite %Benchee.Suite{
                   scenarios: [
                     %Benchee.Benchmark.Scenario{
                       job_name: "My Job",
                       run_times: [190, 200, 210],
                       input_name: "Some Input",
                       input: "Some Input",
                       run_time_statistics: %Benchee.Statistics{
                        average:       200.0,
                        ips:           5000.0,
                        std_dev:       20,
                        std_dev_ratio: 0.1,
                        std_dev_ips:   500,
                        median:        190.0,
                        sample_size:   3,
                        minimum:       190,
                        maximum:       210
                       }
                     }
                   ],
                   system: %{elixir: "1.4.0", erlang: "19.1"},
                   configuration: %Benchee.Configuration{
                     formatter_options: %{html: %{file: @filename}}
                   }
                 }

  test ".format returns an HTML-ish string for every input" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ ~r/<html>.+<article>.+<\/html>/si
    end
  end

  test ".format has the important suite data in the html result" do
    Enum.each comparison_and_job_htmls(), fn(html) ->
      assert_includes html,
        ["[190,200,210]", "\"average\":200.0", "\"median\":190.0","\"ips\":5.0e3",
         "My Job", ">3<", ">190.00 μs<", ">210.00 μs<", ">200.00 μs<"]
     end

  end

  test ".format produces the right JSON data without the input level" do
    %{["Some Input", "comparison"] => html} = HTML.format @sample_suite

    assert html =~ "{\"statistics\":{\"My Job\""
  end

  test ".format shows the units alright" do
    Enum.each comparison_and_job_htmls(), fn(html) ->
      assert html =~ "±"
      assert html =~ "μs"
    end
  end

  defp comparison_and_job_htmls do
    %{["Some Input", "comparison"] => comparison_html,
      ["Some Input", "My Job"] => job_html} = HTML.format @sample_suite

    [comparison_html, job_html]
  end

  test ".format includes the elixir and erlang version everywhere" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ "Elixir 1.4.0"
      assert html =~ "Erlang 19.1"
    end
  end

  test ".format mentions the input" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ "Some Input"
    end
  end

  test ".format does not render the label if no input was given" do
    marker = Benchee.Benchmark.no_input()
    suite = %Benchee.Suite{
               scenarios: [
                 %Benchee.Benchmark.Scenario{
                   job_name: "My Job",
                   run_times: [190, 200, 210],
                   input_name: marker,
                   input: marker,
                   run_time_statistics: %Benchee.Statistics{
                   average:       200.0,
                   ips:           5000.0,
                   std_dev:       20,
                   std_dev_ratio: 0.1,
                   std_dev_ips:   500,
                   median:        190.0,
                   sample_size:   3,
                   minimum:       190,
                   maximum:       210
                   }
                 }
               ],
               system: %{elixir: "1.4.0", erlang: "19.1"},
               configuration: %Benchee.Configuration{
                 formatter_options: %{html: %{file: @filename}}
               }
             }

    format = HTML.format suite

    Enum.each format, fn({_, html}) ->
      refute html =~ "#{marker}"
      refute html =~ "input-label"
    end
  end

  defp assert_includes(html, expected_contents) do
    Enum.each expected_contents, fn(expected_content) ->
      assert html =~ expected_content
    end
  end

  test ".output returns the suite again unchanged and produces files" do
    try do
      capture_io fn ->
        return = Benchee.Formatters.HTML.output(@sample_suite)
        assert return == @sample_suite
      end

      assert File.exists? @expected_filename
      assert_assets_copied()

      content = File.read! @expected_filename
      assert_includes content, ["My Job", "average"]
    after
      if File.exists?(@test_directory), do: File.rm_rf! @test_directory
    end
  end

  test ".output let's you know where it put the html" do
    try do
      output = capture_io fn ->
        Benchee.Formatters.HTML.output(@sample_suite)
      end

      assert output =~ @expected_filename
      assert File.exists? @expected_filename
    after
      if File.exists?(@test_directory), do: File.rm_rf! @test_directory
    end
  end

  defp assert_assets_copied do
    assert File.exists? "#{@test_directory}/assets/javascripts/benchee.js"
    assert File.exists? "#{@test_directory}/assets/javascripts/plotly-1.20.5.min.js"
  end
end

defmodule Benchee.Formatters.HTMLTest do
  use ExUnit.Case
  alias Benchee.Formatters.HTML
  import ExUnit.CaptureIO
  doctest Benchee.Formatters.HTML

  @test_directory "test_output"
  @filename "#{@test_directory}/my.html"
  @expected_filename "#{@test_directory}/my_some_input_comparison.html"
  @sample_suite %Benchee.Suite{
                   scenarios: [
                     %Benchee.Benchmark.Scenario{
                       job_name: "My Job",
                       run_times: [190, 200, 210],
                       input_name: "Some Input",
                       input: "Some Input",
                       run_time_statistics: %Benchee.Statistics{
                        average:       200.0,
                        ips:           5000.0,
                        std_dev:       20,
                        std_dev_ratio: 0.1,
                        std_dev_ips:   500,
                        median:        190.0,
                        sample_size:   3,
                        minimum:       190,
                        maximum:       210
                       }
                     }
                   ],
                   system: %{elixir: "1.4.0", erlang: "19.1"},
                   configuration: %Benchee.Configuration{
                     formatter_options: %{html: %{file: @filename}}
                   }
                 }

  test ".format returns an HTML-ish string for every input" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ ~r/<html>.+<article>.+<\/html>/si
    end
  end

  test ".format has the important suite data in the html result" do
    Enum.each comparison_and_job_htmls(), fn(html) ->
      assert_includes html,
        ["[190,200,210]", "\"average\":200.0", "\"median\":190.0","\"ips\":5.0e3",
         "My Job", ">3<", ">190.00 μs<", ">210.00 μs<", ">200.00 μs<"]
     end

  end

  test ".format produces the right JSON data without the input level" do
    %{["Some Input", "comparison"] => html} = HTML.format @sample_suite

    assert html =~ "{\"statistics\":{\"My Job\""
  end

  test ".format shows the units alright" do
    Enum.each comparison_and_job_htmls(), fn(html) ->
      assert html =~ "±"
      assert html =~ "μs"
    end
  end

  defp comparison_and_job_htmls do
    %{["Some Input", "comparison"] => comparison_html,
      ["Some Input", "My Job"] => job_html} = HTML.format @sample_suite

    [comparison_html, job_html]
  end

  test ".format includes the elixir and erlang version everywhere" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ "Elixir 1.4.0"
      assert html =~ "Erlang 19.1"
    end
  end

  test ".format mentions the input" do
    format = HTML.format @sample_suite

    Enum.each format, fn({_, html}) ->
      assert html =~ "Some Input"
    end
  end

  test ".format does not render the label if no input was given" do
    marker = Benchee.Benchmark.no_input
    suite = %{
      configuration: %{formatter_options: %{html: %{file: @filename}}},
      statistics: %{
        marker => %{
          "My Job" => %{
            average:       200.0,
            ips:           5000.0,
            std_dev:       20,
            std_dev_ratio: 0.1,
            std_dev_ips:   500,
            median:        190.0,
            sample_size:   3,
            minimum:       190,
            maximum:       210
          }
        }
      },
      run_times: %{ marker => %{"My Job" => [190, 200, 210]}},
      system: %{elixir: "1.4.0", erlang: "19.1"}
    }
    format = HTML.format suite

    Enum.each format, fn({_, html}) ->
      refute html =~ "#{marker}"
      refute html =~ "input-label"
    end
  end

  defp assert_includes(html, expected_contents) do
    Enum.each expected_contents, fn(expected_content) ->
      assert html =~ expected_content
    end
  end

  test ".output returns the suite again unchanged and produces files" do
    try do
      capture_io fn ->
        return = Benchee.Formatters.HTML.output(@sample_suite)
        assert return == @sample_suite
      end

      assert File.exists? @expected_filename
      assert_assets_copied()

      content = File.read! @expected_filename
      assert_includes content, ["My Job", "average"]
    after
      if File.exists?(@test_directory), do: File.rm_rf! @test_directory
    end
  end

  test ".output let's you know where it put the html" do
    try do
      output = capture_io fn ->
        Benchee.Formatters.HTML.output(@sample_suite)
      end

      assert output =~ @expected_filename
      assert File.exists? @expected_filename
    after
      if File.exists?(@test_directory), do: File.rm_rf! @test_directory
    end
  end

  defp assert_assets_copied do
    assert File.exists? "#{@test_directory}/assets/javascripts/benchee.js"
    assert File.exists? "#{@test_directory}/assets/javascripts/plotly-1.20.5.min.js"
  end
end