Improve docs for functions in List

Improve docs for functions in List

defmodule List do
  @moduledoc """
  Functions that work on (linked) lists.

  Lists in Elixir are specified between square brackets:

      iex> [1, "two", 3, :four]
      [1, "two", 3, :four]

  Two lists can be concatenated and subtracted using the
  `Kernel.++/2` and `Kernel.--/2` operators:

      iex> [1, 2, 3] ++ [4, 5, 6]
      [1, 2, 3, 4, 5, 6]
      iex> [1, true, 2, false, 3, true] -- [true, false]
      [1, 2, 3, true]

  Lists in Elixir are effectively linked lists, which means
  they are internally represented in pairs containing the
  head and the tail of a list:

      iex> [h | t] = [1, 2, 3]
      iex> h
      1
      iex> t
      [2, 3]

  Similarly, we could write the list `[1, 2, 3]` using only
  such pairs (called cons cells):

      iex> [1 | [2 | [3 | []]]]
      [1, 2, 3]

  Some lists, called improper lists, do not have an empty list as
  the second element in the last cons cell:

      iex> [1 | [2 | [3 | 4]]]
      [1, 2, 3 | 4]

  Although improper lists are generally avoided, they are used in some
  special circumstances like iodata and chardata entities (see the `IO` module).

  Due to their cons cell based representation, prepending an element
  to a list is always fast (constant time), while appending becomes
  slower as the list grows in size (linear time):

      iex> list = [1, 2, 3]
      iex> [0 | list]   # fast
      [0, 1, 2, 3]
      iex> list ++ [4]  # slow
      [1, 2, 3, 4]

  The `Kernel` module contains many functions to manipulate lists
  and that are allowed in guards. For example, `Kernel.hd/1` to
  retrieve the head, `Kernel.tl/1` to fetch the tail and
  `Kernel.length/1` for calculating the length. Keep in mind that,
  similar to appending to a list, calculating the length needs to
  traverse the whole list.

  ## Charlists

  If a list is made of non-negative integers, it can also be called
  a charlist. Elixir uses single quotes to define charlists:

      iex> 'héllo'
      [104, 233, 108, 108, 111]

  In particular, charlists may be printed back in single
  quotes if they contain only ASCII-printable codepoints:

      iex> 'abc'
      'abc'

  The rationale behind this behaviour is to better support
  Erlang libraries which may return text as charlists
  instead of Elixir strings. One example of such functions
  is `Application.loaded_applications`:

      Application.loaded_applications
      #=>  [{:stdlib, 'ERTS  CXC 138 10', '2.6'},
            {:compiler, 'ERTS  CXC 138 10', '6.0.1'},
            {:elixir, 'elixir', '1.0.0'},
            {:kernel, 'ERTS  CXC 138 10', '4.1'},
            {:logger, 'logger', '1.0.0'}]

  ## List and Enum modules

  This module aims to provide operations that are specific
  to lists, like convertion between data types, updates,
  deletions and key lookups (for lists of tuples). For traversing
  lists in general, developers should use the functions in the
  `Enum` module that work across a variety of data types.

  In both `Enum` and `List` modules, any kind of index access
  on a list is linear. Negative indexes are also supported but
  they imply the list will be iterated twice, one to calculate
  the proper index and another to perform the operation.
  """

  @compile :inline_list_funcs

  @doc """
  Deletes the given `item` from the `list`. Returns a new list without
  the item.

  If the `item` occurs more than once in the `list`, just
  the first occurrence is removed.

  ## Examples

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

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

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

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

  ## Examples

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

      iex> List.duplicate([1, 2], 2)
      [[1, 2], [1, 2]]

  """
  @spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

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

  ## Examples

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

  """
  @spec flatten(deep_list) :: list when deep_list: [any | deep_list]
  def flatten(list) do
    :lists.flatten(list)
  end

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

  ## Examples

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

  """
  @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

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

  ## Examples

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

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

  """
  @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

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

  ## Examples

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

  """
  @spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

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

  ## Examples

      iex> List.first([])
      nil

      iex> List.first([1])
      1

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

  """
  @spec first([elem]) :: nil | elem when elem: var
  def first([]),      do: nil
  def first([h | _]), do: h

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

  ## Examples

      iex> List.last([])
      nil

      iex> List.last([1])
      1

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

  """
  @spec last([elem]) :: nil | elem when elem: var
  def last([]),      do: nil
  def last([h]),     do: h
  def last([_ | t]), do: last(t)

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

  ## Examples

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

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

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

  """
  @spec keyfind([tuple], any, non_neg_integer, any) :: any
  def keyfind(list, key, position, default \\ nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

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

  ## Examples

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

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

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

  """
  @spec keymember?([tuple], any, non_neg_integer) :: boolean
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

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

  ## Examples

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

  """
  @spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

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

  ## Examples

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

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

  """
  @spec keysort([tuple], non_neg_integer) :: [tuple]
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

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

  If the item does not exist, it is added to the end of the `list`.

  ## Examples

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

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

  """
  @spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

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

  ## Examples

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

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

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

  """
  @spec keydelete([tuple], any, non_neg_integer) :: [tuple]
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Receives a `list` of tuples and returns the first tuple
  where the element at `position` in the tuple matches the
  given `key`, as well as the `list` without found tuple.

  If such a tuple is not found, `nil` will be returned.

  ## Examples

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

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

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

  """
  @spec keytake([tuple], any, non_neg_integer) :: {tuple, [tuple]} | nil
  def keytake(list, key, position) do
    case :lists.keytake(key, position + 1, list) do
      {:value, item, list} -> {item, list}
      false                -> nil
    end
  end

  @doc """
  Wraps the argument in a list.

  If the argument is already a list, returns the list.
  If the argument is `nil`, returns an empty list.

  ## Examples

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

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

      iex> List.wrap(nil)
      []

  """
  @spec wrap(list | any) :: list
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

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

  The zipping finishes as soon as any list terminates.

  ## Examples

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

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

  """
  @spec zip([list]) :: [tuple]
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

  @doc """
  Returns a list with `value` inserted at the specified `index`.

  Note that `index` is capped at the list length. Negative indices
  indicate an offset from the end of the `list`.

  ## Examples

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

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

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

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

  """
  @spec insert_at(list, integer, any) :: list
  def insert_at(list, index, value) when is_integer(index) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

  @doc """
  Returns a list with a replaced value at the specified `index`.

  Negative indices indicate an offset from the end of the `list`.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

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

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

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

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

  """
  @spec replace_at(list, integer, any) :: list
  def replace_at(list, index, value) when is_integer(index) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

  @doc """
  Returns a list with an updated value at the specified `index`.

  Negative indices indicate an offset from the end of the `list`.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
      [11, 2, 3]

      iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
      [1, 2, 3]

      iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
      [1, 2, 13]

      iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
      [1, 2, 3]

  """
  @spec update_at([elem], integer, (elem -> any)) :: list when elem: var
  def update_at(list, index, fun) when is_function(fun, 1) and is_integer(index) do
    if index < 0 do
      do_update_at(list, length(list) + index, fun)
    else
      do_update_at(list, index, fun)
    end
  end

  @doc """
  Produces a new list by removing the value at the specified `index`.

  Negative indices indicate an offset from the end of the `list`.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

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

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

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

  """
  @spec delete_at(list, integer) :: list
  def delete_at(list, index) when is_integer(index) do
    elem(pop_at(list, index), 1)
  end

  @doc """
  Returns and removes the value at the specified `index` in the `list`.

  Negative indices indicate an offset from the end of the `list`.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.pop_at([1, 2, 3], 0)
      {1, [2, 3]}
      iex> List.pop_at([1, 2, 3], 5)
      {nil, [1, 2, 3]}
      iex> List.pop_at([1, 2, 3], 5, 10)
      {10, [1, 2, 3]}
      iex> List.pop_at([1, 2, 3], -1)
      {3, [1, 2]}

  """
  @spec pop_at(list, integer, any) :: {any, list}
  def pop_at(list, index, default \\ nil) when is_integer(index) do
    if index < 0 do
      do_pop_at(list, length(list) + index, default, [])
    else
      do_pop_at(list, index, default, [])
    end
  end

  @doc """
  Converts a charlist to an atom.

  Currently Elixir does not support conversions from charlists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> List.to_atom('elixir')
      :elixir

  """
  @spec to_atom(charlist) :: atom
  def to_atom(charlist) do
    :erlang.list_to_atom(charlist)
  end

  @doc """
  Converts a charlist to an existing atom. Raises an `ArgumentError`
  if the atom does not exist.

  Currently Elixir does not support conversions from charlists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> _ = :my_atom
      iex> List.to_existing_atom('my_atom')
      :my_atom

      iex> List.to_existing_atom('this_atom_will_never_exist')
      ** (ArgumentError) argument error

  """
  @spec to_existing_atom(charlist) :: atom
  def to_existing_atom(charlist) do
    :erlang.list_to_existing_atom(charlist)
  end

  @doc """
  Returns the float whose text representation is `charlist`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_float('2.2017764e+0')
      2.2017764

  """
  @spec to_float(charlist) :: float
  def to_float(charlist) do
    :erlang.list_to_float(charlist)
  end

  @doc """
  Returns an integer whose text representation is `charlist`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('123')
      123

  """
  @spec to_integer(charlist) :: integer
  def to_integer(charlist) do
    :erlang.list_to_integer(charlist)
  end

  @doc """
  Returns an integer whose text representation is `charlist` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('3FF', 16)
      1023

  """
  @spec to_integer(charlist, 2..36) :: integer
  def to_integer(charlist, base) do
    :erlang.list_to_integer(charlist, base)
  end

  @doc """
  Converts a list to a tuple.

  Inlined by the compiler.

  ## Examples

      iex> List.to_tuple([:share, [:elixir, 163]])
      {:share, [:elixir, 163]}

  """
  @spec to_tuple(list) :: tuple
  def to_tuple(list) do
    :erlang.list_to_tuple(list)
  end

  @doc """
  Converts a list of integers representing codepoints, lists or
  strings into a string.

  Notice that this function expects a list of integers representing
  UTF-8 codepoints. If you have a list of bytes, you must instead use
  the [`:binary` module](http://www.erlang.org/doc/man/binary.html).

  ## Examples

      iex> List.to_string([0x00E6, 0x00DF])
      "æß"

      iex> List.to_string([0x0061, "bc"])
      "abc"

  """
  @spec to_string(:unicode.charlist) :: String.t
  def to_string(list) when is_list(list) do
    try do
      :unicode.characters_to_binary(list)
    rescue
      ArgumentError ->
        raise ArgumentError, """
        cannot convert the given list to a string.

        To be converted to a string, a list must contain only:

          * strings
          * integers representing Unicode codepoints
          * or a list containing one of these three elements

        Please check the given list or call inspect/1 to get the list representation, got:

        #{inspect list}
        """
    else
      result when is_binary(result) ->
        result

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  ## Helpers

  # replace_at

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

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

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

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

  # insert_at

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

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

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

  # update_at

  defp do_update_at([value | list], 0, fun) do
    [fun.(value) | list]
  end

  defp do_update_at(list, index, _fun) when index < 0 do
    list
  end

  defp do_update_at([h | t], index, fun) do
    [h | do_update_at(t, index - 1, fun)]
  end

  defp do_update_at([], _index, _fun) do
    []
  end

  # pop_at

  defp do_pop_at([], _index, default, acc) do
    {default, :lists.reverse(acc)}
  end

  defp do_pop_at(list, index, default, []) when index < 0 do
    {default, list}
  end

  defp do_pop_at([head | tail], 0, _default, acc) do
    {head, :lists.reverse(acc, tail)}
  end

  defp do_pop_at([head | tail], index, default, acc) do
    do_pop_at(tail, index - 1, default, [head | acc])
  end

  # zip

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

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

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

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

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

defmodule List do
  @moduledoc """
  Functions that work on (linked) lists.

  Lists in Elixir are specified between square brackets:

      iex> [1, "two", 3, :four]
      [1, "two", 3, :four]

  Two lists can be concatenated and subtracted using the
  `Kernel.++/2` and `Kernel.--/2` operators:

      iex> [1, 2, 3] ++ [4, 5, 6]
      [1, 2, 3, 4, 5, 6]
      iex> [1, true, 2, false, 3, true] -- [true, false]
      [1, 2, 3, true]

  Lists in Elixir are effectively linked lists, which means
  they are internally represented in pairs containing the
  head and the tail of a list:

      iex> [h | t] = [1, 2, 3]
      iex> h
      1
      iex> t
      [2, 3]

  Similarly, we could write the list `[1, 2, 3]` using only
  such pairs (called cons cells):

      iex> [1 | [2 | [3 | []]]]
      [1, 2, 3]

  Some lists, called improper lists, do not have an empty list as
  the second element in the last cons cell:

      iex> [1 | [2 | [3 | 4]]]
      [1, 2, 3 | 4]

  Although improper lists are generally avoided, they are used in some
  special circumstances like iodata and chardata entities (see the `IO` module).

  Due to their cons cell based representation, prepending an element
  to a list is always fast (constant time), while appending becomes
  slower as the list grows in size (linear time):

      iex> list = [1, 2, 3]
      iex> [0 | list]   # fast
      [0, 1, 2, 3]
      iex> list ++ [4]  # slow
      [1, 2, 3, 4]

  The `Kernel` module contains many functions to manipulate lists
  and that are allowed in guards. For example, `Kernel.hd/1` to
  retrieve the head, `Kernel.tl/1` to fetch the tail and
  `Kernel.length/1` for calculating the length. Keep in mind that,
  similar to appending to a list, calculating the length needs to
  traverse the whole list.

  ## Charlists

  If a list is made of non-negative integers, it can also be called
  a charlist. Elixir uses single quotes to define charlists:

      iex> 'héllo'
      [104, 233, 108, 108, 111]

  In particular, charlists may be printed back in single
  quotes if they contain only ASCII-printable codepoints:

      iex> 'abc'
      'abc'

  The rationale behind this behaviour is to better support
  Erlang libraries which may return text as charlists
  instead of Elixir strings. One example of such functions
  is `Application.loaded_applications`:

      Application.loaded_applications
      #=>  [{:stdlib, 'ERTS  CXC 138 10', '2.6'},
            {:compiler, 'ERTS  CXC 138 10', '6.0.1'},
            {:elixir, 'elixir', '1.0.0'},
            {:kernel, 'ERTS  CXC 138 10', '4.1'},
            {:logger, 'logger', '1.0.0'}]

  ## List and Enum modules

  This module aims to provide operations that are specific
  to lists, like convertion between data types, updates,
  deletions and key lookups (for lists of tuples). For traversing
  lists in general, developers should use the functions in the
  `Enum` module that work across a variety of data types.

  In both `Enum` and `List` modules, any kind of index access
  on a list is linear. Negative indexes are also supported but
  they imply the list will be iterated twice, one to calculate
  the proper index and another to perform the operation.
  """

  @compile :inline_list_funcs

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

  ## Examples

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

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

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

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

  ## Examples

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

      iex> List.duplicate([1, 2], 2)
      [[1, 2], [1, 2]]

  """
  @spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

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

  ## Examples

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

  """
  @spec flatten(deep_list) :: list when deep_list: [any | deep_list]
  def flatten(list) do
    :lists.flatten(list)
  end

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

  ## Examples

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

  """
  @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

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

  ## Examples

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

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

  """
  @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

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

  ## Examples

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

  """
  @spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

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

  ## Examples

      iex> List.first([])
      nil

      iex> List.first([1])
      1

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

  """
  @spec first([elem]) :: nil | elem when elem: var
  def first([]),      do: nil
  def first([h | _]), do: h

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

  ## Examples

      iex> List.last([])
      nil

      iex> List.last([1])
      1

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

  """
  @spec last([elem]) :: nil | elem when elem: var
  def last([]),      do: nil
  def last([h]),     do: h
  def last([_ | t]), do: last(t)

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

  ## Examples

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

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

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

  """
  @spec keyfind([tuple], any, non_neg_integer, any) :: any
  def keyfind(list, key, position, default \\ nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

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

  ## Examples

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

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

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

  """
  @spec keymember?([tuple], any, non_neg_integer) :: boolean
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

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

  ## Examples

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

  """
  @spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

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

  ## Examples

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

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

  """
  @spec keysort([tuple], non_neg_integer) :: [tuple]
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

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

  ## Examples

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

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

  """
  @spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

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

  ## Examples

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

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

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

  """
  @spec keydelete([tuple], any, non_neg_integer) :: [tuple]
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Receives a `list` of tuples and returns the first tuple
  where the element at `position` in the tuple matches the
  given `key`, as well as the `list` without found tuple.

  If such a tuple is not found, `nil` will be returned.

  ## Examples

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

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

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

  """
  @spec keytake([tuple], any, non_neg_integer) :: {tuple, [tuple]} | nil
  def keytake(list, key, position) do
    case :lists.keytake(key, position + 1, list) do
      {:value, item, list} -> {item, list}
      false                -> nil
    end
  end

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

  ## Examples

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

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

      iex> List.wrap(nil)
      []

  """
  @spec wrap(list | any) :: list
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

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

  The zipping finishes as soon as any list terminates.

  ## Examples

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

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

  """
  @spec zip([list]) :: [tuple]
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

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

  ## Examples

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

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

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

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

  """
  @spec insert_at(list, integer, any) :: list
  def insert_at(list, index, value) when is_integer(index) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

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

  ## Examples

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

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

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

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

  """
  @spec replace_at(list, integer, any) :: list
  def replace_at(list, index, value) when is_integer(index) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

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

  ## Examples

      iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
      [11, 2, 3]

      iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
      [1, 2, 3]

      iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
      [1, 2, 13]

      iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
      [1, 2, 3]

  """
  @spec update_at([elem], integer, (elem -> any)) :: list when elem: var
  def update_at(list, index, fun) when is_function(fun, 1) and is_integer(index) do
    if index < 0 do
      do_update_at(list, length(list) + index, fun)
    else
      do_update_at(list, index, fun)
    end
  end

  @doc """
  Produces a new list by removing the value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

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

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

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

  """
  @spec delete_at(list, integer) :: list
  def delete_at(list, index) when is_integer(index) do
    elem(pop_at(list, index), 1)
  end

  @doc """
  Returns and removes the value at the specified `index` in the `list`.

  Negative indices indicate an offset from the end of the `list`.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.pop_at([1, 2, 3], 0)
      {1, [2, 3]}
      iex> List.pop_at([1, 2, 3], 5)
      {nil, [1, 2, 3]}
      iex> List.pop_at([1, 2, 3], 5, 10)
      {10, [1, 2, 3]}
      iex> List.pop_at([1, 2, 3], -1)
      {3, [1, 2]}

  """
  @spec pop_at(list, integer, any) :: {any, list}
  def pop_at(list, index, default \\ nil) when is_integer(index) do
    if index < 0 do
      do_pop_at(list, length(list) + index, default, [])
    else
      do_pop_at(list, index, default, [])
    end
  end

  @doc """
  Converts a charlist to an atom.

  Currently Elixir does not support conversions from charlists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> List.to_atom('elixir')
      :elixir

  """
  @spec to_atom(charlist) :: atom
  def to_atom(charlist) do
    :erlang.list_to_atom(charlist)
  end

  @doc """
  Converts a charlist to an existing atom. Raises an `ArgumentError`
  if the atom does not exist.

  Currently Elixir does not support conversions from charlists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> _ = :my_atom
      iex> List.to_existing_atom('my_atom')
      :my_atom

      iex> List.to_existing_atom('this_atom_will_never_exist')
      ** (ArgumentError) argument error

  """
  @spec to_existing_atom(charlist) :: atom
  def to_existing_atom(charlist) do
    :erlang.list_to_existing_atom(charlist)
  end

  @doc """
  Returns the float whose text representation is `charlist`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_float('2.2017764e+0')
      2.2017764

  """
  @spec to_float(charlist) :: float
  def to_float(charlist) do
    :erlang.list_to_float(charlist)
  end

  @doc """
  Returns an integer whose text representation is `charlist`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('123')
      123

  """
  @spec to_integer(charlist) :: integer
  def to_integer(charlist) do
    :erlang.list_to_integer(charlist)
  end

  @doc """
  Returns an integer whose text representation is `charlist` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('3FF', 16)
      1023

  """
  @spec to_integer(charlist, 2..36) :: integer
  def to_integer(charlist, base) do
    :erlang.list_to_integer(charlist, base)
  end

  @doc """
  Converts a list to a tuple.

  Inlined by the compiler.

  ## Examples

      iex> List.to_tuple([:share, [:elixir, 163]])
      {:share, [:elixir, 163]}

  """
  @spec to_tuple(list) :: tuple
  def to_tuple(list) do
    :erlang.list_to_tuple(list)
  end

  @doc """
  Converts a list of integers representing codepoints, lists or
  strings into a string.

  Notice that this function expects a list of integers representing
  UTF-8 codepoints. If you have a list of bytes, you must instead use
  the [`:binary` module](http://www.erlang.org/doc/man/binary.html).

  ## Examples

      iex> List.to_string([0x00E6, 0x00DF])
      "æß"

      iex> List.to_string([0x0061, "bc"])
      "abc"

  """
  @spec to_string(:unicode.charlist) :: String.t
  def to_string(list) when is_list(list) do
    try do
      :unicode.characters_to_binary(list)
    rescue
      ArgumentError ->
        raise ArgumentError, """
        cannot convert the given list to a string.

        To be converted to a string, a list must contain only:

          * strings
          * integers representing Unicode codepoints
          * or a list containing one of these three elements

        Please check the given list or call inspect/1 to get the list representation, got:

        #{inspect list}
        """
    else
      result when is_binary(result) ->
        result

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  ## Helpers

  # replace_at

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

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

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

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

  # insert_at

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

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

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

  # update_at

  defp do_update_at([value | list], 0, fun) do
    [fun.(value) | list]
  end

  defp do_update_at(list, index, _fun) when index < 0 do
    list
  end

  defp do_update_at([h | t], index, fun) do
    [h | do_update_at(t, index - 1, fun)]
  end

  defp do_update_at([], _index, _fun) do
    []
  end

  # pop_at

  defp do_pop_at([], _index, default, acc) do
    {default, :lists.reverse(acc)}
  end

  defp do_pop_at(list, index, default, []) when index < 0 do
    {default, list}
  end

  defp do_pop_at([head | tail], 0, _default, acc) do
    {head, :lists.reverse(acc, tail)}
  end

  defp do_pop_at([head | tail], index, default, acc) do
    do_pop_at(tail, index - 1, default, [head | acc])
  end

  # zip

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

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

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

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

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


Improve the documentation for Path.wildcard/2

[ci skip]

defmodule Path do
  @moduledoc """
  This module provides conveniences for manipulating or
  retrieving file system paths.

  The functions in this module may receive a chardata as
  argument (i.e. a string or a list of characters / string)
  and will always return a string (encoded in UTF-8).

  The majority of the functions in this module do not
  interact with the file system, except for a few functions
  that require it (like `wildcard/2` and `expand/1`).
  """

  @type t :: :unicode.chardata()

  @doc """
  Converts the given path to an absolute one. Unlike
  `expand/1`, no attempt is made to resolve `..`, `.` or `~`.

  ## Examples

  ### Unix

      Path.absname("foo")
      #=> "/usr/local/foo"

      Path.absname("../x")
      #=> "/usr/local/../x"

  ### Windows

      Path.absname("foo").
      #=> "D:/usr/local/foo"
      Path.absname("../x").
      #=> "D:/usr/local/../x"

  """
  @spec absname(t) :: binary
  def absname(path) do
    absname(path, System.cwd!())
  end

  @doc """
  Builds a path from `relative_to` to `path`.

  If `path` is already an absolute path, `relative_to` is ignored. See also
  `relative_to/2`.

  Unlike `expand/2`, no attempt is made to
  resolve `..`, `.` or `~`.

  ## Examples

      iex> Path.absname("foo", "bar")
      "bar/foo"

      iex> Path.absname("../x", "bar")
      "bar/../x"

  """
  @spec absname(t, t) :: binary
  def absname(path, relative_to) do
    path = IO.chardata_to_string(path)

    case type(path) do
      :relative ->
        absname_join(relative_to, path)

      :absolute ->
        absname_join([path])

      :volumerelative ->
        relative_to = IO.chardata_to_string(relative_to)
        absname_vr(split(path), split(relative_to), relative_to)
    end
  end

  # Absolute path on current drive
  defp absname_vr(["/" | rest], [volume | _], _relative), do: absname_join([volume | rest])

  # Relative to current directory on current drive.
  defp absname_vr([<<x, ?:>> | rest], [<<x, _::binary>> | _], relative),
    do: absname(absname_join(rest), relative)

  # Relative to current directory on another drive.
  defp absname_vr([<<x, ?:>> | name], _, _relative) do
    cwd =
      case :file.get_cwd([x, ?:]) do
        {:ok, dir} -> IO.chardata_to_string(dir)
        {:error, _} -> <<x, ?:, ?/>>
      end

    absname(absname_join(name), cwd)
  end

  # Joins a list
  defp absname_join([name1, name2 | rest]), do: absname_join([absname_join(name1, name2) | rest])

  defp absname_join([name]),
    do: do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())

  # Joins two paths
  defp absname_join(left, right),
    do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())

  defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32)
       when uc_letter in ?A..?Z do
    do_absname_join(rest, relativename, [?:, uc_letter + ?a - ?A], :win32)
  end

  defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32),
    do: do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)

  defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/ | result], os_type),
    do: do_absname_join(rest, relativename, [?/ | result], os_type)

  defp do_absname_join(<<?/, rest::binary>>, relativename, [?/ | result], os_type),
    do: do_absname_join(rest, relativename, [?/ | result], os_type)

  defp do_absname_join(<<>>, <<>>, result, os_type),
    do: IO.iodata_to_binary(reverse_maybe_remove_dir_sep(result, os_type))

  defp do_absname_join(<<>>, relativename, [?: | rest], :win32),
    do: do_absname_join(relativename, <<>>, [?: | rest], :win32)

  defp do_absname_join(<<>>, relativename, [?/ | result], os_type),
    do: do_absname_join(relativename, <<>>, [?/ | result], os_type)

  defp do_absname_join(<<>>, relativename, result, os_type),
    do: do_absname_join(relativename, <<>>, [?/ | result], os_type)

  defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type),
    do: do_absname_join(rest, relativename, [char | result], os_type)

  defp reverse_maybe_remove_dir_sep([?/, ?:, letter], :win32), do: [letter, ?:, ?/]
  defp reverse_maybe_remove_dir_sep([?/], _), do: [?/]
  defp reverse_maybe_remove_dir_sep([?/ | name], _), do: :lists.reverse(name)
  defp reverse_maybe_remove_dir_sep(name, _), do: :lists.reverse(name)

  @doc """
  Converts the path to an absolute one and expands
  any `.` and `..` characters and a leading `~`.

  ## Examples

      Path.expand("/foo/bar/../bar")
      #=> "/foo/bar"

  """
  @spec expand(t) :: binary
  def expand(path) do
    expand_dot(absname(expand_home(path), System.cwd!()))
  end

  @doc """
  Expands the path relative to the path given as the second argument
  expanding any `.` and `..` characters.

  If the path is already an absolute path, `relative_to` is ignored.

  Note that this function treats a `path` with a leading `~` as
  an absolute one.

  The second argument is first expanded to an absolute path.

  ## Examples

      # Assuming that the absolute path to baz is /quux/baz
      Path.expand("foo/bar/../bar", "baz")
      #=> "/quux/baz/foo/bar"

      Path.expand("foo/bar/../bar", "/baz")
      "/baz/foo/bar"
      Path.expand("/foo/bar/../bar", "/baz")
      "/foo/bar"

  """
  @spec expand(t, t) :: binary
  def expand(path, relative_to) do
    expand_dot(absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!()))
  end

  @doc """
  Returns the path type.

  ## Examples

  ### Unix

      Path.type("/")                #=> :absolute
      Path.type("/usr/local/bin")   #=> :absolute
      Path.type("usr/local/bin")    #=> :relative
      Path.type("../usr/local/bin") #=> :relative
      Path.type("~/file")           #=> :relative

  ### Windows

      Path.type("D:/usr/local/bin") #=> :absolute
      Path.type("usr/local/bin")    #=> :relative
      Path.type("D:bar.ex")         #=> :volumerelative
      Path.type("/bar/foo.ex")      #=> :volumerelative

  """
  @spec type(t) :: :absolute | :relative | :volumerelative
  def type(name)
      when is_list(name)
      when is_binary(name) do
    pathtype(name, major_os_type()) |> elem(0)
  end

  @doc """
  Forces the path to be a relative path.

  ## Examples

  ### Unix

      Path.relative("/usr/local/bin")   #=> "usr/local/bin"
      Path.relative("usr/local/bin")    #=> "usr/local/bin"
      Path.relative("../usr/local/bin") #=> "../usr/local/bin"

  ### Windows

      Path.relative("D:/usr/local/bin") #=> "usr/local/bin"
      Path.relative("usr/local/bin")    #=> "usr/local/bin"
      Path.relative("D:bar.ex")         #=> "bar.ex"
      Path.relative("/bar/foo.ex")      #=> "bar/foo.ex"

  """
  @spec relative(t) :: binary
  def relative(name) do
    relative(name, major_os_type())
  end

  defp relative(name, os_type) do
    pathtype(name, os_type)
    |> elem(1)
    |> IO.chardata_to_string()
  end

  defp pathtype(name, os_type) do
    case os_type do
      :win32 -> win32_pathtype(name)
      _ -> unix_pathtype(name)
    end
  end

  defp unix_pathtype(path) when path in ["/", '/'], do: {:absolute, "."}
  defp unix_pathtype(<<?/, relative::binary>>), do: {:absolute, relative}
  defp unix_pathtype([?/ | relative]), do: {:absolute, relative}
  defp unix_pathtype([list | rest]) when is_list(list), do: unix_pathtype(list ++ rest)
  defp unix_pathtype(relative), do: {:relative, relative}

  @slash [?/, ?\\]

  defp win32_pathtype([list | rest]) when is_list(list), do: win32_pathtype(list ++ rest)

  defp win32_pathtype([char, list | rest]) when is_list(list),
    do: win32_pathtype([char | list ++ rest])

  defp win32_pathtype(<<c1, c2, relative::binary>>) when c1 in @slash and c2 in @slash,
    do: {:absolute, relative}

  defp win32_pathtype(<<char, relative::binary>>) when char in @slash,
    do: {:volumerelative, relative}

  defp win32_pathtype(<<_letter, ?:, char, relative::binary>>) when char in @slash,
    do: {:absolute, relative}

  defp win32_pathtype(<<_letter, ?:, relative::binary>>), do: {:volumerelative, relative}

  defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash,
    do: {:absolute, relative}

  defp win32_pathtype([char | relative]) when char in @slash, do: {:volumerelative, relative}

  defp win32_pathtype([c1, c2, list | rest]) when is_list(list),
    do: win32_pathtype([c1, c2 | list ++ rest])

  defp win32_pathtype([_letter, ?:, char | relative]) when char in @slash,
    do: {:absolute, relative}

  defp win32_pathtype([_letter, ?: | relative]), do: {:volumerelative, relative}
  defp win32_pathtype(relative), do: {:relative, relative}

  @doc """
  Returns the given `path` relative to the given `from` path.

  In other words, this function tries to strip the `from` prefix from `path`.

  This function does not query the file system, so it assumes
  no symlinks between the paths.

  In case a direct relative path cannot be found, it returns
  the original path.

  ## Examples

      iex> Path.relative_to("/usr/local/foo", "/usr/local")
      "foo"

      iex> Path.relative_to("/usr/local/foo", "/")
      "usr/local/foo"

      iex> Path.relative_to("/usr/local/foo", "/etc")
      "/usr/local/foo"

  """
  @spec relative_to(t, t) :: binary
  def relative_to(path, from) do
    path = IO.chardata_to_string(path)
    relative_to(split(path), split(from), path)
  end

  defp relative_to([h | t1], [h | t2], original) do
    relative_to(t1, t2, original)
  end

  defp relative_to([_ | _] = l1, [], _original) do
    join(l1)
  end

  defp relative_to(_, _, original) do
    original
  end

  @doc """
  Convenience to get the path relative to the current working
  directory.

  If, for some reason, the current working directory
  cannot be retrieved, this function returns the given `path`.
  """
  @spec relative_to_cwd(t) :: binary
  def relative_to_cwd(path) do
    case :file.get_cwd() do
      {:ok, base} -> relative_to(path, IO.chardata_to_string(base))
      _ -> path
    end
  end

  @doc """
  Returns the last component of the path or the path
  itself if it does not contain any directory separators.

  ## Examples

      iex> Path.basename("foo")
      "foo"

      iex> Path.basename("foo/bar")
      "bar"

      iex> Path.basename("/")
      ""

  """
  @spec basename(t) :: binary
  def basename(path) do
    :filename.basename(IO.chardata_to_string(path))
  end

  @doc """
  Returns the last component of `path` with the `extension`
  stripped.

  This function should be used to remove a specific
  extension which may or may not be there.

  ## Examples

      iex> Path.basename("~/foo/bar.ex", ".ex")
      "bar"

      iex> Path.basename("~/foo/bar.exs", ".ex")
      "bar.exs"

      iex> Path.basename("~/foo/bar.old.ex", ".ex")
      "bar.old"

  """
  @spec basename(t, t) :: binary
  def basename(path, extension) do
    :filename.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
  end

  @doc """
  Returns the directory component of `path`.

  ## Examples

      iex> Path.dirname("/foo/bar.ex")
      "/foo"

      iex> Path.dirname("/foo/bar/baz.ex")
      "/foo/bar"

      iex> Path.dirname("/foo/bar/")
      "/foo/bar"

  """
  @spec dirname(t) :: binary
  def dirname(path) do
    :filename.dirname(IO.chardata_to_string(path))
  end

  @doc """
  Returns the extension of the last component of `path`.

  ## Examples

      iex> Path.extname("foo.erl")
      ".erl"

      iex> Path.extname("~/foo/bar")
      ""

  """
  @spec extname(t) :: binary
  def extname(path) do
    :filename.extension(IO.chardata_to_string(path))
  end

  @doc """
  Returns the `path` with the `extension` stripped.

  ## Examples

      iex> Path.rootname("/foo/bar")
      "/foo/bar"

      iex> Path.rootname("/foo/bar.ex")
      "/foo/bar"

  """
  @spec rootname(t) :: binary
  def rootname(path) do
    :filename.rootname(IO.chardata_to_string(path))
  end

  @doc """
  Returns the `path` with the `extension` stripped.

  This function should be used to remove a specific extension which may
  or may not be there.

  ## Examples

      iex> Path.rootname("/foo/bar.erl", ".erl")
      "/foo/bar"

      iex> Path.rootname("/foo/bar.erl", ".ex")
      "/foo/bar.erl"

  """
  @spec rootname(t, t) :: binary
  def rootname(path, extension) do
    :filename.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
  end

  @doc """
  Joins a list of paths.

  This function should be used to convert a list of paths to a path.
  Note that any trailing slash is removed when joining.

  ## Examples

      iex> Path.join(["~", "foo"])
      "~/foo"

      iex> Path.join(["foo"])
      "foo"

      iex> Path.join(["/", "foo", "bar/"])
      "/foo/bar"

  """
  @spec join(nonempty_list(t)) :: binary
  def join([name1, name2 | rest]), do: join([join(name1, name2) | rest])
  def join([name]), do: IO.chardata_to_string(name)

  @doc """
  Joins two paths.

  The right path will always be expanded to its relative format
  and any trailing slash will be removed when joining.

  ## Examples

      iex> Path.join("foo", "bar")
      "foo/bar"

      iex> Path.join("/foo", "/bar/")
      "/foo/bar"

  The functions in this module support chardata, so giving a list will
  treat it as a single entity:

      iex> Path.join("foo", ["bar", "fiz"])
      "foo/barfiz"

      iex> Path.join(["foo", "bar"], "fiz")
      "foobar/fiz"

  """
  @spec join(t, t) :: binary
  def join(left, right) do
    left = IO.chardata_to_string(left)
    os_type = major_os_type()
    do_join(left, right, os_type) |> remove_dir_sep(os_type)
  end

  defp do_join("", right, os_type), do: relative(right, os_type)
  defp do_join("/", right, os_type), do: "/" <> relative(right, os_type)

  defp do_join(left, right, os_type),
    do: remove_dir_sep(left, os_type) <> "/" <> relative(right, os_type)

  defp remove_dir_sep("", _os_type), do: ""
  defp remove_dir_sep("/", _os_type), do: "/"

  defp remove_dir_sep(bin, os_type) do
    last = :binary.last(bin)

    if last == ?/ or (last == ?\\ and os_type == :win32) do
      binary_part(bin, 0, byte_size(bin) - 1)
    else
      bin
    end
  end

  @doc ~S"""
  Splits the path into a list at the path separator.

  If an empty string is given, returns an empty list.

  On Windows, path is split on both "\" and "/" separators
  and the driver letter, if there is one, is always returned
  in lowercase.

  ## Examples

      iex> Path.split("")
      []

      iex> Path.split("foo")
      ["foo"]

      iex> Path.split("/foo/bar")
      ["/", "foo", "bar"]

  """
  @spec split(t) :: [binary]

  # Work around a bug in Erlang on UNIX
  def split(""), do: []

  def split(path) do
    :filename.split(IO.chardata_to_string(path))
  end

  defmodule Wildcard do
    @moduledoc false

    def read_link_info(file) do
      call({:read_link_info, file})
    end

    def list_dir(dir) do
      case call({:list_dir, dir}) do
        {:ok, files} ->
          {:ok, for(file <- files, hd(file) != ?., do: file)}

        other ->
          other
      end
    end

    @compile {:inline, call: 1}

    defp call(tuple) do
      x = :erlang.dt_spread_tag(true)
      y = :gen_server.call(:file_server_2, tuple)
      :erlang.dt_restore_tag(x)
      y
    end
  end

  @doc """
  Traverses paths according to the given `glob` expression and returns a
  list of matches.

  The wildcard looks like an ordinary path, except that the following
  "wildcard characters" are interpreted in a special way:

    * `?` - matches one character.

    * `*` - matches any number of characters up to the end of the filename, the
      next dot, or the next slash.

    * `**` - two adjacent `*`'s used as a single pattern will match all
      files and zero or more directories and subdirectories.

    * `[char1,char2,...]` - matches any of the characters listed; two
      characters separated by a hyphen will match a range of characters.
      Do not add spaces before and after the comma as it would then match
      paths containing the space character itself.

    * `{item1,item2,...}` - matches one of the alternatives.
      Do not add spaces before and after the comma as it would then match
      paths containing the space character itself.

  Other characters represent themselves. Only paths that have
  exactly the same character in the same position will match. Note
  that matching is case-sensitive: `"a"` will not match `"A"`.

  By default, the patterns `*` and `?` do not match files starting
  with a dot `.`. See the `:match_dot` option in the "Options" section
  below.

  ## Options

    * `:match_dot` - (boolean) if `false`, the special wildcard characters `*` and `?`
      will not match files starting with a dot (`.`). If `true`, files starting with
      a `.` will not be treated specially. Defaults to `false`.

  ## Examples

  Imagine you have a directory called `projects` with three Elixir projects
  inside of it: `elixir`, `ex_doc`, and `plug`. You can find all `.beam` files
  inside the `ebin` directory of each project as follows:

      Path.wildcard("projects/*/ebin/**/*.beam")

  If you want to search for both `.beam` and `.app` files, you could do:

      Path.wildcard("projects/*/ebin/**/*.{beam,app}")

  """
  @spec wildcard(t, keyword) :: [binary]
  def wildcard(glob, opts \\ []) do
    mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard

    glob
    |> chardata_to_list!()
    |> :filelib.wildcard(mod)
    |> Enum.map(&IO.chardata_to_string/1)
  end

  defp chardata_to_list!(chardata) do
    case :unicode.characters_to_list(chardata) do
      result when is_list(result) ->
        if 0 in result do
          raise ArgumentError,
                "cannot execute Path.wildcard/2 for path with null byte, got: #{inspect(chardata)}"
        else
          result
        end

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  defp expand_home(type) do
    case IO.chardata_to_string(type) do
      "~" <> rest -> resolve_home(rest)
      rest -> rest
    end
  end

  defp resolve_home(""), do: System.user_home!()

  defp resolve_home(rest) do
    case {rest, major_os_type()} do
      {"\\" <> _, :win32} ->
        System.user_home!() <> rest

      {"/" <> _, _} ->
        System.user_home!() <> rest

      _ ->
        rest
    end
  end

  # expand_dot the given path by expanding "..", "." and "~".
  defp expand_dot(<<"/", rest::binary>>), do: "/" <> do_expand_dot(rest)

  defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
    do: <<letter, ":/">> <> do_expand_dot(rest)

  defp expand_dot(path), do: do_expand_dot(path)

  defp do_expand_dot(path), do: do_expand_dot(:binary.split(path, "/", [:global]), [])
  defp do_expand_dot([".." | t], [_, _ | acc]), do: do_expand_dot(t, acc)
  defp do_expand_dot([".." | t], []), do: do_expand_dot(t, [])
  defp do_expand_dot(["." | t], acc), do: do_expand_dot(t, acc)
  defp do_expand_dot([h | t], acc), do: do_expand_dot(t, ["/", h | acc])
  defp do_expand_dot([], []), do: ""
  defp do_expand_dot([], ["/" | acc]), do: IO.iodata_to_binary(:lists.reverse(acc))

  defp major_os_type do
    :os.type() |> elem(0)
  end
end

defmodule Path do
  @moduledoc """
  This module provides conveniences for manipulating or
  retrieving file system paths.

  The functions in this module may receive a chardata as
  argument (i.e. a string or a list of characters / string)
  and will always return a string (encoded in UTF-8).

  The majority of the functions in this module do not
  interact with the file system, except for a few functions
  that require it (like `wildcard/2` and `expand/1`).
  """

  @type t :: :unicode.chardata()

  @doc """
  Converts the given path to an absolute one. Unlike
  `expand/1`, no attempt is made to resolve `..`, `.` or `~`.

  ## Examples

  ### Unix

      Path.absname("foo")
      #=> "/usr/local/foo"

      Path.absname("../x")
      #=> "/usr/local/../x"

  ### Windows

      Path.absname("foo").
      #=> "D:/usr/local/foo"
      Path.absname("../x").
      #=> "D:/usr/local/../x"

  """
  @spec absname(t) :: binary
  def absname(path) do
    absname(path, System.cwd!())
  end

  @doc """
  Builds a path from `relative_to` to `path`.

  If `path` is already an absolute path, `relative_to` is ignored. See also
  `relative_to/2`.

  Unlike `expand/2`, no attempt is made to
  resolve `..`, `.` or `~`.

  ## Examples

      iex> Path.absname("foo", "bar")
      "bar/foo"

      iex> Path.absname("../x", "bar")
      "bar/../x"

  """
  @spec absname(t, t) :: binary
  def absname(path, relative_to) do
    path = IO.chardata_to_string(path)

    case type(path) do
      :relative ->
        absname_join(relative_to, path)

      :absolute ->
        absname_join([path])

      :volumerelative ->
        relative_to = IO.chardata_to_string(relative_to)
        absname_vr(split(path), split(relative_to), relative_to)
    end
  end

  # Absolute path on current drive
  defp absname_vr(["/" | rest], [volume | _], _relative), do: absname_join([volume | rest])

  # Relative to current directory on current drive.
  defp absname_vr([<<x, ?:>> | rest], [<<x, _::binary>> | _], relative),
    do: absname(absname_join(rest), relative)

  # Relative to current directory on another drive.
  defp absname_vr([<<x, ?:>> | name], _, _relative) do
    cwd =
      case :file.get_cwd([x, ?:]) do
        {:ok, dir} -> IO.chardata_to_string(dir)
        {:error, _} -> <<x, ?:, ?/>>
      end

    absname(absname_join(name), cwd)
  end

  # Joins a list
  defp absname_join([name1, name2 | rest]), do: absname_join([absname_join(name1, name2) | rest])

  defp absname_join([name]),
    do: do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())

  # Joins two paths
  defp absname_join(left, right),
    do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())

  defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32)
       when uc_letter in ?A..?Z do
    do_absname_join(rest, relativename, [?:, uc_letter + ?a - ?A], :win32)
  end

  defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32),
    do: do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)

  defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/ | result], os_type),
    do: do_absname_join(rest, relativename, [?/ | result], os_type)

  defp do_absname_join(<<?/, rest::binary>>, relativename, [?/ | result], os_type),
    do: do_absname_join(rest, relativename, [?/ | result], os_type)

  defp do_absname_join(<<>>, <<>>, result, os_type),
    do: IO.iodata_to_binary(reverse_maybe_remove_dir_sep(result, os_type))

  defp do_absname_join(<<>>, relativename, [?: | rest], :win32),
    do: do_absname_join(relativename, <<>>, [?: | rest], :win32)

  defp do_absname_join(<<>>, relativename, [?/ | result], os_type),
    do: do_absname_join(relativename, <<>>, [?/ | result], os_type)

  defp do_absname_join(<<>>, relativename, result, os_type),
    do: do_absname_join(relativename, <<>>, [?/ | result], os_type)

  defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type),
    do: do_absname_join(rest, relativename, [char | result], os_type)

  defp reverse_maybe_remove_dir_sep([?/, ?:, letter], :win32), do: [letter, ?:, ?/]
  defp reverse_maybe_remove_dir_sep([?/], _), do: [?/]
  defp reverse_maybe_remove_dir_sep([?/ | name], _), do: :lists.reverse(name)
  defp reverse_maybe_remove_dir_sep(name, _), do: :lists.reverse(name)

  @doc """
  Converts the path to an absolute one and expands
  any `.` and `..` characters and a leading `~`.

  ## Examples

      Path.expand("/foo/bar/../bar")
      #=> "/foo/bar"

  """
  @spec expand(t) :: binary
  def expand(path) do
    expand_dot(absname(expand_home(path), System.cwd!()))
  end

  @doc """
  Expands the path relative to the path given as the second argument
  expanding any `.` and `..` characters.

  If the path is already an absolute path, `relative_to` is ignored.

  Note that this function treats a `path` with a leading `~` as
  an absolute one.

  The second argument is first expanded to an absolute path.

  ## Examples

      # Assuming that the absolute path to baz is /quux/baz
      Path.expand("foo/bar/../bar", "baz")
      #=> "/quux/baz/foo/bar"

      Path.expand("foo/bar/../bar", "/baz")
      "/baz/foo/bar"
      Path.expand("/foo/bar/../bar", "/baz")
      "/foo/bar"

  """
  @spec expand(t, t) :: binary
  def expand(path, relative_to) do
    expand_dot(absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!()))
  end

  @doc """
  Returns the path type.

  ## Examples

  ### Unix

      Path.type("/")                #=> :absolute
      Path.type("/usr/local/bin")   #=> :absolute
      Path.type("usr/local/bin")    #=> :relative
      Path.type("../usr/local/bin") #=> :relative
      Path.type("~/file")           #=> :relative

  ### Windows

      Path.type("D:/usr/local/bin") #=> :absolute
      Path.type("usr/local/bin")    #=> :relative
      Path.type("D:bar.ex")         #=> :volumerelative
      Path.type("/bar/foo.ex")      #=> :volumerelative

  """
  @spec type(t) :: :absolute | :relative | :volumerelative
  def type(name)
      when is_list(name)
      when is_binary(name) do
    pathtype(name, major_os_type()) |> elem(0)
  end

  @doc """
  Forces the path to be a relative path.

  ## Examples

  ### Unix

      Path.relative("/usr/local/bin")   #=> "usr/local/bin"
      Path.relative("usr/local/bin")    #=> "usr/local/bin"
      Path.relative("../usr/local/bin") #=> "../usr/local/bin"

  ### Windows

      Path.relative("D:/usr/local/bin") #=> "usr/local/bin"
      Path.relative("usr/local/bin")    #=> "usr/local/bin"
      Path.relative("D:bar.ex")         #=> "bar.ex"
      Path.relative("/bar/foo.ex")      #=> "bar/foo.ex"

  """
  @spec relative(t) :: binary
  def relative(name) do
    relative(name, major_os_type())
  end

  defp relative(name, os_type) do
    pathtype(name, os_type)
    |> elem(1)
    |> IO.chardata_to_string()
  end

  defp pathtype(name, os_type) do
    case os_type do
      :win32 -> win32_pathtype(name)
      _ -> unix_pathtype(name)
    end
  end

  defp unix_pathtype(path) when path in ["/", '/'], do: {:absolute, "."}
  defp unix_pathtype(<<?/, relative::binary>>), do: {:absolute, relative}
  defp unix_pathtype([?/ | relative]), do: {:absolute, relative}
  defp unix_pathtype([list | rest]) when is_list(list), do: unix_pathtype(list ++ rest)
  defp unix_pathtype(relative), do: {:relative, relative}

  @slash [?/, ?\\]

  defp win32_pathtype([list | rest]) when is_list(list), do: win32_pathtype(list ++ rest)

  defp win32_pathtype([char, list | rest]) when is_list(list),
    do: win32_pathtype([char | list ++ rest])

  defp win32_pathtype(<<c1, c2, relative::binary>>) when c1 in @slash and c2 in @slash,
    do: {:absolute, relative}

  defp win32_pathtype(<<char, relative::binary>>) when char in @slash,
    do: {:volumerelative, relative}

  defp win32_pathtype(<<_letter, ?:, char, relative::binary>>) when char in @slash,
    do: {:absolute, relative}

  defp win32_pathtype(<<_letter, ?:, relative::binary>>), do: {:volumerelative, relative}

  defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash,
    do: {:absolute, relative}

  defp win32_pathtype([char | relative]) when char in @slash, do: {:volumerelative, relative}

  defp win32_pathtype([c1, c2, list | rest]) when is_list(list),
    do: win32_pathtype([c1, c2 | list ++ rest])

  defp win32_pathtype([_letter, ?:, char | relative]) when char in @slash,
    do: {:absolute, relative}

  defp win32_pathtype([_letter, ?: | relative]), do: {:volumerelative, relative}
  defp win32_pathtype(relative), do: {:relative, relative}

  @doc """
  Returns the given `path` relative to the given `from` path.

  In other words, this function tries to strip the `from` prefix from `path`.

  This function does not query the file system, so it assumes
  no symlinks between the paths.

  In case a direct relative path cannot be found, it returns
  the original path.

  ## Examples

      iex> Path.relative_to("/usr/local/foo", "/usr/local")
      "foo"

      iex> Path.relative_to("/usr/local/foo", "/")
      "usr/local/foo"

      iex> Path.relative_to("/usr/local/foo", "/etc")
      "/usr/local/foo"

  """
  @spec relative_to(t, t) :: binary
  def relative_to(path, from) do
    path = IO.chardata_to_string(path)
    relative_to(split(path), split(from), path)
  end

  defp relative_to([h | t1], [h | t2], original) do
    relative_to(t1, t2, original)
  end

  defp relative_to([_ | _] = l1, [], _original) do
    join(l1)
  end

  defp relative_to(_, _, original) do
    original
  end

  @doc """
  Convenience to get the path relative to the current working
  directory.

  If, for some reason, the current working directory
  cannot be retrieved, this function returns the given `path`.
  """
  @spec relative_to_cwd(t) :: binary
  def relative_to_cwd(path) do
    case :file.get_cwd() do
      {:ok, base} -> relative_to(path, IO.chardata_to_string(base))
      _ -> path
    end
  end

  @doc """
  Returns the last component of the path or the path
  itself if it does not contain any directory separators.

  ## Examples

      iex> Path.basename("foo")
      "foo"

      iex> Path.basename("foo/bar")
      "bar"

      iex> Path.basename("/")
      ""

  """
  @spec basename(t) :: binary
  def basename(path) do
    :filename.basename(IO.chardata_to_string(path))
  end

  @doc """
  Returns the last component of `path` with the `extension`
  stripped.

  This function should be used to remove a specific
  extension which may or may not be there.

  ## Examples

      iex> Path.basename("~/foo/bar.ex", ".ex")
      "bar"

      iex> Path.basename("~/foo/bar.exs", ".ex")
      "bar.exs"

      iex> Path.basename("~/foo/bar.old.ex", ".ex")
      "bar.old"

  """
  @spec basename(t, t) :: binary
  def basename(path, extension) do
    :filename.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
  end

  @doc """
  Returns the directory component of `path`.

  ## Examples

      iex> Path.dirname("/foo/bar.ex")
      "/foo"

      iex> Path.dirname("/foo/bar/baz.ex")
      "/foo/bar"

      iex> Path.dirname("/foo/bar/")
      "/foo/bar"

  """
  @spec dirname(t) :: binary
  def dirname(path) do
    :filename.dirname(IO.chardata_to_string(path))
  end

  @doc """
  Returns the extension of the last component of `path`.

  ## Examples

      iex> Path.extname("foo.erl")
      ".erl"

      iex> Path.extname("~/foo/bar")
      ""

  """
  @spec extname(t) :: binary
  def extname(path) do
    :filename.extension(IO.chardata_to_string(path))
  end

  @doc """
  Returns the `path` with the `extension` stripped.

  ## Examples

      iex> Path.rootname("/foo/bar")
      "/foo/bar"

      iex> Path.rootname("/foo/bar.ex")
      "/foo/bar"

  """
  @spec rootname(t) :: binary
  def rootname(path) do
    :filename.rootname(IO.chardata_to_string(path))
  end

  @doc """
  Returns the `path` with the `extension` stripped.

  This function should be used to remove a specific extension which may
  or may not be there.

  ## Examples

      iex> Path.rootname("/foo/bar.erl", ".erl")
      "/foo/bar"

      iex> Path.rootname("/foo/bar.erl", ".ex")
      "/foo/bar.erl"

  """
  @spec rootname(t, t) :: binary
  def rootname(path, extension) do
    :filename.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
  end

  @doc """
  Joins a list of paths.

  This function should be used to convert a list of paths to a path.
  Note that any trailing slash is removed when joining.

  ## Examples

      iex> Path.join(["~", "foo"])
      "~/foo"

      iex> Path.join(["foo"])
      "foo"

      iex> Path.join(["/", "foo", "bar/"])
      "/foo/bar"

  """
  @spec join(nonempty_list(t)) :: binary
  def join([name1, name2 | rest]), do: join([join(name1, name2) | rest])
  def join([name]), do: IO.chardata_to_string(name)

  @doc """
  Joins two paths.

  The right path will always be expanded to its relative format
  and any trailing slash will be removed when joining.

  ## Examples

      iex> Path.join("foo", "bar")
      "foo/bar"

      iex> Path.join("/foo", "/bar/")
      "/foo/bar"

  The functions in this module support chardata, so giving a list will
  treat it as a single entity:

      iex> Path.join("foo", ["bar", "fiz"])
      "foo/barfiz"

      iex> Path.join(["foo", "bar"], "fiz")
      "foobar/fiz"

  """
  @spec join(t, t) :: binary
  def join(left, right) do
    left = IO.chardata_to_string(left)
    os_type = major_os_type()
    do_join(left, right, os_type) |> remove_dir_sep(os_type)
  end

  defp do_join("", right, os_type), do: relative(right, os_type)
  defp do_join("/", right, os_type), do: "/" <> relative(right, os_type)

  defp do_join(left, right, os_type),
    do: remove_dir_sep(left, os_type) <> "/" <> relative(right, os_type)

  defp remove_dir_sep("", _os_type), do: ""
  defp remove_dir_sep("/", _os_type), do: "/"

  defp remove_dir_sep(bin, os_type) do
    last = :binary.last(bin)

    if last == ?/ or (last == ?\\ and os_type == :win32) do
      binary_part(bin, 0, byte_size(bin) - 1)
    else
      bin
    end
  end

  @doc ~S"""
  Splits the path into a list at the path separator.

  If an empty string is given, returns an empty list.

  On Windows, path is split on both "\" and "/" separators
  and the driver letter, if there is one, is always returned
  in lowercase.

  ## Examples

      iex> Path.split("")
      []

      iex> Path.split("foo")
      ["foo"]

      iex> Path.split("/foo/bar")
      ["/", "foo", "bar"]

  """
  @spec split(t) :: [binary]

  # Work around a bug in Erlang on UNIX
  def split(""), do: []

  def split(path) do
    :filename.split(IO.chardata_to_string(path))
  end

  defmodule Wildcard do
    @moduledoc false

    def read_link_info(file) do
      call({:read_link_info, file})
    end

    def list_dir(dir) do
      case call({:list_dir, dir}) do
        {:ok, files} ->
          {:ok, for(file <- files, hd(file) != ?., do: file)}

        other ->
          other
      end
    end

    @compile {:inline, call: 1}

    defp call(tuple) do
      x = :erlang.dt_spread_tag(true)
      y = :gen_server.call(:file_server_2, tuple)
      :erlang.dt_restore_tag(x)
      y
    end
  end

  @doc """
  Traverses paths according to the given `glob` expression and returns a
  list of matches.

  The wildcard looks like an ordinary path, except that certain
  "wildcard characters" are interpreted in a special way. The
  following characters are special:

    * `?` - matches one character

    * `*` - matches any number of characters up to the end of the filename, the
      next dot, or the next slash

    * `**` - two adjacent `*`'s used as a single pattern will match all
      files and zero or more directories and subdirectories

    * `[char1,char2,...]` - matches any of the characters listed; two
      characters separated by a hyphen will match a range of characters.
      Do not add spaces before and after the comma as it would then match
      paths containing the space character itself.

    * `{item1,item2,...}` - matches one of the alternatives
      Do not add spaces before and after the comma as it would then match
      paths containing the space character itself.

  Other characters represent themselves. Only paths that have
  exactly the same character in the same position will match. Note
  that matching is case-sensitive: `"a"` will not match `"A"`.

  By default, the patterns `*` and `?` do not match files starting
  with a dot `.` unless `match_dot: true` is given in `opts`.

  ## Examples

  Imagine you have a directory called `projects` with three Elixir projects
  inside of it: `elixir`, `ex_doc`, and `plug`. You can find all `.beam` files
  inside the `ebin` directory of each project as follows:

      Path.wildcard("projects/*/ebin/**/*.beam")

  If you want to search for both `.beam` and `.app` files, you could do:

      Path.wildcard("projects/*/ebin/**/*.{beam,app}")

  """
  @spec wildcard(t, keyword) :: [binary]
  def wildcard(glob, opts \\ []) do
    mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard

    glob
    |> chardata_to_list!()
    |> :filelib.wildcard(mod)
    |> Enum.map(&IO.chardata_to_string/1)
  end

  defp chardata_to_list!(chardata) do
    case :unicode.characters_to_list(chardata) do
      result when is_list(result) ->
        if 0 in result do
          raise ArgumentError,
                "cannot execute Path.wildcard/2 for path with null byte, got: #{inspect(chardata)}"
        else
          result
        end

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  defp expand_home(type) do
    case IO.chardata_to_string(type) do
      "~" <> rest -> resolve_home(rest)
      rest -> rest
    end
  end

  defp resolve_home(""), do: System.user_home!()

  defp resolve_home(rest) do
    case {rest, major_os_type()} do
      {"\\" <> _, :win32} ->
        System.user_home!() <> rest

      {"/" <> _, _} ->
        System.user_home!() <> rest

      _ ->
        rest
    end
  end

  # expand_dot the given path by expanding "..", "." and "~".
  defp expand_dot(<<"/", rest::binary>>), do: "/" <> do_expand_dot(rest)

  defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
    do: <<letter, ":/">> <> do_expand_dot(rest)

  defp expand_dot(path), do: do_expand_dot(path)

  defp do_expand_dot(path), do: do_expand_dot(:binary.split(path, "/", [:global]), [])
  defp do_expand_dot([".." | t], [_, _ | acc]), do: do_expand_dot(t, acc)
  defp do_expand_dot([".." | t], []), do: do_expand_dot(t, [])
  defp do_expand_dot(["." | t], acc), do: do_expand_dot(t, acc)
  defp do_expand_dot([h | t], acc), do: do_expand_dot(t, ["/", h | acc])
  defp do_expand_dot([], []), do: ""
  defp do_expand_dot([], ["/" | acc]), do: IO.iodata_to_binary(:lists.reverse(acc))

  defp major_os_type do
    :os.type() |> elem(0)
  end
end


finds metadata start marker

finds metadata start marker

defmodule Geolix.Database do
  @metadata_marker <<171, 205, 239>> <> "MaxMind.com"

  def read(db_dir) do
    cities    = read_cities(db_dir)
    countries = read_countries(db_dir)

    cond do
      is_list(cities) and is_list(countries) -> cities ++ countries
      is_tuple(cities)    -> cities
      is_tuple(countries) -> countries
      true -> { :error, "Unknown error" }
    end
  end

  defp read_cities(_) do
    []
  end

  defp read_countries(db_dir) do
    db_file    = db_dir <> "GeoLite2-Country.mmdb"
    db_file_gz = db_file <> ".gz"

    cond do
      File.regular?(db_file)    -> parse_countries({ :regular, db_file })
      File.regular?(db_file_gz) -> parse_countries({ :gzip , db_file_gz})
      true -> { :error, "Failed to find 'GeoLite2-Country.mmdb[.gz]' in given path '#{db_dir}'!" }
    end
  end

  defp parse_countries({ :regular, db_file }) do
    stream_countries(File.binstream!(db_file, [:read], 1))
  end
  defp parse_countries({ :gzip, db_file }) do
    stream_countries(File.binstream!(db_file, [:read, :compressed], 1))
  end

  defp stream_countries(stream) do
    meta_stream = drop_until_meta(stream)

    IO.puts inspect(@metadata_marker) <> " <> " <> inspect(Enum.join(Enum.take(meta_stream, byte_size(@metadata_marker))))
  end

  defp drop_until_meta(stream) do
    stream = Enum.drop_while(stream, fn(c) -> c != String.at(@metadata_marker, 0) end)
    marker = Enum.take(stream, byte_size(@metadata_marker)) |> Enum.join

    if marker == @metadata_marker do
      stream
    else
      drop_until_meta(Enum.drop(stream, 1))
    end
  end
end

defmodule Geolix.Database do
  @metadata_marker "\xAB\xCD\xEFMaxMind.com"

  def read(db_dir) do
    cities    = read_cities(db_dir)
    countries = read_countries(db_dir)

    cond do
      is_list(cities) and is_list(countries) -> cities ++ countries
      is_tuple(cities)    -> cities
      is_tuple(countries) -> countries
      true -> { :error, "Unknown error" }
    end
  end

  defp read_cities(db_dir) do
    []
  end

  defp read_countries(db_dir) do
    db_file    = db_dir <> "GeoLite2-Country.mmdb"
    db_file_gz = db_file <> ".gz"

    cond do
      File.regular?(db_file)    -> parse_countries(File.read(db_file))
      File.regular?(db_file_gz) -> parse_countries_gz(File.read(db_file_gz))
      true -> { :error, "Failed to find 'GeoLite2-Country.mmdb[.gz]' in given path '#{db_dir}'!" }
    end
  end

  defp parse_countries_gz({ :ok, gz_data }) do
    parse_countries(:zlib.gunzip(gz_data))
  end
  defp parse_countries_gz({ :error, reason }) do
    { :error, "Failed to read country db: #{reason}" }
  end

  defp parse_countries({ :ok, data }) do
    parse_countries(data)
  end
  defp parse_countries(data) when is_binary(data) do
    IO.inspect "Reading stuff! Size: " <> integer_to_binary(String.length(data))
    []
  end
end


Clean fields in reply message in message module

defmodule IElixir.Message do
  @moduledoc """
  This is documentation for Message structure and some utils that helps in
  encoding, parsing, assembling and sending messages.

  Here are extracted functions which helps with messages management.
  """

  require Logger
  alias IElixir.HMAC

  defstruct uuid: nil,
    baddad42: nil,
    header: nil,
    parent_header: nil,
    metadata: nil,
    content: nil,
    blob: nil

  @doc false
  def encode(message) do
    header = Poison.encode!(message.header)
    parent_header = Poison.encode!(message.parent_header)
    metadata = Poison.encode!(message.metadata)
    content = Poison.encode!(message.content)

    message = [
      message.uuid,
      "<IDS|MSG>",
      HMAC.compute_signature(header, parent_header, metadata, content),
      header,
      parent_header,
      metadata,
      content
    ]
    Logger.debug("Message encoded: #{inspect message}")
    message
  end

  @doc false
  def parse([uuid, "<IDS|MSG>", baddad42, header, parent_header, metadata, content | blob]) do
    %IElixir.Message{uuid: uuid,
      baddad42: baddad42,
      header: Poison.Parser.parse!(header),
      parent_header: Poison.Parser.parse!(parent_header),
      metadata: Poison.Parser.parse!(metadata),
      content: Poison.Parser.parse!(content),
      blob: blob}
  end
  def parse(message) do
    Logger.warn("Invalid message on shell socket #{inspect message}")
  end

  @doc false
  def assemble_message({:zmq, _, message, flags}, {sock, message_buffer}, process_fun) do
    case assemble_message_part(message, flags, message_buffer) do
      {:buffer, buffer} ->
        {sock, buffer}
      {:msg, message} ->
        process_fun.(message.header["msg_type"], message, sock)
        {sock, []}
    end
  end

  defp assemble_message_part(message, flags, message_buffer) do
    message_buffer = [message | message_buffer]
    if :rcvmore in flags do
      {:buffer, message_buffer}
    else
      {:msg, parse(Enum.reverse(message_buffer))}
    end
  end

  @doc false
  def send_message(sock, message, message_type, content) do
    new_message = %{message |
      "header": %{
        "msg_id" => :uuid.uuid_to_string(:uuid.get_v4(), :binary_standard),
        "username" => "ielixir_kernel",
        "session" => message.header["session"],
        "msg_type" => message_type,
        "version" => "5.0",
      },
      "parent_header": message.header,
      "metadata": %{},
      "content": content,
    }
    send_all(sock, encode(new_message))
  end

  @doc false
  def send_all(sock, [message]) do
    :ok = :erlzmq.send(sock, message, [])
  end
  def send_all(sock, [message | other_messages]) do
    :ok = :erlzmq.send(sock, message, [:sndmore])
    send_all(sock, other_messages)
  end
end

defmodule IElixir.Message do
  @moduledoc """
  This is documentation for Message structure and some utils that helps in
  encoding, parsing, assembling and sending messages.

  Here are extracted functions which helps with messages management.
  """

  require Logger
  alias IElixir.HMAC

  defstruct uuid: nil,
    baddad42: nil,
    header: nil,
    parent_header: nil,
    metadata: nil,
    content: nil,
    blob: nil

  @doc false
  def encode(message) do
    header = Poison.encode!(message.header)
    parent_header = Poison.encode!(message.parent_header)
    metadata = Poison.encode!(message.metadata)
    content = Poison.encode!(message.content)

    message = [
      message.uuid,
      "<IDS|MSG>",
      HMAC.compute_signature(header, parent_header, metadata, content),
      header,
      parent_header,
      metadata,
      content
    ]
    Logger.debug("Message encoded: #{inspect message}")
    message
  end

  @doc false
  def parse([uuid, "<IDS|MSG>", baddad42, header, parent_header, metadata, content | blob]) do
    %IElixir.Message{uuid: uuid,
      baddad42: baddad42,
      header: Poison.Parser.parse!(header),
      parent_header: Poison.Parser.parse!(parent_header),
      metadata: Poison.Parser.parse!(metadata),
      content: Poison.Parser.parse!(content),
      blob: blob}
  end
  def parse(message) do
    Logger.warn("Invalid message on shell socket #{inspect message}")
  end

  @doc false
  def assemble_message({:zmq, _, message, flags}, {sock, message_buffer}, process_fun) do
    case assemble_message_part(message, flags, message_buffer) do
      {:buffer, buffer} ->
        {sock, buffer}
      {:msg, message} ->
        process_fun.(message.header["msg_type"], message, sock)
        {sock, []}
    end
  end

  defp assemble_message_part(message, flags, message_buffer) do
    message_buffer = [message | message_buffer]
    if :rcvmore in flags do
      {:buffer, message_buffer}
    else
      {:msg, parse(Enum.reverse(message_buffer))}
    end
  end

  @doc false
  def send_message(sock, message, message_type, content) do
    new_message = %{message |
      "parent_header": message.header,
      "header": %{message.header |
        "msg_type" => message_type
      },
      "content": content
    }
    send_all(sock, encode(new_message))
  end

  @doc false
  def send_all(sock, [message]) do
    :ok = :erlzmq.send(sock, message, [])
  end
  def send_all(sock, [message | other_messages]) do
    :ok = :erlzmq.send(sock, message, [:sndmore])
    send_all(sock, other_messages)
  end
end