Improve wording in Map module (#10318)

Improve wording in Map module (#10318)

defmodule Map do
  @moduledoc """
  Maps are the "go to" key-value data structure in Elixir.

  Maps can be created with the `%{}` syntax, and key-value pairs can be
  expressed as `key => value`:

      iex> %{}
      %{}
      iex> %{"one" => :two, 3 => "four"}
      %{3 => "four", "one" => :two}

  Key-value pairs in a map do not follow any order (that's why the printed map
  in the example above has a different order than the map that was created).

  Maps do not impose any restriction on the key type: anything can be a key in a
  map. As a key-value structure, maps do not allow duplicated keys. Keys are
  compared using the exact-equality operator (`===/2`). If colliding keys are defined
  in a map literal, the last one prevails.

  When the key in a key-value pair is an atom, the `key: value` shorthand syntax
  can be used (as in many other special forms), provided key-value pairs are put at
  the end:

      iex> %{"hello" => "world", a: 1, b: 2}
      %{:a => 1, :b => 2, "hello" => "world"}

  Keys in maps can be accessed through some of the functions in this module
  (such as `Map.get/3` or `Map.fetch/2`) or through the `map[]` syntax provided
  by the `Access` module:

      iex> map = %{a: 1, b: 2}
      iex> Map.fetch(map, :a)
      {:ok, 1}
      iex> map[:b]
      2
      iex> map["non_existing_key"]
      nil

  To access atom keys, one may also use the `map.key` notation. Note that `map.key`
  will raise a `KeyError` if the `map` doesn't contain the key `:key`, compared to
  `map[:key]`, that would return `nil`.

      map = %{foo: "bar", baz: "bong"}
      map.foo
      #=> "bar"
      map.non_existing_key
      #=> ** (KeyError) key :non_existing_key not found in: %{baz: "bong", foo: "bar"}

  > Note: do not add parens when accessing fields, such as in `data.key()`.
  > If parenthesis are used, Elixir will consider it to be a function call
  > on `data`, which would be expected to be an atom.

  The two syntaxes for accessing keys reveal the dual nature of maps. The `map[key]`
  syntax is used for dynamically created maps that may have any key, of any type.
  `map.key` is used with maps that hold a predetermined set of atoms keys, which are
  expected to always be present. Structs, defined via `defstruct/1`, are one example
  of such "static maps", where the keys can also be checked during compile time.

  Maps can be pattern matched on. When a map is on the left-hand side of a
  pattern match, it will match if the map on the right-hand side contains the
  keys on the left-hand side and their values match the ones on the left-hand
  side. This means that an empty map matches every map.

      iex> %{} = %{foo: "bar"}
      %{foo: "bar"}
      iex> %{a: a} = %{:a => 1, "b" => 2, [:c, :e, :e] => 3}
      iex> a
      1

  But this will raise a `MatchError` exception:

      %{:c => 3} = %{:a => 1, 2 => :b}

  Variables can be used as map keys both when writing map literals as well as
  when matching:

      iex> n = 1
      1
      iex> %{n => :one}
      %{1 => :one}
      iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
      %{1 => :one, 2 => :two, 3 => :three}

  Maps also support a specific update syntax to update the value stored under
  *existing* atom keys:

      iex> map = %{one: 1, two: 2}
      iex> %{map | one: "one"}
      %{one: "one", two: 2}

  When a key that does not exist in the map is updated a `KeyError` exception will be raised:

      %{map | three: 3}

  The functions in this module that need to find a specific key work in logarithmic time.
  This means that the time it takes to find keys grows as the map grows, but it's not
  directly proportional to the map size. In comparison to finding an element in a list,
  it performs better because lists have a linear time complexity. Some functions,
  such as `keys/1` and `values/1`, run in linear time because they need to get to every
  element in the map.

  Maps also implement the `Enumerable` protocol, so many functions to work with maps
  are found in the `Enum` module. Additionally, the following functions for maps are
  found in `Kernel`:

    * `map_size/1`

  """

  @type key :: any
  @type value :: any
  @compile {:inline, fetch: 2, fetch!: 2, get: 2, put: 3, delete: 2, has_key?: 2, replace!: 3}

  @doc """
  Returns all keys from `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.keys(%{a: 1, b: 2})
      [:a, :b]

  """
  @spec keys(map) :: [key]
  defdelegate keys(map), to: :maps

  @doc """
  Returns all values from `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.values(%{a: 1, b: 2})
      [1, 2]

  """
  @spec values(map) :: [value]
  defdelegate values(map), to: :maps

  @doc """
  Converts `map` to a list.

  Each key-value pair in the map is converted to a two-element tuple `{key,
  value}` in the resulting list.

  Inlined by the compiler.

  ## Examples

      iex> Map.to_list(%{a: 1})
      [a: 1]
      iex> Map.to_list(%{1 => 2})
      [{1, 2}]

  """
  @spec to_list(map) :: [{term, term}]
  defdelegate to_list(map), to: :maps

  @doc """
  Returns a new empty map.

  ## Examples

      iex> Map.new()
      %{}

  """
  @spec new :: map
  def new, do: %{}

  @doc """
  Creates a map from an `enumerable`.

  Duplicated keys are removed; the latest one prevails.

  ## Examples

      iex> Map.new([{:b, 1}, {:a, 2}])
      %{a: 2, b: 1}
      iex> Map.new(a: 1, a: 2, a: 3)
      %{a: 3}

  """
  @spec new(Enumerable.t()) :: map
  def new(enumerable)
  def new(list) when is_list(list), do: :maps.from_list(list)
  def new(%_{} = struct), do: new_from_enum(struct)
  def new(%{} = map), do: map
  def new(enum), do: new_from_enum(enum)

  defp new_from_enum(enumerable) do
    enumerable
    |> Enum.to_list()
    |> :maps.from_list()
  end

  @doc """
  Creates a map from an `enumerable` via the given transformation function.

  Duplicated keys are removed; the latest one prevails.

  ## Examples

      iex> Map.new([:a, :b], fn x -> {x, x} end)
      %{a: :a, b: :b}

  """
  @spec new(Enumerable.t(), (term -> {key, value})) :: map
  def new(enumerable, transform) when is_function(transform, 1) do
    enumerable
    |> Enum.to_list()
    |> new_transform(transform, [])
  end

  defp new_transform([], _fun, acc) do
    acc
    |> :lists.reverse()
    |> :maps.from_list()
  end

  defp new_transform([element | rest], fun, acc) do
    new_transform(rest, fun, [fun.(element) | acc])
  end

  @doc """
  Returns whether the given `key` exists in the given `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.has_key?(%{a: 1}, :a)
      true
      iex> Map.has_key?(%{a: 1}, :b)
      false

  """
  @spec has_key?(map, key) :: boolean
  def has_key?(map, key), do: :maps.is_key(key, map)

  @doc """
  Fetches the value for a specific `key` in the given `map`.

  If `map` contains the given `key` then its value is returned in the shape of `{:ok, value}`.
  If `map` doesn't contain `key`, `:error` is returned.

  Inlined by the compiler.

  ## Examples

      iex> Map.fetch(%{a: 1}, :a)
      {:ok, 1}
      iex> Map.fetch(%{a: 1}, :b)
      :error

  """
  @spec fetch(map, key) :: {:ok, value} | :error
  def fetch(map, key), do: :maps.find(key, map)

  @doc """
  Fetches the value for a specific `key` in the given `map`, erroring out if
  `map` doesn't contain `key`.

  If `map` contains `key`, the corresponding value is returned. If
  `map` doesn't contain `key`, a `KeyError` exception is raised.

  Inlined by the compiler.

  ## Examples

      iex> Map.fetch!(%{a: 1}, :a)
      1

  """
  @spec fetch!(map, key) :: value
  def fetch!(map, key) do
    :maps.get(key, map)
  end

  @doc """
  Puts the given `value` under `key` unless the entry `key`
  already exists in `map`.

  ## Examples

      iex> Map.put_new(%{a: 1}, :b, 2)
      %{a: 1, b: 2}
      iex> Map.put_new(%{a: 1, b: 2}, :a, 3)
      %{a: 1, b: 2}

  """
  @spec put_new(map, key, value) :: map
  def put_new(map, key, value) do
    case map do
      %{^key => _value} ->
        map

      %{} ->
        put(map, key, value)

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Puts a value under `key` only if the `key` already exists in `map`.

  ## Examples

      iex> Map.replace(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

      iex> Map.replace(%{a: 1}, :b, 2)
      %{a: 1}

  """
  @doc since: "1.11.0"
  @spec replace(map, key, value) :: map
  def replace(map, key, value) do
    case map do
      %{^key => _value} ->
        put(map, key, value)

      %{} ->
        map

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Puts a value under `key` only if the `key` already exists in `map`.

  If `key` is not present in `map`, a `KeyError` exception is raised.

  Inlined by the compiler.

  ## Examples

      iex> Map.replace!(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

      iex> Map.replace!(%{a: 1}, :b, 2)
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @doc since: "1.5.0"
  @spec replace!(map, key, value) :: map
  def replace!(map, key, value) do
    :maps.update(key, value, map)
  end

  @doc """
  Evaluates `fun` and puts the result under `key`
  in `map` unless `key` is already present.

  This function is useful in case you want to compute the value to put under
  `key` only if `key` is not already present, as for example, when the value is expensive to
  calculate or generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   3
      ...> end
      iex> Map.put_new_lazy(map, :a, fun)
      %{a: 1}
      iex> Map.put_new_lazy(map, :b, fun)
      %{a: 1, b: 3}

  """
  @spec put_new_lazy(map, key, (() -> value)) :: map
  def put_new_lazy(map, key, fun) when is_function(fun, 0) do
    case map do
      %{^key => _value} ->
        map

      %{} ->
        put(map, key, fun.())

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Returns a new map with all the key-value pairs in `map` where the key
  is in `keys`.

  If `keys` contains keys that are not in `map`, they're simply ignored.

  ## Examples

      iex> Map.take(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
      %{a: 1, c: 3}

  """
  @spec take(map, [key]) :: map
  def take(map, keys)

  def take(map, keys) when is_map(map) and is_list(keys) do
    take(keys, map, _acc = [])
  end

  def take(map, keys) when is_map(map) do
    IO.warn(
      "Map.take/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    take(map, Enum.to_list(keys))
  end

  def take(non_map, _keys) do
    :erlang.error({:badmap, non_map})
  end

  defp take([], _map, acc) do
    :maps.from_list(acc)
  end

  defp take([key | rest], map, acc) do
    acc =
      case map do
        %{^key => value} -> [{key, value} | acc]
        %{} -> acc
      end

    take(rest, map, acc)
  end

  @doc """
  Gets the value for a specific `key` in `map`.

  If `key` is present in `map` then its value `value` is
  returned. Otherwise, `default` is returned.

  If `default` is not provided, `nil` is used.

  ## Examples

      iex> Map.get(%{}, :a)
      nil
      iex> Map.get(%{a: 1}, :a)
      1
      iex> Map.get(%{a: 1}, :b)
      nil
      iex> Map.get(%{a: 1}, :b, 3)
      3

  """
  @spec get(map, key, value) :: value
  def get(map, key, default \\ nil) do
    case map do
      %{^key => value} ->
        value

      %{} ->
        default

      other ->
        :erlang.error({:badmap, other}, [map, key, default])
    end
  end

  @doc """
  Gets the value for a specific `key` in `map`.

  If `key` is present in `map` then its value `value` is
  returned. Otherwise, `fun` is evaluated and its result is returned.

  This is useful if the default value is very expensive to calculate or
  generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   13
      ...> end
      iex> Map.get_lazy(map, :a, fun)
      1
      iex> Map.get_lazy(map, :b, fun)
      13

  """
  @spec get_lazy(map, key, (() -> value)) :: value
  def get_lazy(map, key, fun) when is_function(fun, 0) do
    case map do
      %{^key => value} ->
        value

      %{} ->
        fun.()

      other ->
        :erlang.error({:badmap, other}, [map, key, fun])
    end
  end

  @doc """
  Puts the given `value` under `key` in `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.put(%{a: 1}, :b, 2)
      %{a: 1, b: 2}
      iex> Map.put(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

  """
  @spec put(map, key, value) :: map
  def put(map, key, value) do
    :maps.put(key, value, map)
  end

  @doc """
  Deletes the entry in `map` for a specific `key`.

  If the `key` does not exist, returns `map` unchanged.

  Inlined by the compiler.

  ## Examples

      iex> Map.delete(%{a: 1, b: 2}, :a)
      %{b: 2}
      iex> Map.delete(%{b: 2}, :a)
      %{b: 2}

  """
  @spec delete(map, key) :: map
  def delete(map, key), do: :maps.remove(key, map)

  @doc """
  Merges two maps into one.

  All keys in `map2` will be added to `map1`, overriding any existing one
  (i.e., the keys in `map2` "have precedence" over the ones in `map1`).

  If you have a struct and you would like to merge a set of keys into the
  struct, do not use this function, as it would merge all keys on the right
  side into the struct, even if the key is not part of the struct. Instead,
  use `Kernel.struct/2`.

  Inlined by the compiler.

  ## Examples

      iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
      %{a: 3, b: 2, d: 4}

  """
  @spec merge(map, map) :: map
  defdelegate merge(map1, map2), to: :maps

  @doc """
  Merges two maps into one, resolving conflicts through the given `fun`.

  All keys in `map2` will be added to `map1`. The given function will be invoked
  when there are duplicate keys; its arguments are `key` (the duplicate key),
  `value1` (the value of `key` in `map1`), and `value2` (the value of `key` in
  `map2`). The value returned by `fun` is used as the value under `key` in
  the resulting map.

  ## Examples

      iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4}, fn _k, v1, v2 ->
      ...>   v1 + v2
      ...> end)
      %{a: 4, b: 2, d: 4}

  """
  @spec merge(map, map, (key, value, value -> value)) :: map
  def merge(map1, map2, fun) when is_function(fun, 3) do
    if map_size(map1) > map_size(map2) do
      folder = fn key, val2, acc ->
        update(acc, key, val2, fn val1 -> fun.(key, val1, val2) end)
      end

      :maps.fold(folder, map1, map2)
    else
      folder = fn key, val2, acc ->
        update(acc, key, val2, fn val1 -> fun.(key, val2, val1) end)
      end

      :maps.fold(folder, map2, map1)
    end
  end

  @doc """
  Updates the `key` in `map` with the given function.

  If `key` is present in `map` then the existing value is passed to `fun` and its result is
  used as the updated value of `key`. If `key` is
  not present in `map`, `default` is inserted as the value of `key`. The default
  value will not be passed through the update function.

  ## Examples

      iex> Map.update(%{a: 1}, :a, 13, fn existing_value -> existing_value * 2 end)
      %{a: 2}
      iex> Map.update(%{a: 1}, :b, 11, fn existing_value -> existing_value * 2 end)
      %{a: 1, b: 11}

  """
  @spec update(map, key, default :: value, (existing_value :: value -> updated_value :: value)) ::
          map
  def update(map, key, default, fun) when is_function(fun, 1) do
    case map do
      %{^key => value} ->
        put(map, key, fun.(value))

      %{} ->
        put(map, key, default)

      other ->
        :erlang.error({:badmap, other}, [map, key, default, fun])
    end
  end

  @doc """
  Removes the value associated with `key` in `map` and returns the value and the updated map.

  If `key` is present in `map`, it returns `{value, new_map}` where `value` is the value of
  the key and `new_map` is the result of removing `key` from `map`. If `key`
  is not present in `map`, `{default, map}` is returned.

  ## Examples

      iex> Map.pop(%{a: 1}, :a)
      {1, %{}}
      iex> Map.pop(%{a: 1}, :b)
      {nil, %{a: 1}}
      iex> Map.pop(%{a: 1}, :b, 3)
      {3, %{a: 1}}

  """
  @spec pop(map, key, value) :: {value, new_map :: map}
  def pop(map, key, default \\ nil) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> {default, map}
    end
  end

  @doc """
  Returns and removes the value associated with `key` in `map` or raises
  if `key` is not present.

  Behaves the same as `pop/3` but raises if `key` is not present in `map`.

  ## Examples

      iex> Map.pop!(%{a: 1}, :a)
      {1, %{}}
      iex> Map.pop!(%{a: 1, b: 2}, :a)
      {1, %{b: 2}}
      iex> Map.pop!(%{a: 1}, :b)
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @doc since: "1.10.0"
  @spec pop!(map, key) :: {value, map}
  def pop!(map, key) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> raise KeyError, key: key, term: map
    end
  end

  @doc """
  Lazily returns and removes the value associated with `key` in `map`.

  If `key` is present in `map`, it returns `{value, new_map}` where `value` is the value of
  the key and `new_map` is the result of removing `key` from `map`. If `key`
  is not present in `map`, `{fun_result, map}` is returned, where `fun_result`
  is the result of applying `fun`.

  This is useful if the default value is very expensive to calculate or
  generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   13
      ...> end
      iex> Map.pop_lazy(map, :a, fun)
      {1, %{}}
      iex> Map.pop_lazy(map, :b, fun)
      {13, %{a: 1}}

  """
  @spec pop_lazy(map, key, (() -> value)) :: {value, map}
  def pop_lazy(map, key, fun) when is_function(fun, 0) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> {fun.(), map}
    end
  end

  @doc """
  Drops the given `keys` from `map`.

  If `keys` contains keys that are not in `map`, they're simply ignored.

  ## Examples

      iex> Map.drop(%{a: 1, b: 2, c: 3}, [:b, :d])
      %{a: 1, c: 3}

  """
  @spec drop(map, [key]) :: map
  def drop(map, keys)

  def drop(map, keys) when is_map(map) and is_list(keys) do
    drop_keys(keys, map)
  end

  def drop(map, keys) when is_map(map) do
    IO.warn(
      "Map.drop/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    drop(map, Enum.to_list(keys))
  end

  def drop(non_map, keys) do
    :erlang.error({:badmap, non_map}, [non_map, keys])
  end

  defp drop_keys([], acc), do: acc

  defp drop_keys([key | rest], acc) do
    drop_keys(rest, delete(acc, key))
  end

  @doc """
  Takes all entries corresponding to the given `keys` in `map` and extracts
  them into a separate map.

  Returns a tuple with the new map and the old map with removed keys.

  Keys for which there are no entries in `map` are ignored.

  ## Examples

      iex> Map.split(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
      {%{a: 1, c: 3}, %{b: 2}}

  """
  @spec split(map, [key]) :: {map, map}
  def split(map, keys)

  def split(map, keys) when is_map(map) and is_list(keys) do
    split(keys, [], map)
  end

  def split(map, keys) when is_map(map) do
    IO.warn(
      "Map.split/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    split(map, Enum.to_list(keys))
  end

  def split(non_map, keys) do
    :erlang.error({:badmap, non_map}, [non_map, keys])
  end

  defp split([], included, excluded) do
    {:maps.from_list(included), excluded}
  end

  defp split([key | rest], included, excluded) do
    case excluded do
      %{^key => value} ->
        split(rest, [{key, value} | included], delete(excluded, key))

      _other ->
        split(rest, included, excluded)
    end
  end

  @doc """
  Updates `key` with the given function.

  If `key` is present in `map` then the existing value is passed to `fun` and its result is
  used as the updated value of `key`. If `key` is
  not present in `map`, a `KeyError` exception is raised.

  ## Examples

      iex> Map.update!(%{a: 1}, :a, &(&1 * 2))
      %{a: 2}

      iex> Map.update!(%{a: 1}, :b, &(&1 * 2))
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @spec update!(map, key, (existing_value :: value -> updated_value :: value)) :: map
  def update!(map, key, fun) when is_function(fun, 1) do
    value = fetch!(map, key)
    put(map, key, fun.(value))
  end

  @doc """
  Gets the value from `key` and updates it, all in one pass.

  `fun` is called with the current value under `key` in `map` (or `nil` if `key`
  is not present in `map`) and must return a two-element tuple: the current value
  (the retrieved value, which can be operated on before being returned) and the
  new value to be stored under `key` in the resulting new map. `fun` may also
  return `:pop`, which means the current value shall be removed from `map` and
  returned (making this function behave like `Map.pop(map, key)`).

  The returned value is a two-element tuple with the current value returned by
  `fun` and a new map with the updated value under `key`.

  ## Examples

      iex> Map.get_and_update(%{a: 1}, :a, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {1, %{a: "new value!"}}

      iex> Map.get_and_update(%{a: 1}, :b, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {nil, %{b: "new value!", a: 1}}

      iex> Map.get_and_update(%{a: 1}, :a, fn _ -> :pop end)
      {1, %{}}

      iex> Map.get_and_update(%{a: 1}, :b, fn _ -> :pop end)
      {nil, %{a: 1}}

  """
  @spec get_and_update(map, key, (value -> {current_value, new_value :: value} | :pop)) ::
          {current_value, map}
        when current_value: value
  def get_and_update(map, key, fun) when is_function(fun, 1) do
    current = get(map, key)

    case fun.(current) do
      {get, update} ->
        {get, put(map, key, update)}

      :pop ->
        {current, delete(map, key)}

      other ->
        raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
    end
  end

  @doc """
  Gets the value from `key` and updates it, all in one pass. Raises if there is no `key`.

  Behaves exactly like `get_and_update/3`, but raises a `KeyError` exception if
  `key` is not present in `map`.

  ## Examples

      iex> Map.get_and_update!(%{a: 1}, :a, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {1, %{a: "new value!"}}

      iex> Map.get_and_update!(%{a: 1}, :b, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      ** (KeyError) key :b not found in: %{a: 1}

      iex> Map.get_and_update!(%{a: 1}, :a, fn _ ->
      ...>   :pop
      ...> end)
      {1, %{}}

  """
  @spec get_and_update!(map, key, (value -> {current_value, new_value :: value} | :pop)) ::
          {current_value, map}
        when current_value: value
  def get_and_update!(map, key, fun) when is_function(fun, 1) do
    value = fetch!(map, key)

    case fun.(value) do
      {get, update} ->
        {get, put(map, key, update)}

      :pop ->
        {value, delete(map, key)}

      other ->
        raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
    end
  end

  @doc """
  Converts a `struct` to map.

  It accepts the struct module or a struct itself and
  simply removes the `__struct__` field from the given struct
  or from a new struct generated from the given module.

  ## Example

      defmodule User do
        defstruct [:name]
      end

      Map.from_struct(User)
      #=> %{name: nil}

      Map.from_struct(%User{name: "john"})
      #=> %{name: "john"}

  """
  @spec from_struct(atom | struct) :: map
  def from_struct(struct) when is_atom(struct) do
    delete(struct.__struct__(), :__struct__)
  end

  def from_struct(%_{} = struct) do
    delete(struct, :__struct__)
  end

  @doc """
  Checks if two maps are equal.

  Two maps are considered to be equal if they contain
  the same keys and those keys contain the same values.

  ## Examples

      iex> Map.equal?(%{a: 1, b: 2}, %{b: 2, a: 1})
      true
      iex> Map.equal?(%{a: 1, b: 2}, %{b: 1, a: 2})
      false

  """
  @spec equal?(map, map) :: boolean
  def equal?(map1, map2)

  def equal?(%{} = map1, %{} = map2), do: map1 === map2
  def equal?(%{} = map1, map2), do: :erlang.error({:badmap, map2}, [map1, map2])
  def equal?(term, other), do: :erlang.error({:badmap, term}, [term, other])

  @doc false
  @deprecated "Use Kernel.map_size/1 instead"
  def size(map) do
    map_size(map)
  end
end

defmodule Map do
  @moduledoc """
  Maps are the "go to" key-value data structure in Elixir.

  Maps can be created with the `%{}` syntax, and key-value pairs can be
  expressed as `key => value`:

      iex> %{}
      %{}
      iex> %{"one" => :two, 3 => "four"}
      %{3 => "four", "one" => :two}

  Key-value pairs in a map do not follow any order (that's why the printed map
  in the example above has a different order than the map that was created).

  Maps do not impose any restriction on the key type: anything can be a key in a
  map. As a key-value structure, maps do not allow duplicated keys. Keys are
  compared using the exact-equality operator (`===/2`). If colliding keys are defined
  in a map literal, the last one prevails.

  When the key in a key-value pair is an atom, the `key: value` shorthand syntax
  can be used (as in many other special forms), provided key-value pairs are put at
  the end:

      iex> %{"hello" => "world", a: 1, b: 2}
      %{:a => 1, :b => 2, "hello" => "world"}

  Keys in maps can be accessed through some of the functions in this module
  (such as `Map.get/3` or `Map.fetch/2`) or through the `map[]` syntax provided
  by the `Access` module:

      iex> map = %{a: 1, b: 2}
      iex> Map.fetch(map, :a)
      {:ok, 1}
      iex> map[:b]
      2
      iex> map["non_existing_key"]
      nil

  To access atom keys, one may also use the `map.key` notation. Note that `map.key`
  will raise a `KeyError` if the `map` doesn't contain the key `:key`, compared to
  `map[:key]`, that would return `nil`.

      map = %{foo: "bar", baz: "bong"}
      map.foo
      #=> "bar"
      map.non_existing_key
      #=> ** (KeyError) key :non_existing_key not found in: %{baz: "bong", foo: "bar"}

  > Note: do not add parens when accessing fields, such as in `data.key()`.
  > If parenthesis are used, Elixir will consider it to be a function call
  > on `data`, which would be expected to be an atom.

  The two syntaxes for accessing keys reveal the dual nature of maps. The `map[key]`
  syntax is used for dynamically created maps that may have any key, of any type.
  `map.key` is used with maps that hold a predetermined set of atoms keys, which are
  expected to always be present. Structs, defined via `defstruct/1`, are one example
  of such "static maps", where the keys can also be checked during compile time.

  Maps can be pattern matched on. When a map is on the left-hand side of a
  pattern match, it will match if the map on the right-hand side contains the
  keys on the left-hand side and their values match the ones on the left-hand
  side. This means that an empty map matches every map.

      iex> %{} = %{foo: "bar"}
      %{foo: "bar"}
      iex> %{a: a} = %{:a => 1, "b" => 2, [:c, :e, :e] => 3}
      iex> a
      1

  But this will raise a `MatchError` exception:

      %{:c => 3} = %{:a => 1, 2 => :b}

  Variables can be used as map keys both when writing map literals as well as
  when matching:

      iex> n = 1
      1
      iex> %{n => :one}
      %{1 => :one}
      iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
      %{1 => :one, 2 => :two, 3 => :three}

  Maps also support a specific update syntax to update the value stored under
  *existing* atom keys:

      iex> map = %{one: 1, two: 2}
      iex> %{map | one: "one"}
      %{one: "one", two: 2}

  When a key that does not exist in the map is updated a `KeyError` exception will be raised:

      %{map | three: 3}

  The functions in this module that need to find a specific key work in logarithmic time.
  This means that the time it takes to find keys grows as the map grows, but it's not
  directly proportional to the map size. In comparison to finding an element in a list,
  it performs better because lists have a linear time complexity. Some functions,
  such as `keys/1` and `values/1`, run in linear time because they need to get to every
  element in the map.

  Maps also implement the `Enumerable` protocol, so many functions to work with maps
  are found in the `Enum` module. Additionally, the following functions for maps are
  found in `Kernel`:

    * `map_size/1`

  """

  @type key :: any
  @type value :: any
  @compile {:inline, fetch: 2, fetch!: 2, get: 2, put: 3, delete: 2, has_key?: 2, replace!: 3}

  @doc """
  Returns all keys from `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.keys(%{a: 1, b: 2})
      [:a, :b]

  """
  @spec keys(map) :: [key]
  defdelegate keys(map), to: :maps

  @doc """
  Returns all values from `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.values(%{a: 1, b: 2})
      [1, 2]

  """
  @spec values(map) :: [value]
  defdelegate values(map), to: :maps

  @doc """
  Converts `map` to a list.

  Each key-value pair in the map is converted to a two-element tuple `{key,
  value}` in the resulting list.

  Inlined by the compiler.

  ## Examples

      iex> Map.to_list(%{a: 1})
      [a: 1]
      iex> Map.to_list(%{1 => 2})
      [{1, 2}]

  """
  @spec to_list(map) :: [{term, term}]
  defdelegate to_list(map), to: :maps

  @doc """
  Returns a new empty map.

  ## Examples

      iex> Map.new()
      %{}

  """
  @spec new :: map
  def new, do: %{}

  @doc """
  Creates a map from an `enumerable`.

  Duplicated keys are removed; the latest one prevails.

  ## Examples

      iex> Map.new([{:b, 1}, {:a, 2}])
      %{a: 2, b: 1}
      iex> Map.new(a: 1, a: 2, a: 3)
      %{a: 3}

  """
  @spec new(Enumerable.t()) :: map
  def new(enumerable)
  def new(list) when is_list(list), do: :maps.from_list(list)
  def new(%_{} = struct), do: new_from_enum(struct)
  def new(%{} = map), do: map
  def new(enum), do: new_from_enum(enum)

  defp new_from_enum(enumerable) do
    enumerable
    |> Enum.to_list()
    |> :maps.from_list()
  end

  @doc """
  Creates a map from an `enumerable` via the given transformation function.

  Duplicated keys are removed; the latest one prevails.

  ## Examples

      iex> Map.new([:a, :b], fn x -> {x, x} end)
      %{a: :a, b: :b}

  """
  @spec new(Enumerable.t(), (term -> {key, value})) :: map
  def new(enumerable, transform) when is_function(transform, 1) do
    enumerable
    |> Enum.to_list()
    |> new_transform(transform, [])
  end

  defp new_transform([], _fun, acc) do
    acc
    |> :lists.reverse()
    |> :maps.from_list()
  end

  defp new_transform([element | rest], fun, acc) do
    new_transform(rest, fun, [fun.(element) | acc])
  end

  @doc """
  Returns whether the given `key` exists in the given `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.has_key?(%{a: 1}, :a)
      true
      iex> Map.has_key?(%{a: 1}, :b)
      false

  """
  @spec has_key?(map, key) :: boolean
  def has_key?(map, key), do: :maps.is_key(key, map)

  @doc """
  Fetches the value for a specific `key` in the given `map`.

  If `map` contains the given `key` with value `value`, then `{:ok, value}` is
  returned. If `map` doesn't contain `key`, `:error` is returned.

  Inlined by the compiler.

  ## Examples

      iex> Map.fetch(%{a: 1}, :a)
      {:ok, 1}
      iex> Map.fetch(%{a: 1}, :b)
      :error

  """
  @spec fetch(map, key) :: {:ok, value} | :error
  def fetch(map, key), do: :maps.find(key, map)

  @doc """
  Fetches the value for a specific `key` in the given `map`, erroring out if
  `map` doesn't contain `key`.

  If `map` contains the given `key`, the corresponding value is returned. If
  `map` doesn't contain `key`, a `KeyError` exception is raised.

  Inlined by the compiler.

  ## Examples

      iex> Map.fetch!(%{a: 1}, :a)
      1

  """
  @spec fetch!(map, key) :: value
  def fetch!(map, key) do
    :maps.get(key, map)
  end

  @doc """
  Puts the given `value` under `key` unless the entry `key`
  already exists in `map`.

  ## Examples

      iex> Map.put_new(%{a: 1}, :b, 2)
      %{a: 1, b: 2}
      iex> Map.put_new(%{a: 1, b: 2}, :a, 3)
      %{a: 1, b: 2}

  """
  @spec put_new(map, key, value) :: map
  def put_new(map, key, value) do
    case map do
      %{^key => _value} ->
        map

      %{} ->
        put(map, key, value)

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Puts a value under `key` only if the `key` already exists in `map`.

  ## Examples

      iex> Map.replace(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

      iex> Map.replace(%{a: 1}, :b, 2)
      %{a: 1}

  """
  @doc since: "1.11.0"
  @spec replace(map, key, value) :: map
  def replace(map, key, value) do
    case map do
      %{^key => _value} ->
        put(map, key, value)

      %{} ->
        map

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Puts a value under `key` only if the `key` already exists in `map`.

  If `key` is not present in `map`, a `KeyError` exception is raised.

  Inlined by the compiler.

  ## Examples

      iex> Map.replace!(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

      iex> Map.replace!(%{a: 1}, :b, 2)
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @doc since: "1.5.0"
  @spec replace!(map, key, value) :: map
  def replace!(map, key, value) do
    :maps.update(key, value, map)
  end

  @doc """
  Evaluates `fun` and puts the result under `key`
  in `map` unless `key` is already present.

  This function is useful in case you want to compute the value to put under
  `key` only if `key` is not already present, as for example, when the value is expensive to
  calculate or generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   3
      ...> end
      iex> Map.put_new_lazy(map, :a, fun)
      %{a: 1}
      iex> Map.put_new_lazy(map, :b, fun)
      %{a: 1, b: 3}

  """
  @spec put_new_lazy(map, key, (() -> value)) :: map
  def put_new_lazy(map, key, fun) when is_function(fun, 0) do
    case map do
      %{^key => _value} ->
        map

      %{} ->
        put(map, key, fun.())

      other ->
        :erlang.error({:badmap, other})
    end
  end

  @doc """
  Returns a new map with all the key-value pairs in `map` where the key
  is in `keys`.

  If `keys` contains keys that are not in `map`, they're simply ignored.

  ## Examples

      iex> Map.take(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
      %{a: 1, c: 3}

  """
  @spec take(map, [key]) :: map
  def take(map, keys)

  def take(map, keys) when is_map(map) and is_list(keys) do
    take(keys, map, _acc = [])
  end

  def take(map, keys) when is_map(map) do
    IO.warn(
      "Map.take/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    take(map, Enum.to_list(keys))
  end

  def take(non_map, _keys) do
    :erlang.error({:badmap, non_map})
  end

  defp take([], _map, acc) do
    :maps.from_list(acc)
  end

  defp take([key | rest], map, acc) do
    acc =
      case map do
        %{^key => value} -> [{key, value} | acc]
        %{} -> acc
      end

    take(rest, map, acc)
  end

  @doc """
  Gets the value for a specific `key` in `map`.

  If `key` is present in `map` with value `value`, then `value` is
  returned. Otherwise, `default` is returned.

  If `default` is not provided, `nil` is used.

  ## Examples

      iex> Map.get(%{}, :a)
      nil
      iex> Map.get(%{a: 1}, :a)
      1
      iex> Map.get(%{a: 1}, :b)
      nil
      iex> Map.get(%{a: 1}, :b, 3)
      3

  """
  @spec get(map, key, value) :: value
  def get(map, key, default \\ nil) do
    case map do
      %{^key => value} ->
        value

      %{} ->
        default

      other ->
        :erlang.error({:badmap, other}, [map, key, default])
    end
  end

  @doc """
  Gets the value for a specific `key` in `map`.

  If `key` is present in `map` with value `value`, then `value` is
  returned. Otherwise, `fun` is evaluated and its result is returned.

  This is useful if the default value is very expensive to calculate or
  generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   13
      ...> end
      iex> Map.get_lazy(map, :a, fun)
      1
      iex> Map.get_lazy(map, :b, fun)
      13

  """
  @spec get_lazy(map, key, (() -> value)) :: value
  def get_lazy(map, key, fun) when is_function(fun, 0) do
    case map do
      %{^key => value} ->
        value

      %{} ->
        fun.()

      other ->
        :erlang.error({:badmap, other}, [map, key, fun])
    end
  end

  @doc """
  Puts the given `value` under `key` in `map`.

  Inlined by the compiler.

  ## Examples

      iex> Map.put(%{a: 1}, :b, 2)
      %{a: 1, b: 2}
      iex> Map.put(%{a: 1, b: 2}, :a, 3)
      %{a: 3, b: 2}

  """
  @spec put(map, key, value) :: map
  def put(map, key, value) do
    :maps.put(key, value, map)
  end

  @doc """
  Deletes the entry in `map` for a specific `key`.

  If the `key` does not exist, returns `map` unchanged.

  Inlined by the compiler.

  ## Examples

      iex> Map.delete(%{a: 1, b: 2}, :a)
      %{b: 2}
      iex> Map.delete(%{b: 2}, :a)
      %{b: 2}

  """
  @spec delete(map, key) :: map
  def delete(map, key), do: :maps.remove(key, map)

  @doc """
  Merges two maps into one.

  All keys in `map2` will be added to `map1`, overriding any existing one
  (i.e., the keys in `map2` "have precedence" over the ones in `map1`).

  If you have a struct and you would like to merge a set of keys into the
  struct, do not use this function, as it would merge all keys on the right
  side into the struct, even if the key is not part of the struct. Instead,
  use `Kernel.struct/2`.

  Inlined by the compiler.

  ## Examples

      iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
      %{a: 3, b: 2, d: 4}

  """
  @spec merge(map, map) :: map
  defdelegate merge(map1, map2), to: :maps

  @doc """
  Merges two maps into one, resolving conflicts through the given `fun`.

  All keys in `map2` will be added to `map1`. The given function will be invoked
  when there are duplicate keys; its arguments are `key` (the duplicate key),
  `value1` (the value of `key` in `map1`), and `value2` (the value of `key` in
  `map2`). The value returned by `fun` is used as the value under `key` in
  the resulting map.

  ## Examples

      iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4}, fn _k, v1, v2 ->
      ...>   v1 + v2
      ...> end)
      %{a: 4, b: 2, d: 4}

  """
  @spec merge(map, map, (key, value, value -> value)) :: map
  def merge(map1, map2, fun) when is_function(fun, 3) do
    if map_size(map1) > map_size(map2) do
      folder = fn key, val2, acc ->
        update(acc, key, val2, fn val1 -> fun.(key, val1, val2) end)
      end

      :maps.fold(folder, map1, map2)
    else
      folder = fn key, val2, acc ->
        update(acc, key, val2, fn val1 -> fun.(key, val2, val1) end)
      end

      :maps.fold(folder, map2, map1)
    end
  end

  @doc """
  Updates the `key` in `map` with the given function.

  If `key` is present in `map` with value `value`, `fun` is invoked with
  argument `value` and its result is used as the new value of `key`. If `key` is
  not present in `map`, `default` is inserted as the value of `key`. The default
  value will not be passed through the update function.

  ## Examples

      iex> Map.update(%{a: 1}, :a, 13, fn existing_value -> existing_value * 2 end)
      %{a: 2}
      iex> Map.update(%{a: 1}, :b, 11, fn existing_value -> existing_value * 2 end)
      %{a: 1, b: 11}

  """
  @spec update(map, key, default :: value, (existing_value :: value -> updated_value :: value)) ::
          map
  def update(map, key, default, fun) when is_function(fun, 1) do
    case map do
      %{^key => value} ->
        put(map, key, fun.(value))

      %{} ->
        put(map, key, default)

      other ->
        :erlang.error({:badmap, other}, [map, key, default, fun])
    end
  end

  @doc """
  Returns and removes the value associated with `key` in `map`.

  If `key` is present in `map` with value `value`, `{value, new_map}` is
  returned where `new_map` is the result of removing `key` from `map`. If `key`
  is not present in `map`, `{default, map}` is returned.

  ## Examples

      iex> Map.pop(%{a: 1}, :a)
      {1, %{}}
      iex> Map.pop(%{a: 1}, :b)
      {nil, %{a: 1}}
      iex> Map.pop(%{a: 1}, :b, 3)
      {3, %{a: 1}}

  """
  @spec pop(map, key, value) :: {value, map}
  def pop(map, key, default \\ nil) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> {default, map}
    end
  end

  @doc """
  Returns and removes the value associated with `key` in `map` or raises
  if `key` is not present.

  Behaves the same as `pop/3` but raises if `key` is not present in `map`.

  ## Examples

      iex> Map.pop!(%{a: 1}, :a)
      {1, %{}}
      iex> Map.pop!(%{a: 1, b: 2}, :a)
      {1, %{b: 2}}
      iex> Map.pop!(%{a: 1}, :b)
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @doc since: "1.10.0"
  @spec pop!(map, key) :: {value, map}
  def pop!(map, key) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> raise KeyError, key: key, term: map
    end
  end

  @doc """
  Lazily returns and removes the value associated with `key` in `map`.

  If `key` is present in `map` with value `value`, `{value, new_map}` is
  returned where `new_map` is the result of removing `key` from `map`. If `key`
  is not present in `map`, `{fun_result, map}` is returned, where `fun_result`
  is the result of applying `fun`.

  This is useful if the default value is very expensive to calculate or
  generally difficult to setup and teardown again.

  ## Examples

      iex> map = %{a: 1}
      iex> fun = fn ->
      ...>   # some expensive operation here
      ...>   13
      ...> end
      iex> Map.pop_lazy(map, :a, fun)
      {1, %{}}
      iex> Map.pop_lazy(map, :b, fun)
      {13, %{a: 1}}

  """
  @spec pop_lazy(map, key, (() -> value)) :: {value, map}
  def pop_lazy(map, key, fun) when is_function(fun, 0) do
    case :maps.take(key, map) do
      {_, _} = tuple -> tuple
      :error -> {fun.(), map}
    end
  end

  @doc """
  Drops the given `keys` from `map`.

  If `keys` contains keys that are not in `map`, they're simply ignored.

  ## Examples

      iex> Map.drop(%{a: 1, b: 2, c: 3}, [:b, :d])
      %{a: 1, c: 3}

  """
  @spec drop(map, [key]) :: map
  def drop(map, keys)

  def drop(map, keys) when is_map(map) and is_list(keys) do
    drop_keys(keys, map)
  end

  def drop(map, keys) when is_map(map) do
    IO.warn(
      "Map.drop/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    drop(map, Enum.to_list(keys))
  end

  def drop(non_map, keys) do
    :erlang.error({:badmap, non_map}, [non_map, keys])
  end

  defp drop_keys([], acc), do: acc

  defp drop_keys([key | rest], acc) do
    drop_keys(rest, delete(acc, key))
  end

  @doc """
  Takes all entries corresponding to the given `keys` in `map` and extracts
  them into a separate map.

  Returns a tuple with the new map and the old map with removed keys.

  Keys for which there are no entries in `map` are ignored.

  ## Examples

      iex> Map.split(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
      {%{a: 1, c: 3}, %{b: 2}}

  """
  @spec split(map, [key]) :: {map, map}
  def split(map, keys)

  def split(map, keys) when is_map(map) and is_list(keys) do
    split(keys, [], map)
  end

  def split(map, keys) when is_map(map) do
    IO.warn(
      "Map.split/2 with an Enumerable of keys that is not a list is deprecated. " <>
        " Use a list of keys instead."
    )

    split(map, Enum.to_list(keys))
  end

  def split(non_map, keys) do
    :erlang.error({:badmap, non_map}, [non_map, keys])
  end

  defp split([], included, excluded) do
    {:maps.from_list(included), excluded}
  end

  defp split([key | rest], included, excluded) do
    case excluded do
      %{^key => value} ->
        split(rest, [{key, value} | included], delete(excluded, key))

      _other ->
        split(rest, included, excluded)
    end
  end

  @doc """
  Updates `key` with the given function.

  If `key` is present in `map` with value `value`, `fun` is invoked with
  argument `value` and its result is used as the new value of `key`. If `key` is
  not present in `map`, a `KeyError` exception is raised.

  ## Examples

      iex> Map.update!(%{a: 1}, :a, &(&1 * 2))
      %{a: 2}

      iex> Map.update!(%{a: 1}, :b, &(&1 * 2))
      ** (KeyError) key :b not found in: %{a: 1}

  """
  @spec update!(map, key, (current_value :: value -> new_value :: value)) :: map
  def update!(map, key, fun) when is_function(fun, 1) do
    value = fetch!(map, key)
    put(map, key, fun.(value))
  end

  @doc """
  Gets the value from `key` and updates it, all in one pass.

  `fun` is called with the current value under `key` in `map` (or `nil` if `key`
  is not present in `map`) and must return a two-element tuple: the current value
  (the retrieved value, which can be operated on before being returned) and the
  new value to be stored under `key` in the resulting new map. `fun` may also
  return `:pop`, which means the current value shall be removed from `map` and
  returned (making this function behave like `Map.pop(map, key)`).

  The returned value is a two-element tuple with the current value returned by
  `fun` and a new map with the updated value under `key`.

  ## Examples

      iex> Map.get_and_update(%{a: 1}, :a, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {1, %{a: "new value!"}}

      iex> Map.get_and_update(%{a: 1}, :b, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {nil, %{b: "new value!", a: 1}}

      iex> Map.get_and_update(%{a: 1}, :a, fn _ -> :pop end)
      {1, %{}}

      iex> Map.get_and_update(%{a: 1}, :b, fn _ -> :pop end)
      {nil, %{a: 1}}

  """
  @spec get_and_update(map, key, (value -> {current_value, new_value :: value} | :pop)) ::
          {current_value, map}
        when current_value: value
  def get_and_update(map, key, fun) when is_function(fun, 1) do
    current = get(map, key)

    case fun.(current) do
      {get, update} ->
        {get, put(map, key, update)}

      :pop ->
        {current, delete(map, key)}

      other ->
        raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
    end
  end

  @doc """
  Gets the value from `key` and updates it, all in one pass. Raises if there is no `key`.

  Behaves exactly like `get_and_update/3`, but raises a `KeyError` exception if
  `key` is not present in `map`.

  ## Examples

      iex> Map.get_and_update!(%{a: 1}, :a, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      {1, %{a: "new value!"}}

      iex> Map.get_and_update!(%{a: 1}, :b, fn current_value ->
      ...>   {current_value, "new value!"}
      ...> end)
      ** (KeyError) key :b not found in: %{a: 1}

      iex> Map.get_and_update!(%{a: 1}, :a, fn _ ->
      ...>   :pop
      ...> end)
      {1, %{}}

  """
  @spec get_and_update!(map, key, (value -> {current_value, new_value :: value} | :pop)) ::
          {current_value, map}
        when current_value: value
  def get_and_update!(map, key, fun) when is_function(fun, 1) do
    value = fetch!(map, key)

    case fun.(value) do
      {get, update} ->
        {get, put(map, key, update)}

      :pop ->
        {value, delete(map, key)}

      other ->
        raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
    end
  end

  @doc """
  Converts a `struct` to map.

  It accepts the struct module or a struct itself and
  simply removes the `__struct__` field from the given struct
  or from a new struct generated from the given module.

  ## Example

      defmodule User do
        defstruct [:name]
      end

      Map.from_struct(User)
      #=> %{name: nil}

      Map.from_struct(%User{name: "john"})
      #=> %{name: "john"}

  """
  @spec from_struct(atom | struct) :: map
  def from_struct(struct) when is_atom(struct) do
    delete(struct.__struct__(), :__struct__)
  end

  def from_struct(%_{} = struct) do
    delete(struct, :__struct__)
  end

  @doc """
  Checks if two maps are equal.

  Two maps are considered to be equal if they contain
  the same keys and those keys contain the same values.

  ## Examples

      iex> Map.equal?(%{a: 1, b: 2}, %{b: 2, a: 1})
      true
      iex> Map.equal?(%{a: 1, b: 2}, %{b: 1, a: 2})
      false

  """
  @spec equal?(map, map) :: boolean
  def equal?(map1, map2)

  def equal?(%{} = map1, %{} = map2), do: map1 === map2
  def equal?(%{} = map1, map2), do: :erlang.error({:badmap, map2}, [map1, map2])
  def equal?(term, other), do: :erlang.error({:badmap, term}, [term, other])

  @doc false
  @deprecated "Use Kernel.map_size/1 instead"
  def size(map) do
    map_size(map)
  end
end


Ensure records can be created with no field, closes #299

Code.require_file "../test_helper", __FILE__

defrecord RecordTest.FileInfo,
  Record.extract(:file_info, from_lib: "kernel/include/file.hrl")

name = RecordTest.DynamicName
defrecord name, a: 0, b: 1

defrecord RecordTest.WithNoField, []

defmodule RecordTest do
  use ExUnit.Case

  test :record_constructor_with_dict do
    record   = RecordTest.FileInfo.new(type: :regular)
    assert record.type == :regular
    assert record.access == nil
  end

  test :record_accessors do
    record = RecordTest.FileInfo.new(file_info)
    assert record.type == :regular
    assert record.access == :read_write

    new_record = record.access :read
    assert new_record.access == :read
  end

  test :dynamic_record_name do
    record = RecordTest.DynamicName.new
    assert record.a == 0
    assert record.b == 1
  end

  test :dynamic_update do
    record = RecordTest.DynamicName.new
    assert record.update_a(10 + &1).a == 10
  end

  test :is_record do
    assert is_record(RecordTest.FileInfo.new, RecordTest.FileInfo)
    refute is_record(a_list, RecordTest.FileInfo)
    refute is_record(RecordTest.FileInfo.new, List)
  end

  defp file_info do
    { :ok, file_info } = Erlang.file.read_file_info(__FILE__)
    file_info
  end

  defp a_list do
    [:a, :b, :c]
  end
end

Code.require_file "../test_helper", __FILE__

defrecord RecordTest.FileInfo,
  Record.extract(:file_info, from_lib: "kernel/include/file.hrl")

name = RecordTest.DynamicName
defrecord name, a: 0, b: 1

defmodule RecordTest do
  use ExUnit.Case

  test :record_constructor_with_dict do
    record   = RecordTest.FileInfo.new(type: :regular)
    assert record.type == :regular
    assert record.access == nil
  end

  test :record_accessors do
    record = RecordTest.FileInfo.new(file_info)
    assert record.type == :regular
    assert record.access == :read_write

    new_record = record.access :read
    assert new_record.access == :read
  end

  test :dynamic_record_name do
    record = RecordTest.DynamicName.new
    assert record.a == 0
    assert record.b == 1
  end

  test :dynamic_update do
    record = RecordTest.DynamicName.new
    assert record.update_a(10 + &1).a == 10
  end

  test :is_record do
    assert is_record(RecordTest.FileInfo.new, RecordTest.FileInfo)
    refute is_record(a_list, RecordTest.FileInfo)
    refute is_record(RecordTest.FileInfo.new, List)
  end

  defp file_info do
    { :ok, file_info } = Erlang.file.read_file_info(__FILE__)
    file_info
  end

  defp a_list do
    [:a, :b, :c]
  end
end


Improve lookup/3 typespec

Improve lookup/3 typespec

defmodule Geolix.Adapter.MMDB2.Database do
  @moduledoc false

  alias Geolix.Adapter.MMDB2.Result
  alias Geolix.Adapter.MMDB2.Storage
  alias MMDB2Decoder.Metadata

  @mmdb2_opts [double_precision: 8, float_precision: 4, map_keys: :atoms]

  @doc """
  Performs a lookup in a loaded database.
  """
  @spec lookup(:inet.ip_address(), Keyword.t(), %{id: atom}) :: map | nil
  def lookup(ip, opts, %{id: id}) do
    case Storage.get(id) do
      {meta, tree, data} when is_map(meta) and is_binary(tree) and is_binary(data) ->
        lookup(ip, meta, tree, data, opts)

      _ ->
        nil
    end
  end

  @doc """
  Returns the metadata for a loaded database.
  """
  @spec metadata(%{id: atom}) :: Metadata.t() | nil
  def metadata(%{id: id}) do
    case Storage.get(id) do
      {meta, _, _} when is_map(meta) ->
        meta

      _ ->
        nil
    end
  end

  defp lookup(ip, meta, tree, data, opts) do
    case MMDB2Decoder.lookup(ip, meta, tree, data, @mmdb2_opts) do
      {:ok, result} when is_map(result) ->
        result_as = Keyword.get(opts, :as, :struct)

        result
        |> Map.put(:ip_address, ip)
        |> maybe_to_struct(result_as, meta, opts)

      _ ->
        nil
    end
  end

  defp maybe_to_struct(result, :raw, _, _), do: result

  defp maybe_to_struct(result, :struct, %{database_type: type}, opts) do
    locale = Keyword.get(opts, :locale, :en)

    Result.to_struct(type, result, locale)
  end
end

defmodule Geolix.Adapter.MMDB2.Database do
  @moduledoc false

  alias Geolix.Adapter.MMDB2.Result
  alias Geolix.Adapter.MMDB2.Storage
  alias MMDB2Decoder.Metadata

  @mmdb2_opts [double_precision: 8, float_precision: 4, map_keys: :atoms]

  @doc """
  Performs a lookup in a loaded database.
  """
  @spec lookup(tuple, Keyword.t(), map) :: map | nil
  def lookup(ip, opts, %{id: id}) do
    case Storage.get(id) do
      {meta, tree, data} when is_map(meta) and is_binary(tree) and is_binary(data) ->
        lookup(ip, meta, tree, data, opts)

      _ ->
        nil
    end
  end

  @doc """
  Returns the metadata for a loaded database.
  """
  @spec metadata(%{id: atom}) :: Metadata.t() | nil
  def metadata(%{id: id}) do
    case Storage.get(id) do
      {meta, _, _} when is_map(meta) ->
        meta

      _ ->
        nil
    end
  end

  defp lookup(ip, meta, tree, data, opts) do
    case MMDB2Decoder.lookup(ip, meta, tree, data, @mmdb2_opts) do
      {:ok, result} when is_map(result) ->
        result_as = Keyword.get(opts, :as, :struct)

        result
        |> Map.put(:ip_address, ip)
        |> maybe_to_struct(result_as, meta, opts)

      _ ->
        nil
    end
  end

  defp maybe_to_struct(result, :raw, _, _), do: result

  defp maybe_to_struct(result, :struct, %{database_type: type}, opts) do
    locale = Keyword.get(opts, :locale, :en)

    Result.to_struct(type, result, locale)
  end
end


Cleanup Page documentation.

Cleanup Page documentation.

defmodule Wobserver.Page do
  @moduledoc """
  Page management for custom commands and pages in api and wobserver.
  """

  alias Wobserver.Page

  @pages_table :wobserver_pages

  @typedoc ~S"""
  Accepted page formats.
  """
  @type data ::
    Page.t
    | map
    | {String.t, atom, fun}
    | {String.t, atom, fun, boolean}

  @typedoc ~S"""
  Page structure.

  Fields:
    - `title`, the name of the page. Is used for the web interface menu.
    - `command`, single atom to associate the page with.
    - `callback`, function to be evaluated, when the a api is called or page is viewd.
                  The result is converted to JSON and displayed.
    - `options`, map containing options for the page.

  Options:
    - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.
    - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.
  """
  @type t :: %__MODULE__{
    title: String.t,
    command: atom,
    callback: fun,
    options: keyword,
  }

  defstruct [
    :title,
    :command,
    :callback,
    options: %{
      api_only: false,
      refresh: 1,
    },
  ]

  @doc ~S"""
  List all registered pages.

  For every page the following information is given:
    - `title`
    - `command`
    - `api_only`
    - `refresh`
  """
  @spec list :: list(map)
  def list do
    ensure_table()

    @pages_table
    |> :ets.match(:"$1")
    |> Enum.map(fn [{command, %Page{title: title, options: options}}] ->
         %{
           title: title,
           command: command,
           api_only: options.api_only,
           refresh: options.refresh,
          }
       end)
  end

  @doc ~S"""
  Find the page for a given `command`

  Returns `:page_not_found`, if no page can be found.
  """
  @spec find(command :: atom) :: Page.t
  def find(command) do
    ensure_table()

    case :ets.lookup(@pages_table, command) do
      [{^command, page}] -> page
      _ -> :page_not_found
    end
  end

  @doc ~S"""
  Calls the function associated with the `command`/page.

  Returns the result of the function or `:page_not_found`, if the page can not be found.
  """
  @spec call(Page.t | atom) :: any
  def call(:page_not_found), do: :page_not_found
  def call(%Page{callback: callback}), do: callback.()
  def call(command) when is_atom(command), do: command |> find() |> call()
  def call(_), do: :page_not_found

  @doc ~S"""
  Registers a `page` with `:wobserver`.

  Returns true if succesfully added. (otherwise false)

  The following inputs are accepted:
    - `{title, command, callback}`
    - `{title, command, callback, options}`
    - a `map` with the following fields:
        - `title`
        - `command`
        - `callback`
        - `options` (optional)

  The fields are used as followed:
    - `title`, the name of the page. Is used for the web interface menu.
    - `command`, single atom to associate the page with.
    - `callback`, function to be evaluated, when the a api is called or page is viewd.
                  The result is converted to JSON and displayed.
    - `options`, options for the page.

  The following options can be set:
    - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.
    - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.
  """
  @spec register(page :: Page.data) :: boolean
  def register(page)

  def register({title, command, callback}),
    do: register(title, command, callback)
  def register({title, command, callback, options}),
    do: register(title, command, callback, options)

  def register(page = %Page{}) do
    ensure_table()
    :ets.insert @pages_table, {page.command, page}
  end

  def register(%{title: t, command: command, callback: call, options: options}),
    do: register(t, command, call, options)
  def register(%{title: title, command: command, callback: callback}),
    do: register(title, command, callback)
  def register(_), do: false

  @doc ~S"""
  Registers a `page` with `:wobserver`.

  The arguments are used as followed:
    - `title`, the name of the page. Is used for the web interface menu.
    - `command`, single atom to associate the page with.
    - `callback`, function to be evaluated, when the a api is called or page is viewd.
                  The result is converted to JSON and displayed.
    - `options`, options for the page.

  The following options can be set:
    - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.
    - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.
  """
  @spec register(
    title :: String.t,
    command :: atom,
    callback :: fun,
    options :: keyword
  ) :: boolean
  def register(title, command, callback, options \\ []) do
    register(%Page{
      title: title,
      command: command,
      callback: callback,
      options: %{
        api_only: Keyword.get(options, :api_only, false),
        refresh: Keyword.get(options, :refresh, 1.0)
      },
    })
  end

  @doc ~S"""
  Loads custom pages from configuration and adds them to `:wobserver`.

  To add custom pages set the `:pages` option.
  The `:pages` option must be a list of page data.

  The page data can be formatted as:
    - `{title, command, callback}`
    - `{title, command, callback, options}`
    - a `map` with the following fields:
        - `title`
        - `command`
        - `callback`
        - `options` (optional)

  For more information and types see: `Wobserver.Page.register/1`.

  Example:
  ```elixir
  config :wobserver,
    pages: [
      {"Example", :example, fn -> %{x:  9} end}
    ]
  ```
  """
  @spec load_config :: [any]
  def load_config do
    ensure_table()

    :wobserver
    |> Application.get_env(:pages, [])
    |> Enum.map(&register/1)
  end

  # Helpers

  defp ensure_table do
    case :ets.info(@pages_table) do
      :undefined ->
        :ets.new @pages_table, [:named_table, :public]
        true
      _ ->
        true
    end
  end
end

defmodule Wobserver.Page do
  @moduledoc """
  Page management for custom commands and pages in api and wobserver.
  """

  alias Wobserver.Page

  @pages_table :wobserver_pages

  @typedoc ~S"""
  Accepted page formats.
  """
  @type data ::
    Page.t
    | map
    | {String.t, atom, fun}
    | {String.t, atom, fun, boolean}

  @typedoc ~S"""
  Page structure.

  Fields:
    - `title`, the name of the page. Is used for the web interface menu.
    - `command`, single atom to associate the page with.
    - `callback`, function to be evaluated, when the a api is called or page is viewd.
                  The result is converted to JSON and displayed.
    - `options`, map containing options for the page.

  Options:
    - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.
    - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.
  """
  @type t :: %__MODULE__{
    title: String.t,
    command: atom,
    callback: fun,
    options: keyword,
  }

  defstruct [
    :title,
    :command,
    :callback,
    options: %{
      api_only: false,
      refresh: 1,
    },
  ]

  @doc ~S"""
  List all registered pages.

  For every page the following information is given:
    - `title`
    - `command`
    - `api_only`
    - `refresh`
  """
  @spec list :: list(map)
  def list do
    ensure_table()

    @pages_table
    |> :ets.match(:"$1")
    |> Enum.map(fn [{command, %Page{title: title, options: options}}] ->
         %{
           title: title,
           command: command,
           api_only: options.api_only,
           refresh: options.refresh,
          }
       end)
  end

  @doc ~S"""
  Find the page for a given command.

  Returns `:page_not_found`, if no page can be found.
  """
  @spec find(command :: atom) :: Page.t
  def find(command) do
    ensure_table()

    case :ets.lookup(@pages_table, command) do
      [{^command, page}] -> page
      _ -> :page_not_found
    end
  end

  @doc ~S"""
  Calls the function associated with the command/page.

  Returns the result of the function or `:page_not_found`, if the page can not be found.
  """
  @spec call(Page.t | atom) :: any
  def call(:page_not_found), do: :page_not_found
  def call(%Page{callback: callback}), do: callback.()
  def call(command) when is_atom(command), do: command |> find() |> call()
  def call(_), do: :page_not_found

  @doc ~S"""
  Registers a page with `:wobserver`.

  Returns true if succesfully added. (otherwise false)

  The following inputs are accepted:
    - `{title, command, callback}`
    - `{title, command, callback, options}`
    - a `map` with the following fields:
        - `title`
        - `command`
        - `callback`
        - `options` (optional)

  The fields are used as followed:
    - `title`, the name of the page. Is used for the web interface menu.
    - `command`, single atom to associate the page with.
    - `callback`, function to be evaluated, when the a api is called or page is viewd.
                  The result is converted to JSON and displayed.
    - `options`, options for the page.

  The following options can be set:
    - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.
    - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.
  """
  @spec register(page :: Page.data) :: boolean
  def register(page)

  def register({title, command, callback}),
    do: register(title, command, callback)
  def register({title, command, callback, options}),
    do: register(title, command, callback, options)

  def register(page = %Page{}) do
    ensure_table()
    :ets.insert @pages_table, {page.command, page}
  end

  def register(%{title: t, command: command, callback: call, options: options}),
    do: register(t, command, call, options)
  def register(%{title: title, command: command, callback: callback}),
    do: register(title, command, callback)
  def register(_), do: false

  @doc ~S"""
  Registers a page with `:wobserver`.

  For more information and types see: `Wobserver.Page.register/1`.
  """
  @spec register(
    title :: String.t,
    command :: atom,
    callback :: fun,
    options :: keyword
  ) :: boolean
  def register(title, command, callback, options \\ []) do
    register(%Page{
      title: title,
      command: command,
      callback: callback,
      options: %{
        api_only: Keyword.get(options, :api_only, false),
        refresh: Keyword.get(options, :refresh, 1.0)
      },
    })
  end

  @doc ~S"""
  Loads custom pages from configuration and adds them to `:wobserver`.

  To add custom pages set the `:pages` option.
  The `:pages` option must be a list of page data.

  The page data can be formatted as:
    - `{title, command, callback}`
    - `{title, command, callback, options}`
    - a `map` with the following fields:
        - `title`
        - `command`
        - `callback`
        - `options` (optional)

  For more information and types see: `Wobserver.Page.register/1`.

  Example:
  ```elixir
  config :wobserver,
    pages: [
      {"Example", :example, fn -> %{x:  9} end}
    ]
  ```
  """
  @spec load_config :: [any]
  def load_config do
    ensure_table()

    :wobserver
    |> Application.get_env(:pages, [])
    |> Enum.map(&register/1)
  end

  # Helpers

  defp ensure_table do
    case :ets.info(@pages_table) do
      :undefined ->
        :ets.new @pages_table, [:named_table, :public]
        true
      _ ->
        true
    end
  end
end


Improve formatting and naming

Improve formatting and naming

defmodule Benchee.Formatters.Console do
  @moduledoc """
  Formatter to transform the statistics output into a structure suitable for
  output through `IO.puts` on the console.
  """

  alias Benchee.Statistics
  alias Benchee.Unit.{Count, Duration}

  @default_label_width 4 # Length of column header
  @ips_width 13
  @average_width 15
  @deviation_width 13
  @median_width 15

  @doc """
  Formats the benchmark statistis using `Benchee.Formatters.Console.format/1`
  and then prints it out directly to the console using `IO.puts/2`
  """
  def output(suite) do
    suite
    |> format
    |> IO.puts
  end

  @doc """
  Formats the benchmark statistics to a report suitable for output on the CLI.

  ## Examples

  ```
  iex> jobs = %{"My Job" => %{average: 200.0, ips: 5000.0, std_dev_ratio: 0.1, median: 190.0}}
  iex> Benchee.Formatters.Console.format(%{statistics: jobs, config: %{print: %{comparison: false}}})
  ["\nName             ips        average    deviation         median\n",
  "My Job        5.00 K      200.00 μs    (±10.00%)      190.00 μs"]

  ```

  """
  def format(%{statistics: job_stats, config: config}) do
    sorted_stats = Statistics.sort(job_stats)
    units = units(sorted_stats)
    label_width = label_width job_stats
    [column_descriptors(label_width) | job_reports(sorted_stats, units, label_width)
      ++ comparison_report(sorted_stats, units, label_width, config)]
    |> remove_last_blank_line
  end

  defp column_descriptors(label_width) do
    "\n~*s~*s~*s~*s~*s\n"
    |> :io_lib.format([-label_width, "Name", @ips_width, "ips",
                       @average_width, "average",
                       @deviation_width, "deviation", @median_width, "median"])
    |> to_string
  end

  defp label_width(jobs) do
    max_label_width = jobs
      |> Enum.map(fn({job_name, _}) -> String.length(job_name) end)
      |> Stream.concat([@default_label_width])
      |> Enum.max
    max_label_width + 1
  end

  defp job_reports(jobs, units, label_width) do
    Enum.map(jobs, fn(job) -> format_job job, units, label_width end)
  end

  defp units(jobs) do
    # Produces a map like
    #   %{run_time: [12345, 15431, 13222], ips: [1, 2, 3]}
    collected_values =
      jobs
      |> Enum.flat_map(fn({_name, job}) -> Map.to_list(job) end)
      # TODO: Simplify when dropping support for 1.2
      # For compatibility with Elixir 1.2. In 1.3, the following group-reduce-map
      # can b replaced by a single call to `group_by/3`
      #   Enum.group_by(fn({stat_name, _}) -> stat_name end, fn({_, value}) -> value end)
      |> Enum.group_by(fn({stat_name, _value}) -> stat_name end)
      |> Enum.reduce(%{}, fn({stat_name, occurrences}, acc) ->
        Map.put(acc, stat_name, Enum.map(occurrences, fn({_stat_name, value}) -> value end))
      end)

    %{
      run_time: Duration.best(collected_values.average),
      ips:      Count.best(collected_values.ips),
    }
  end

  defp format_job({name, %{average:       average,
                           ips:           ips,
                           std_dev_ratio: std_dev_ratio,
                           median:        median}
                         },
                         %{run_time:      run_time_unit,
                           ips:           ips_unit,
                         }, label_width) do
    "~*s~*ts~*ts~*ts~*ts\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit),
                       @average_width, run_time_out(average, run_time_unit),
                       @deviation_width, deviation_out(std_dev_ratio),
                       @median_width, run_time_out(median, run_time_unit)])
    |> to_string
  end

  defp ips_out(ips, unit) do
    Count.format(Count.scale(ips, unit))
  end

  defp run_time_out(average, unit) do
    Duration.format(Duration.scale(average, unit))
  end

  defp deviation_out(std_dev_ratio) do
    "(~ts~.2f%)"
    |> :io_lib.format(["±", std_dev_ratio * 100.0])
    |> to_string
  end

  defp comparison_report([_reference], _, _, _config) do
    [] # No need for a comparison when only one benchmark was run
  end
  defp comparison_report(_, _, _, %{console: %{comparison: false}}) do
    []
  end
  defp comparison_report([reference | other_jobs], units, label_width, _config) do
    [
      comparison_descriptor,
      reference_report(reference, units, label_width) |
      comparisons(reference, units, label_width, other_jobs)
    ]
  end

  defp reference_report({name, %{ips: ips}}, %{ips: ips_unit}, label_width) do
    "~*s~*s\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit)])
    |> to_string
  end

  defp comparisons({_, reference_stats}, units, label_width, jobs_to_compare) do
    Enum.map jobs_to_compare, fn(job = {_, job_stats}) ->
      format_comparison(job, units, label_width, (reference_stats.ips / job_stats.ips))
    end
  end

  defp format_comparison({name, %{ips: ips}}, %{ips: ips_unit}, label_width, times_slower) do
    "~*s~*s - ~.2fx slower\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit), times_slower])
    |> to_string
  end

  defp comparison_descriptor do
    "\nComparison: \n"
  end

  defp remove_last_blank_line([head]) do
    [String.rstrip(head)]
  end
  defp remove_last_blank_line([head | tail]) do
    [head | remove_last_blank_line(tail)]
  end

end

defmodule Benchee.Formatters.Console do
  @moduledoc """
  Formatter to transform the statistics output into a structure suitable for
  output through `IO.puts` on the console.
  """

  alias Benchee.Statistics
  alias Benchee.Unit.{Count, Duration}

  @default_label_width 4 # Length of column header
  @ips_width 13
  @average_width 15
  @deviation_width 13
  @median_width 15

  @doc """
  Formats the benchmark statistis using `Benchee.Formatters.Console.format/1`
  and then prints it out directly to the console using `IO.puts/2`
  """
  def output(suite) do
    suite
    |> format
    |> IO.puts
  end

  @doc """
  Formats the benchmark statistics to a report suitable for output on the CLI.

  ## Examples

  ```
  iex> jobs = %{"My Job" => %{average: 200.0, ips: 5000.0, std_dev_ratio: 0.1, median: 190.0}}
  iex> Benchee.Formatters.Console.format(%{statistics: jobs, config: %{print: %{comparison: false}}})
  ["\nName             ips        average    deviation         median\n",
  "My Job        5.00 K      200.00 μs    (±10.00%)      190.00 μs"]

  ```

  """
  def format(%{statistics: job_stats, config: config}) do
    sorted_stats = Statistics.sort(job_stats)
    units = units(sorted_stats)
    label_width = label_width job_stats
    [column_descriptors(label_width) | job_reports(sorted_stats, units, label_width)
      ++ comparison_report(sorted_stats, units, label_width, config)]
    |> remove_last_blank_line
  end

  defp column_descriptors(label_width) do
    "\n~*s~*s~*s~*s~*s\n"
    |> :io_lib.format([-label_width, "Name", @ips_width, "ips",
                       @average_width, "average",
                       @deviation_width, "deviation", @median_width, "median"])
    |> to_string
  end

  defp label_width(jobs) do
    max_label_width = jobs
      |> Enum.map(fn({job_name, _}) -> String.length(job_name) end)
      |> Stream.concat([@default_label_width])
      |> Enum.max
    max_label_width + 1
  end

  defp job_reports(jobs, units, label_width) do
    Enum.map(jobs, fn(job) -> format_job job, units, label_width end)
  end

  defp units(jobs) do
    # Produces a map like
    #   %{run_time: [12345, 15431, 13222], ips: [1, 2, 3]}
    collected_values = jobs
    |> Enum.flat_map(fn({_name, job}) -> Map.to_list(job) end)
    # TODO: Simplify when dropping support for 1.2
    # For compatibility with Elixir 1.2. In 1.3, the following group-reduce-map
    # can b replaced by a single call to `group_by/3`
    #   Enum.group_by(fn({measurement, _}) -> measurement end, fn({_, value}) -> value end)
    |> Enum.group_by(fn({measurement, _unit}) -> measurement end)
    |> Enum.reduce(%{}, fn({measurement, occurrences}, acc) ->
      Map.put(acc, measurement, Enum.map(occurrences, fn({_meas, value}) -> value end))
    end)

    %{
      run_time: Duration.best(collected_values.average),
      ips:      Count.best(collected_values.ips),
    }
  end

  defp format_job({name, %{average:       average,
                           ips:           ips,
                           std_dev_ratio: std_dev_ratio,
                           median:        median}
                         },
                         %{run_time:      run_time_unit,
                           ips:           ips_unit,
                         }, label_width) do
    "~*s~*ts~*ts~*ts~*ts\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit),
                       @average_width, run_time_out(average, run_time_unit),
                       @deviation_width, deviation_out(std_dev_ratio),
                       @median_width, run_time_out(median, run_time_unit)])
    |> to_string
  end

  defp ips_out(ips, unit) do
    Count.format(Count.scale(ips, unit))
  end

  defp run_time_out(average, unit) do
    Duration.format(Duration.scale(average, unit))
  end

  defp deviation_out(std_dev_ratio) do
    "(~ts~.2f%)"
    |> :io_lib.format(["±", std_dev_ratio * 100.0])
    |> to_string
  end

  defp comparison_report([_reference], _, _, _config) do
    [] # No need for a comparison when only one benchmark was run
  end
  defp comparison_report(_, _, _, %{console: %{comparison: false}}) do
    []
  end
  defp comparison_report([reference | other_jobs], units, label_width, _config) do
    [
      comparison_descriptor,
      reference_report(reference, units, label_width) |
      comparisons(reference, units, label_width, other_jobs)
    ]
  end

  defp reference_report({name, %{ips: ips}}, %{ips: ips_unit}, label_width) do
    "~*s~*s\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit)])
    |> to_string
  end

  defp comparisons({_, reference_stats}, units, label_width, jobs_to_compare) do
    Enum.map jobs_to_compare, fn(job = {_, job_stats}) ->
      format_comparison(job, units, label_width, (reference_stats.ips / job_stats.ips))
    end
  end

  defp format_comparison({name, %{ips: ips}}, %{ips: ips_unit}, label_width, times_slower) do
    "~*s~*s - ~.2fx slower\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit), times_slower])
    |> to_string
  end

  defp comparison_descriptor do
    "\nComparison: \n"
  end

  defp remove_last_blank_line([head]) do
    [String.rstrip(head)]
  end
  defp remove_last_blank_line([head | tail]) do
    [head | remove_last_blank_line(tail)]
  end

end


Remove commented code

Remove commented code

defmodule ExDocEPUB.Formatter.EPUB do
  @moduledoc """
  Provide EPUB documentation
  """

  alias ExDoc.Formatter.HTML
  alias ExDocEPUB.Formatter.EPUB.Templates

  @doc """
  Generate EPUB documentation for the given modules
  """
  @spec run(list, %ExDoc.Config{}) :: String.t
  def run(module_nodes, config) when is_map(config) do
    output = Path.expand(config.output)
    File.rm_rf!(output)
    :ok = File.mkdir_p("#{output}/OEBPS")

    generate_assets(output, config)

    all = HTML.Autolink.all(module_nodes)
    modules = HTML.filter_list(:modules, all)
    exceptions = HTML.filter_list(:exceptions, all)
    protocols = HTML.filter_list(:protocols, all)

    generate_mimetype(output)
    generate_container(output)
    generate_content(output, config, all)
    generate_toc(output, config, all)
    generate_title(output, config)
    generate_list(output, config, modules)
    generate_list(output, config, exceptions)
    generate_list(output, config, protocols)

    #generate_epub(output)
    #File.rm_rf!(output)
  end

  defp templates_path(other) do
    Path.expand("epub/templates/#{other}", __DIR__)
  end

  defp assets do
   [{ templates_path("css/*.css"), "." }]
  end

  defp generate_assets(output, _config) do
    Enum.each assets, fn({pattern, _dir}) ->
      Enum.map Path.wildcard(pattern), fn(file) ->
        base = Path.basename(file)
        File.copy(file, "#{output}/OEBPS/#{base}")
      end
    end
  end

  defp generate_mimetype(output) do
    content = "application/epub+zip"
    File.write("#{output}/mimetype", content)
  end

  defp generate_container(output) do
    content = Templates.container_template()
    File.mkdir_p("#{output}/META-INF")
    File.write("#{output}/META-INF/container.xml", content)
  end

  defp generate_content(output, config, all) do
    content = Templates.content_template(config, all)
    File.write("#{output}/OEBPS/content.opf", content)
  end

  defp generate_toc(output, config, all) do
    content = Templates.toc_template(config, all)
    File.write("#{output}/OEBPS/toc.ncx", content)
  end

  defp generate_title(output, config) do
    content = Templates.title_template(config)
    File.write("#{output}/OEBPS/title.html", content)
  end

  defp generate_list(output, config, nodes) do
    File.mkdir_p("#{output}/OEBPS/modules")
    nodes
    |> Enum.map(&Task.async(fn -> generate_module_page(output, config, &1) end))
    |> Enum.map(&Task.await/1)
  end

  defp generate_module_page(output, config, node) do
    content = Templates.module_page(config, node)
    File.write("#{output}/OEBPS/modules/#{node.id}.html", content)
  end

  #defp generate_epub(output) do
    #zip -0Xq book.epub mimetype
    #zip -Xr9Dq book.epub *
    #project = "elixir.epub"
    #{:ok, _} = :zip.create(String.to_char_list(project),
    #             files_to_add(output),
    #             uncompress: ['mimetype'])
    #:ok
  #end
end

defmodule ExDocEPUB.Formatter.EPUB do
  @moduledoc """
  Provide EPUB documentation
  """

  alias ExDoc.Formatter.HTML
  alias ExDocEPUB.Formatter.EPUB.Templates

  @doc """
  Generate EPUB documentation for the given modules
  """
  @spec run(list, %ExDoc.Config{}) :: String.t
  def run(module_nodes, config) when is_map(config) do
    output = Path.expand(config.output)
    File.rm_rf!(output)
    :ok = File.mkdir_p("#{output}/OEBPS")

    generate_assets(output, config)

    all = HTML.Autolink.all(module_nodes)
    modules = HTML.filter_list(:modules, all)
    exceptions = HTML.filter_list(:exceptions, all)
    protocols = HTML.filter_list(:protocols, all)

    #has_readme = config.readme && generate_readme(output, module_nodes, config, modules, exceptions, protocols)
    #generate_overview(modules, exceptions, protocols, output, config, has_readme)

    generate_mimetype(output)
    generate_container(output)
    generate_content(output, config, all)
    generate_toc(output, config, all)
    generate_title(output, config)
    generate_list(output, config, modules)
    generate_list(output, config, exceptions)
    generate_list(output, config, protocols)

    #generate_epub(output)
    #File.rm_rf!(output)
  end

  #defp generate_overview(modules, exceptions, protocols, output, config, has_readme) do
    #  content = Templates.overview_template(config, modules, exceptions, protocols, has_readme)
    #  :ok = File.write("#{output}/overview.html", content)
    #end

  defp templates_path(other) do
    Path.expand("epub/templates/#{other}", __DIR__)
  end

  defp assets do
   [{ templates_path("css/*.css"), "." }]
  end

  defp generate_assets(output, _config) do
    Enum.each assets, fn({pattern, _dir}) ->
      Enum.map Path.wildcard(pattern), fn(file) ->
        base = Path.basename(file)
        File.copy(file, "#{output}/OEBPS/#{base}")
      end
    end
  end

  defp generate_mimetype(output) do
    content = "application/epub+zip"
    File.write("#{output}/mimetype", content)
  end

  defp generate_container(output) do
    content = Templates.container_template()
    File.mkdir_p("#{output}/META-INF")
    File.write("#{output}/META-INF/container.xml", content)
  end

  defp generate_content(output, config, all) do
    content = Templates.content_template(config, all)
    File.write("#{output}/OEBPS/content.opf", content)
  end

  defp generate_toc(output, config, all) do
    content = Templates.toc_template(config, all)
    File.write("#{output}/OEBPS/toc.ncx", content)
  end

  defp generate_title(output, config) do
    content = Templates.title_template(config)
    File.write("#{output}/OEBPS/title.html", content)
  end

  defp generate_list(output, config, nodes) do
    File.mkdir_p("#{output}/OEBPS/modules")
    nodes
    |> Enum.map(&Task.async(fn -> generate_module_page(output, config, &1) end))
    |> Enum.map(&Task.await/1)
  end

  defp generate_module_page(output, config, node) do
    content = Templates.module_page(config, node)
    File.write("#{output}/OEBPS/modules/#{node.id}.html", content)
  end

  #defp generate_epub(output) do
    #zip -0Xq book.epub mimetype
    #zip -Xr9Dq book.epub *
    #project = "elixir.epub"
    #{:ok, _} = :zip.create(String.to_char_list(project),
    #             files_to_add(output),
    #             uncompress: ['mimetype'])
    #:ok
  #end
end


Set the manager before going into the dep

# This module is responsible for retrieving
# dependencies of a given project. This
# module and its functions are private to Mix.
defmodule Mix.Deps.Retriever do
  @moduledoc false

  @doc """
  Gets all direct children of the current `Mix.Project`
  as a `Mix.Dep` record. Umbrella project dependencies
  are included as children.
  """
  def children do
    scms = Mix.SCM.available
    from = current_source(:mix)
    Enum.map(Mix.project[:deps] || [], &to_dep(&1, scms, from)) ++
             Mix.Deps.Umbrella.children
  end

  @doc """
  Fetches the given dependency information, including its
  latest status and children.
  """
  def fetch(dep, config) do
    Mix.Dep[manager: manager, scm: scm, opts: opts] = dep
    dep = dep.status(scm_status(scm, opts))

    validate_app(cond do
      not ok?(dep.status) ->
        dep

      manager == :rebar ->
        rebar_dep(dep, config)

      mixfile?(dep) ->
        mix_dep(dep.manager(:mix), config)

      rebarconfig?(dep) or rebarexec?(dep) ->
        rebar_dep(dep.manager(:rebar), config)

      makefile?(dep) ->
        dep.manager(:make)

      true ->
        mix_dep(dep.manager(:mix), config)
    end)
  end

  ## Helpers

  defp current_source(manager) do
    case manager do
      :mix   -> "mix.exs"
      :rebar -> "rebar.config"
    end |> Path.absname
  end

  defp to_dep(tuple, scms, from, manager // nil) do
    dep = with_scm_and_app(tuple, scms).from(from).manager(manager)

    if match?({ _, req, _ } when is_regex(req), tuple) and
        not String.ends_with?(from, "rebar.config") do
      invalid_dep_format(tuple)
    end

    dep
  end

  defp mix_dep(Mix.Dep[opts: opts, app: app, status: status] = dep, config) do
    Mix.Deps.in_dependency(dep, config, fn _ ->
      default =
        if Mix.Project.umbrella? do
          false
        else
          Path.join(Mix.project[:compile_path], "#{app}.app")
        end

      opts = Keyword.put_new(opts, :app, default)
      stat = if vsn = old_elixir_lock(), do: { :elixirlock, vsn }, else: status
      dep.manager(:mix).opts(opts).deps(children).status(stat)
    end)
  end

  defp rebar_dep(Mix.Dep[] = dep, config) do
    Mix.Deps.in_dependency(dep, config, fn _ ->
      config = Mix.Rebar.load_config(".")
      extra  = Dict.take(config, [:sub_dirs])
      dep.manager(:rebar).extra(extra).deps(rebar_children(config))
    end)
  end

  defp rebar_children(root_config) do
    scms = Mix.SCM.available
    from = current_source(:rebar)
    Mix.Rebar.recur(root_config, fn config ->
      Mix.Rebar.deps(config) |> Enum.map(&to_dep(&1, scms, from, :rebar))
    end) |> Enum.concat
  end

  defp with_scm_and_app({ app, opts }, scms) when is_atom(app) and is_list(opts) do
    with_scm_and_app({ app, nil, opts }, scms)
  end

  defp with_scm_and_app({ app, req, opts }, scms) when is_atom(app) and
      (is_binary(req) or is_regex(req) or req == nil) and is_list(opts) do

    path = Path.join(Mix.project[:deps_path], app)
    opts = Keyword.put(opts, :dest, path)

    { scm, opts } = Enum.find_value scms, { nil, [] }, fn(scm) ->
      (new = scm.accepts_options(app, opts)) && { scm, new }
    end

    if scm do
      Mix.Dep[
        scm: scm,
        app: app,
        requirement: req,
        status: scm_status(scm, opts),
        opts: opts
      ]
    else
      raise Mix.Error, message: "#{inspect Mix.Project.get} did not specify a supported scm " <>
                                "for app #{inspect app}, expected one of :git, :path or :in_umbrella"
    end
  end

  defp with_scm_and_app(other, _scms) do
    invalid_dep_format(other)
  end

  defp scm_status(scm, opts) do
    if scm.checked_out? opts do
      { :ok, nil }
    else
      { :unavailable, opts[:dest] }
    end
  end

  defp validate_app(Mix.Dep[opts: opts, requirement: req, app: app, status: status] = dep) do
    opts_app = opts[:app]

    if not ok?(status) or opts_app == false do
      dep
    else
      path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
      path = Path.expand(path, opts[:dest])
      dep.status app_status(path, app, req)
    end
  end

  defp app_status(app_path, app, req) do
    case :file.consult(app_path) do
      { :ok, [{ :application, ^app, config }] } ->
        case List.keyfind(config, :vsn, 0) do
          { :vsn, actual } when is_list(actual) ->
            actual = iolist_to_binary(actual)
            if vsn_match?(req, actual) do
              { :ok, actual }
            else
              { :nomatchvsn, actual }
            end
          { :vsn, actual } ->
            { :invalidvsn, actual }
          nil ->
            { :invalidvsn, nil }
        end
      { :ok, _ } -> { :invalidapp, app_path }
      { :error, _ } -> { :noappfile, app_path }
    end
  end

  defp vsn_match?(nil, _actual), do: true
  defp vsn_match?(req, actual) when is_regex(req),  do: actual =~ req
  defp vsn_match?(req, actual) when is_binary(req) do
    Version.match?(actual, req)
  end

  defp mixfile?(dep) do
    File.regular?(Path.join(dep.opts[:dest], "mix.exs"))
  end

  defp rebarexec?(dep) do
    File.regular?(Path.join(dep.opts[:dest], "rebar"))
  end

  defp rebarconfig?(dep) do
    Enum.any?(["rebar.config", "rebar.config.script"], fn file ->
      File.regular?(Path.join(dep.opts[:dest], file))
    end)
  end

  defp makefile?(dep) do
    File.regular? Path.join(dep.opts[:dest], "Makefile")
  end

  defp ok?({ :ok, _ }), do: true
  defp ok?(_), do: false

  defp invalid_dep_format(dep) do
    raise Mix.Error, message: %s(Dependency specified in the wrong format: #{inspect dep}, ) <>
      %s(expected { app :: atom, opts :: Keyword.t } | { app :: atom, requirement :: String.t, opts :: Keyword.t })
  end

  defp old_elixir_lock do
    old_vsn = Mix.Deps.Lock.elixir_vsn
    if old_vsn && old_vsn != System.version do
      old_vsn
    end
  end
end

# This module is responsible for retrieving
# dependencies of a given project. This
# module and its functions are private to Mix.
defmodule Mix.Deps.Retriever do
  @moduledoc false

  @doc """
  Gets all direct children of the current `Mix.Project`
  as a `Mix.Dep` record. Umbrella project dependencies
  are included as children.
  """
  def children do
    scms = Mix.SCM.available
    from = current_source(:mix)
    Enum.map(Mix.project[:deps] || [], &to_dep(&1, scms, from)) ++
             Mix.Deps.Umbrella.children
  end

  @doc """
  Fetches the given dependency information, including its
  latest status and children.
  """
  def fetch(dep, config) do
    Mix.Dep[manager: manager, scm: scm, opts: opts] = dep
    dep = dep.status(scm_status(scm, opts))

    validate_app(cond do
      not ok?(dep.status) ->
        dep

      manager == :rebar ->
        rebar_dep(dep, config)

      mixfile?(dep) ->
        mix_dep(dep, config)

      rebarconfig?(dep) or rebarexec?(dep) ->
        rebar_dep(dep, config)

      makefile?(dep) ->
        make_dep(dep)

      true ->
        mix_dep(dep, config)
    end)
  end

  ## Helpers

  defp current_source(manager) do
    case manager do
      :mix   -> "mix.exs"
      :rebar -> "rebar.config"
    end |> Path.absname
  end

  defp to_dep(tuple, scms, from, manager // nil) do
    dep = with_scm_and_app(tuple, scms).from(from).manager(manager)

    if match?({ _, req, _ } when is_regex(req), tuple) and
        not String.ends_with?(from, "rebar.config") do
      invalid_dep_format(tuple)
    end

    dep
  end

  defp mix_dep(Mix.Dep[opts: opts, app: app, status: status] = dep, config) do
    Mix.Deps.in_dependency(dep, config, fn _ ->
      default =
        if Mix.Project.umbrella? do
          false
        else
          Path.join(Mix.project[:compile_path], "#{app}.app")
        end

      opts = Keyword.put_new(opts, :app, default)
      stat = if vsn = old_elixir_lock(), do: { :elixirlock, vsn }, else: status
      dep.manager(:mix).opts(opts).deps(children).status(stat)
    end)
  end

  defp rebar_dep(Mix.Dep[opts: opts] = dep, config) do
    Mix.Deps.in_dependency(dep, config, fn _ ->
      config = Mix.Rebar.load_config(opts[:dest])
      extra  = Dict.take(config, [:sub_dirs])
      dep.manager(:rebar).extra(extra).deps(rebar_children(config))
    end)
  end

  defp rebar_children(root_config) do
    scms = Mix.SCM.available
    from = current_source(:rebar)
    Mix.Rebar.recur(root_config, fn config ->
      Mix.Rebar.deps(config) |> Enum.map(&to_dep(&1, scms, from, :rebar))
    end) |> Enum.concat
  end

  defp make_dep(Mix.Dep[] = dep) do
    dep.manager(:make)
  end

  defp with_scm_and_app({ app, opts }, scms) when is_atom(app) and is_list(opts) do
    with_scm_and_app({ app, nil, opts }, scms)
  end

  defp with_scm_and_app({ app, req, opts }, scms) when is_atom(app) and
      (is_binary(req) or is_regex(req) or req == nil) and is_list(opts) do

    path = Path.join(Mix.project[:deps_path], app)
    opts = Keyword.put(opts, :dest, path)

    { scm, opts } = Enum.find_value scms, { nil, [] }, fn(scm) ->
      (new = scm.accepts_options(app, opts)) && { scm, new }
    end

    if scm do
      Mix.Dep[
        scm: scm,
        app: app,
        requirement: req,
        status: scm_status(scm, opts),
        opts: opts
      ]
    else
      raise Mix.Error, message: "#{inspect Mix.Project.get} did not specify a supported scm " <>
                                "for app #{inspect app}, expected one of :git, :path or :in_umbrella"
    end
  end

  defp with_scm_and_app(other, _scms) do
    invalid_dep_format(other)
  end

  defp scm_status(scm, opts) do
    if scm.checked_out? opts do
      { :ok, nil }
    else
      { :unavailable, opts[:dest] }
    end
  end

  defp validate_app(Mix.Dep[opts: opts, requirement: req, app: app, status: status] = dep) do
    opts_app = opts[:app]

    if not ok?(status) or opts_app == false do
      dep
    else
      path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
      path = Path.expand(path, opts[:dest])
      dep.status app_status(path, app, req)
    end
  end

  defp app_status(app_path, app, req) do
    case :file.consult(app_path) do
      { :ok, [{ :application, ^app, config }] } ->
        case List.keyfind(config, :vsn, 0) do
          { :vsn, actual } when is_list(actual) ->
            actual = iolist_to_binary(actual)
            if vsn_match?(req, actual) do
              { :ok, actual }
            else
              { :nomatchvsn, actual }
            end
          { :vsn, actual } ->
            { :invalidvsn, actual }
          nil ->
            { :invalidvsn, nil }
        end
      { :ok, _ } -> { :invalidapp, app_path }
      { :error, _ } -> { :noappfile, app_path }
    end
  end

  defp vsn_match?(nil, _actual), do: true
  defp vsn_match?(req, actual) when is_regex(req),  do: actual =~ req
  defp vsn_match?(req, actual) when is_binary(req) do
    Version.match?(actual, req)
  end

  defp mixfile?(dep) do
    File.regular?(Path.join(dep.opts[:dest], "mix.exs"))
  end

  defp rebarexec?(dep) do
    File.regular?(Path.join(dep.opts[:dest], "rebar"))
  end

  defp rebarconfig?(dep) do
    Enum.any?(["rebar.config", "rebar.config.script"], fn file ->
      File.regular?(Path.join(dep.opts[:dest], file))
    end)
  end

  defp makefile?(dep) do
    File.regular? Path.join(dep.opts[:dest], "Makefile")
  end

  defp ok?({ :ok, _ }), do: true
  defp ok?(_), do: false

  defp invalid_dep_format(dep) do
    raise Mix.Error, message: %s(Dependency specified in the wrong format: #{inspect dep}, ) <>
      %s(expected { app :: atom, opts :: Keyword.t } | { app :: atom, requirement :: String.t, opts :: Keyword.t })
  end

  defp old_elixir_lock do
    old_vsn = Mix.Deps.Lock.elixir_vsn
    if old_vsn && old_vsn != System.version do
      old_vsn
    end
  end
end


Fallback to first facility on invalid/missing subdomain

defmodule OpenPantry.Plugs.Facility do
  import Ecto.Query, only: [from: 2]

  alias OpenPantry.{Repo, Facility}

  def init(_opts) do
    Application.get_env(:open_pantry, OpenPantry.Web.Endpoint)[:url][:host]
    |> String.length()
  end

  def call(conn, domain_length) do
    subdomain = subdomain(conn.host, domain_length)

    facility =
      case Repo.get_by(Facility, subdomain: subdomain) do
        nil -> Repo.one!(from Facility, limit: 1)
        facility -> facility
      end

    Plug.Conn.assign(conn, :facility, facility)
  end

  defp subdomain(host, domain_length) do
    to = -domain_length - 2
    String.slice(host, 0..to)
  end
end

defmodule OpenPantry.Plugs.Facility do
  alias OpenPantry.{Repo, Facility}

  def init(_opts) do
    Application.get_env(:open_pantry, OpenPantry.Web.Endpoint)[:url][:host]
    |> String.length()
  end

  def call(conn, domain_length) do
    subdomain = subdomain(conn.host, domain_length)
    facility = Repo.get_by!(Facility, subdomain: subdomain)

    Plug.Conn.assign(conn, :facility, facility)
  end

  defp subdomain(host, domain_length) do
    to = -domain_length - 2
    String.slice(host, 0..to)
  end
end