Add to credo file.

Add to credo file.

# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
  #
  # You can have as many configs as you like in the `configs:` field.
  configs: [
    %{
      #
      # Run any exec using `mix credo -C <name>`. If no exec name is given
      # "default" is used.
      #
      name: "default",
      #
      # These are the files included in the analysis:
      files: %{
        #
        # You can give explicit globs or simply directories.
        # In the latter case `**/*.{ex,exs}` will be used.
        #
        included: ["lib/", "src/", "test/", "web/", "apps/"],
        excluded: [~r"/_build/", ~r"/deps/"]
      },
      #
      # If you create your own checks, you must specify the source files for
      # them here, so they can be loaded by Credo before running the analysis.
      #
      requires: [],
      #
      # If you want to enforce a style guide and need a more traditional linting
      # experience, you can change `strict` to `true` below:
      #
      strict: false,
      #
      # If you want to use uncolored output by default, you can change `color`
      # to `false` below:
      #
      color: true,
      #
      # You can customize the parameters of any check by adding a second element
      # to the tuple.
      #
      # To disable a check put `false` as second element:
      #
      #     {Credo.Check.Design.DuplicatedCode, false}
      #
      checks: [
        #
        ## Consistency Checks
        #
        {Credo.Check.Consistency.ExceptionNames},
        {Credo.Check.Consistency.LineEndings},
        {Credo.Check.Consistency.ParameterPatternMatching},
        {Credo.Check.Consistency.SpaceAroundOperators},
        {Credo.Check.Consistency.SpaceInParentheses},
        {Credo.Check.Consistency.TabsOrSpaces},

        #
        ## Design Checks
        #
        # You can customize the priority of any check
        # Priority values are: `low, normal, high, higher`
        #
        {Credo.Check.Design.AliasUsage, priority: :low},
        # For some checks, you can also set other parameters
        #
        # If you don't want the `setup` and `test` macro calls in ExUnit tests
        # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
        # set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
        #
        {Credo.Check.Design.DuplicatedCode, excluded_macros: []},
        # You can also customize the exit_status of each check.
        # If you don't want TODO comments to cause `mix credo` to fail, just
        # set this value to 0 (zero).
        #
        {Credo.Check.Design.TagTODO, exit_status: 2},
        {Credo.Check.Design.TagFIXME},

        #
        ## Readability Checks
        #
        {Credo.Check.Readability.AliasOrder},
        {Credo.Check.Readability.FunctionNames},
        {Credo.Check.Readability.LargeNumbers},
        {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
        {Credo.Check.Readability.ModuleAttributeNames},
        {Credo.Check.Readability.ModuleDoc},
        {Credo.Check.Readability.ModuleNames},
        {Credo.Check.Readability.ParenthesesOnZeroArityDefs},
        {Credo.Check.Readability.ParenthesesInCondition},
        {Credo.Check.Readability.PredicateFunctionNames},
        {Credo.Check.Readability.PreferImplicitTry},
        {Credo.Check.Readability.RedundantBlankLines},
        {Credo.Check.Readability.StringSigils},
        {Credo.Check.Readability.TrailingBlankLine},
        {Credo.Check.Readability.TrailingWhiteSpace},
        {Credo.Check.Readability.VariableNames},
        {Credo.Check.Readability.Semicolons},
        {Credo.Check.Readability.SpaceAfterCommas},

        #
        ## Refactoring Opportunities
        #
        {Credo.Check.Refactor.DoubleBooleanNegation},
        {Credo.Check.Refactor.CondStatements},
        {Credo.Check.Refactor.CyclomaticComplexity},
        {Credo.Check.Refactor.MapMap},
        {Credo.Check.Refactor.FilterReject},
        {Credo.Check.Refactor.RejectFilter},
        {Credo.Check.Refactor.FilterFilter},
        {Credo.Check.Refactor.RejectReject},
        {Credo.Check.Refactor.FunctionArity},
        {Credo.Check.Refactor.LongQuoteBlocks},
        {Credo.Check.Refactor.MatchInCondition},
        {Credo.Check.Refactor.NegatedConditionsInUnless},
        {Credo.Check.Refactor.NegatedConditionsWithElse},
        {Credo.Check.Refactor.Nesting},
        {Credo.Check.Refactor.PipeChainStart,
         excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []},
        {Credo.Check.Refactor.UnlessWithElse},

        #
        ## Warnings
        #
        {Credo.Check.Warning.BoolOperationOnSameValues},
        {Credo.Check.Warning.ExpensiveEmptyEnumCheck},
        {Credo.Check.Warning.IExPry},
        {Credo.Check.Warning.IoInspect},
        {Credo.Check.Warning.LazyLogging},
        {Credo.Check.Warning.OperationOnSameValues},
        {Credo.Check.Warning.OperationWithConstantResult},
        {Credo.Check.Warning.UnusedEnumOperation},
        {Credo.Check.Warning.UnusedFileOperation},
        {Credo.Check.Warning.UnusedKeywordOperation},
        {Credo.Check.Warning.UnusedListOperation},
        {Credo.Check.Warning.UnusedPathOperation},
        {Credo.Check.Warning.UnusedRegexOperation},
        {Credo.Check.Warning.UnusedStringOperation},
        {Credo.Check.Warning.UnusedTupleOperation},
        {Credo.Check.Warning.RaiseInsideRescue},

        #
        # Controversial and experimental checks (opt-in, just remove `, false`)
        #
        {Credo.Check.Refactor.ABCSize, false},
        {Credo.Check.Refactor.AppendSingleItem, false},
        {Credo.Check.Refactor.VariableRebinding, false},
        {Credo.Check.Warning.MapGetUnsafePass, false},
        {Credo.Check.Consistency.MultiAliasImportRequireUse, false},

        #
        # Deprecated checks (these will be deleted after a grace period)
        #
        {Credo.Check.Readability.Specs, false}

        #
        # Custom checks can be created using `mix credo.gen.check`.
        #
      ]
    }
  ]
}

# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
  #
  # You can have as many configs as you like in the `configs:` field.
  configs: [
    %{
      #
      # Run any exec using `mix credo -C <name>`. If no exec name is given
      # "default" is used.
      #
      name: "default",
      #
      # These are the files included in the analysis:
      files: %{
        #
        # You can give explicit globs or simply directories.
        # In the latter case `**/*.{ex,exs}` will be used.
        #
        included: ["lib/", "src/", "test/", "web/", "apps/"],
        excluded: [~r"/_build/", ~r"/deps/"]
      },
      #
      # If you create your own checks, you must specify the source files for
      # them here, so they can be loaded by Credo before running the analysis.
      #
      requires: [],
      #
      # If you want to enforce a style guide and need a more traditional linting
      # experience, you can change `strict` to `true` below:
      #
      strict: false,
      #
      # If you want to use uncolored output by default, you can change `color`
      # to `false` below:
      #
      color: true,
      #
      # You can customize the parameters of any check by adding a second element
      # to the tuple.
      #
      # To disable a check put `false` as second element:
      #
      #     {Credo.Check.Design.DuplicatedCode, false}
      #
      checks: [
        #
        ## Consistency Checks
        #
        {Credo.Check.Consistency.ExceptionNames},
        {Credo.Check.Consistency.LineEndings},
        {Credo.Check.Consistency.ParameterPatternMatching},
        {Credo.Check.Consistency.SpaceAroundOperators},
        {Credo.Check.Consistency.SpaceInParentheses},
        {Credo.Check.Consistency.TabsOrSpaces},

        #
        ## Design Checks
        #
        # You can customize the priority of any check
        # Priority values are: `low, normal, high, higher`
        #
        {Credo.Check.Design.AliasUsage, priority: :low},
        # For some checks, you can also set other parameters
        #
        # If you don't want the `setup` and `test` macro calls in ExUnit tests
        # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
        # set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
        #
        {Credo.Check.Design.DuplicatedCode, excluded_macros: []},
        # You can also customize the exit_status of each check.
        # If you don't want TODO comments to cause `mix credo` to fail, just
        # set this value to 0 (zero).
        #
        {Credo.Check.Design.TagTODO, exit_status: 2},
        {Credo.Check.Design.TagFIXME},

        #
        ## Readability Checks
        #
        {Credo.Check.Readability.AliasOrder},
        {Credo.Check.Readability.FunctionNames},
        {Credo.Check.Readability.LargeNumbers},
        {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
        {Credo.Check.Readability.ModuleAttributeNames},
        {Credo.Check.Readability.ModuleDoc},
        {Credo.Check.Readability.ModuleNames},
        {Credo.Check.Readability.ParenthesesOnZeroArityDefs},
        {Credo.Check.Readability.ParenthesesInCondition},
        {Credo.Check.Readability.PredicateFunctionNames},
        {Credo.Check.Readability.PreferImplicitTry},
        {Credo.Check.Readability.RedundantBlankLines},
        {Credo.Check.Readability.StringSigils},
        {Credo.Check.Readability.TrailingBlankLine},
        {Credo.Check.Readability.TrailingWhiteSpace},
        {Credo.Check.Readability.VariableNames},
        {Credo.Check.Readability.Semicolons},
        {Credo.Check.Readability.SpaceAfterCommas},

        #
        ## Refactoring Opportunities
        #
        {Credo.Check.Refactor.DoubleBooleanNegation},
        {Credo.Check.Refactor.CondStatements},
        {Credo.Check.Refactor.CyclomaticComplexity},
        {Credo.Check.Refactor.FunctionArity},
        {Credo.Check.Refactor.LongQuoteBlocks},
        {Credo.Check.Refactor.MatchInCondition},
        {Credo.Check.Refactor.NegatedConditionsInUnless},
        {Credo.Check.Refactor.NegatedConditionsWithElse},
        {Credo.Check.Refactor.Nesting},
        {Credo.Check.Refactor.PipeChainStart,
         excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []},
        {Credo.Check.Refactor.UnlessWithElse},

        #
        ## Warnings
        #
        {Credo.Check.Warning.BoolOperationOnSameValues},
        {Credo.Check.Warning.ExpensiveEmptyEnumCheck},
        {Credo.Check.Warning.IExPry},
        {Credo.Check.Warning.IoInspect},
        {Credo.Check.Warning.LazyLogging},
        {Credo.Check.Warning.OperationOnSameValues},
        {Credo.Check.Warning.OperationWithConstantResult},
        {Credo.Check.Warning.UnusedEnumOperation},
        {Credo.Check.Warning.UnusedFileOperation},
        {Credo.Check.Warning.UnusedKeywordOperation},
        {Credo.Check.Warning.UnusedListOperation},
        {Credo.Check.Warning.UnusedPathOperation},
        {Credo.Check.Warning.UnusedRegexOperation},
        {Credo.Check.Warning.UnusedStringOperation},
        {Credo.Check.Warning.UnusedTupleOperation},
        {Credo.Check.Warning.RaiseInsideRescue},

        #
        # Controversial and experimental checks (opt-in, just remove `, false`)
        #
        {Credo.Check.Refactor.ABCSize, false},
        {Credo.Check.Refactor.AppendSingleItem, false},
        {Credo.Check.Refactor.VariableRebinding, false},
        {Credo.Check.Warning.MapGetUnsafePass, false},
        {Credo.Check.Consistency.MultiAliasImportRequireUse, false},

        #
        # Deprecated checks (these will be deleted after a grace period)
        #
        {Credo.Check.Readability.Specs, false}

        #
        # Custom checks can be created using `mix credo.gen.check`.
        #
      ]
    }
  ]
}


Fixed misleading function name, it converts header string to atom (#61)


pleases the dialyzer gods

pleases the dialyzer gods

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

  require Logger

  alias Geolix.Adapter.MMDB2.Reader
  alias Geolix.Adapter.MMDB2.Storage

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.load_database/1`.

  Requires the parameter `:source` as the location of the database. Can access
  the system environment by receiving a `{ :system, "env_var_name" }` tuple.

  Using `{ :system, "env_var_name", "/path/to/default.mmdb2" }` you can define
  a fallback value to be used if the environment variable is not set.
  """
  @spec load_database(map) :: :ok | {:error, term}
  def load_database(%{source: {:system, var, default}} = database) do
    database
    |> Map.put(:source, System.get_env(var) || default)
    |> load_database()
  end

  def load_database(%{source: {:system, var}} = database) do
    database
    |> Map.put(:source, System.get_env(var))
    |> load_database()
  end

  def load_database(%{source: source} = database) do
    source
    |> Reader.read_database()
    |> store_data(database)
  end

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.unload_database/1`.
  """
  def unload_database(%{id: id}), do: store_data({:ok, nil, nil, nil}, id)

  defp store_data({:error, :enoent} = error, %{id: id, source: source}) do
    _ =
      Logger.info(fn ->
        "Source for database #{inspect(id)} not found: #{inspect(source)}"
      end)

    error
  end

  defp store_data({:error, :no_metadata} = error, %{id: id}) do
    _ =
      Logger.info(fn ->
        "Failed to read metadata for database #{inspect(id)}"
      end)

    error
  end

  defp store_data({:error, {:remote, reason}} = error, %{id: id}) do
    _ =
      Logger.info(fn ->
        "Failed to read remote for database #{inspect(id)}: #{inspect(reason)}"
      end)

    error
  end

  defp store_data({:error, reason} = error, %{id: id}) do
    _ =
      Logger.info(fn ->
        "Failed to load database #{inspect(id)}: #{inspect(reason)}"
      end)

    error
  end

  defp store_data({:ok, meta, tree, data}, %{id: id}) do
    Storage.Data.set(id, data)
    Storage.Metadata.set(id, meta)
    Storage.Tree.set(id, tree)

    :ok
  end
end

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

  require Logger

  alias Geolix.Adapter.MMDB2.Reader
  alias Geolix.Adapter.MMDB2.Storage

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.load_database/1`.

  Requires the parameter `:source` as the location of the database. Can access
  the system environment by receiving a `{ :system, "env_var_name" }` tuple.

  Using `{ :system, "env_var_name", "/path/to/default.mmdb2" }` you can define
  a fallback value to be used if the environment variable is not set.
  """
  @spec load_database(map) :: :ok | {:error, term}
  def load_database(%{source: {:system, var, default}} = database) do
    database
    |> Map.put(:source, System.get_env(var) || default)
    |> load_database()
  end

  def load_database(%{source: {:system, var}} = database) do
    database
    |> Map.put(:source, System.get_env(var))
    |> load_database()
  end

  def load_database(%{source: source} = database) do
    source
    |> Reader.read_database()
    |> store_data(database)
  end

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.unload_database/1`.
  """
  def unload_database(%{id: id}), do: store_data({:ok, nil, nil, nil}, id)

  defp store_data({:error, :enoent} = error, %{id: id, source: source}) do
    Logger.info(fn ->
      "Source for database #{inspect(id)} not found: #{inspect(source)}"
    end)

    error
  end

  defp store_data({:error, :no_metadata} = error, %{id: id}) do
    Logger.info(fn ->
      "Failed to read metadata for database #{inspect(id)}"
    end)

    error
  end

  defp store_data({:error, {:remote, reason}} = error, %{id: id}) do
    Logger.info(fn ->
      "Failed to read remote for database #{inspect(id)}: #{inspect(reason)}"
    end)

    error
  end

  defp store_data({:error, reason} = error, %{id: id}) do
    Logger.info(fn ->
      "Failed to load database #{inspect(id)}: #{inspect(reason)}"
    end)

    error
  end

  defp store_data({:ok, meta, tree, data}, %{id: id}) do
    Storage.Data.set(id, data)
    Storage.Metadata.set(id, meta)
    Storage.Tree.set(id, tree)

    :ok
  end
end


Fix error when attempting to log torrent additions

defmodule Augur.Transmission do
  use GenServer
  require Logger

  alias HTTPoison.Response
  alias Augur.Torrent

  @url "http://192.168.5.115:9091/transmission/rpc"

  ## Client

  @doc """
  Start Link
  """
  def start_link(name \\ nil) do
    GenServer.start_link(__MODULE__, :ok, [name: name])
  end

  @doc """
  Adds multiple torrent to transmission
  """
  def add_torrents(torrents) do
    Enum.map(torrents, &add_torrent/1)
  end

  @doc """
  Adds a torrent to transmission
  """
  def add_torrent(%Torrent{} = torrent) do
    GenServer.cast(Augur.Transmission, {:add_torrent, torrent})
  end

  @doc """
  Creates torrents using given paramaters and then adds it
  """
  def add_torrent(ep_id, downloadDir, url) do
    torrent =
      %Torrent{
        ep_id: ep_id,
        downloadDir: downloadDir,
        url: url
      }

    GenServer.cast(Augur.Transmission, {:add_torrent, torrent})
  end
  @doc """
  Polls status of torrents from list of ids
  """
  def poll(ids) do
    GenServer.cast(Augur.Transmission, {:poll, ids})
  end

  ## Server Callbacks

  @doc """
  Gets a session_id from transmission rpc
  """
  def init(:ok) do
    {:ok, _, s_id} = request("session-get", %{}, "0")
    {:ok, s_id}
  end

  @doc """
  Adds torrent to transmission
  """
  def handle_cast({:add_torrent, torrent}, s_id) do
    method = "torrent-add"
    path = Application.get_env(:animu, :input_root)
    arguments =
      %{"filename"     => torrent.url,
        "download-dir" => Path.join(path, torrent.downloadDir),
        "paused"       => false,
      }

    case request(method, arguments, s_id) do
      {:ok, %{"arguments" => %{"torrent-added" => torrent_info}}, s_id} ->
        id = torrent_info["id"]
        torrent = %Torrent{torrent | id: id}
        Augur.cache_torrents(%{id => torrent})
        Logger.info("Added torrent: #{inspect torrent.name}")
        {:noreply, s_id}
      {:ok, %{"arguments" => %{"torrent-duplicate" => torrent_info}}, s_id} ->
        id = torrent_info["id"]
        torrent = %Torrent{torrent | id: id}
        Augur.cache_torrents(%{id => torrent})
        Logger.info("Added duplicate torrent: #{inspect torrent.name}")
        {:noreply, s_id}
      reply ->
        Logger.error("Failed to add torrent due to: #{reply}")
        {:noreply, s_id}
    end
  end

  @doc """
  Polls the status of torrents from a list of ids
  """
  def handle_cast({:poll, ids}, s_id) do
    {torrents, s_id} = poll(ids, s_id)
    Augur.cache_torrents(torrents)
    {:noreply, s_id}
  end

  ## Helpers

  # Helper function that keeps session_id up to date and sends requests to
  # Transmission's RPC API.
  defp request(method, arguments, session_id) do
    options = [recv_timeout: 20 * 1000]

    headers = [{"X-Transmission-Session-Id", session_id},
               {"Accept", "application/json; charset=utf-8"}]

    body =
      Poison.encode!(%{method: method, arguments: arguments})

    case HTTPoison.post(@url, body, headers, options) do
      {:ok, %Response{status_code: 200, body: body}} ->
        {:ok, Poison.decode!(body), session_id}

      {:ok, %Response{status_code: 409, headers: headers}} ->
        session_id = Map.new(headers)["X-Transmission-Session-Id"]
        request(method, arguments, session_id)

      {:error, %HTTPoison.Error{reason: reason}} ->
        Logger.error "Transmission request failed due to #{reason} error"
        {:error, session_id}

      _ ->
        Logger.error "Transmission request failed due to unkown reasons"
        {:error, session_id}
    end
  end

  # Polls status of tracked torrents
  defp poll([], s_id), do: {%{}, s_id}
  defp poll(ids, s_id) do
    method = "torrent-get"
    arguments =
      %{"fields" => ["id", "percentDone", "downloadDir", "name"],
        "ids" => ids
      }

    case request(method, arguments, s_id) do
      {:ok, %{"arguments" => %{"torrents" => torrents}, "result" => "success"}, s_id} ->
        torrents =
           Map.new(torrents, fn t -> {t["id"], Torrent.new(t)} end)
        {torrents, s_id}

      _ ->
        {%{}, s_id}
    end
  end
end

defmodule Augur.Transmission do
  use GenServer
  require Logger

  alias HTTPoison.Response
  alias Augur.Torrent

  @url "http://192.168.5.115:9091/transmission/rpc"

  ## Client

  @doc """
  Start Link
  """
  def start_link(name \\ nil) do
    GenServer.start_link(__MODULE__, :ok, [name: name])
  end

  @doc """
  Adds multiple torrent to transmission
  """
  def add_torrents(torrents) do
    Enum.map(torrents, &add_torrent/1)
  end

  @doc """
  Adds a torrent to transmission
  """
  def add_torrent(%Torrent{} = torrent) do
    GenServer.cast(Augur.Transmission, {:add_torrent, torrent})
  end

  @doc """
  Creates torrents using given paramaters and then adds it
  """
  def add_torrent(ep_id, downloadDir, url) do
    torrent =
      %Torrent{
        ep_id: ep_id,
        downloadDir: downloadDir,
        url: url
      }

    GenServer.cast(Augur.Transmission, {:add_torrent, torrent})
  end
  @doc """
  Polls status of torrents from list of ids
  """
  def poll(ids) do
    GenServer.cast(Augur.Transmission, {:poll, ids})
  end

  ## Server Callbacks

  @doc """
  Gets a session_id from transmission rpc
  """
  def init(:ok) do
    {:ok, _, s_id} = request("session-get", %{}, "0")
    {:ok, s_id}
  end

  @doc """
  Adds torrent to transmission
  """
  def handle_cast({:add_torrent, torrent}, s_id) do
    method = "torrent-add"
    path = Application.get_env(:animu, :input_root)
    arguments =
      %{"filename"     => torrent.url,
        "download-dir" => Path.join(path, torrent.downloadDir),
        "paused"       => false,
      }

    case request(method, arguments, s_id) do
      {:ok, %{"arguments" => %{"torrent-added" => torrent_info}}, s_id} ->
        id = torrent_info["id"]
        torrent = %Torrent{torrent | id: id}
        Augur.cache_torrents(%{id => torrent})
        Logger.info("Added torrent: #{inspect torrent}")
        {:noreply, s_id}
      {:ok, %{"arguments" => %{"torrent-duplicate" => torrent_info}}, s_id} ->
        id = torrent_info["id"]
        torrent = %Torrent{torrent | id: id}
        Augur.cache_torrents(%{id => torrent})
        Logger.info("Added duplicate torrent: #{inspect torrent}")
        {:noreply, s_id}
      reply ->
        Logger.error("Failed to add torrent due to: #{reply}")
        {:noreply, s_id}
    end
  end

  @doc """
  Polls the status of torrents from a list of ids
  """
  def handle_cast({:poll, ids}, s_id) do
    {torrents, s_id} = poll(ids, s_id)
    Augur.cache_torrents(torrents)
    {:noreply, s_id}
  end

  ## Helpers

  # Helper function that keeps session_id up to date and sends requests to
  # Transmission's RPC API.
  defp request(method, arguments, session_id) do
    options = [recv_timeout: 20 * 1000]

    headers = [{"X-Transmission-Session-Id", session_id},
               {"Accept", "application/json; charset=utf-8"}]

    body =
      Poison.encode!(%{method: method, arguments: arguments})

    case HTTPoison.post(@url, body, headers, options) do
      {:ok, %Response{status_code: 200, body: body}} ->
        {:ok, Poison.decode!(body), session_id}

      {:ok, %Response{status_code: 409, headers: headers}} ->
        session_id = Map.new(headers)["X-Transmission-Session-Id"]
        request(method, arguments, session_id)

      {:error, %HTTPoison.Error{reason: reason}} ->
        Logger.error "Transmission request failed due to #{reason} error"
        {:error, session_id}

      _ ->
        Logger.error "Transmission request failed due to unkown reasons"
        {:error, session_id}
    end
  end

  # Polls status of tracked torrents
  defp poll([], s_id), do: {%{}, s_id}
  defp poll(ids, s_id) do
    method = "torrent-get"
    arguments =
      %{"fields" => ["id", "percentDone", "downloadDir", "name"],
        "ids" => ids
      }

    case request(method, arguments, s_id) do
      {:ok, %{"arguments" => %{"torrents" => torrents}, "result" => "success"}, s_id} ->
        torrents =
           Map.new(torrents, fn t -> {t["id"], Torrent.new(t)} end)
        {torrents, s_id}

      _ ->
        {%{}, s_id}
    end
  end
end


Fix warnings in Inspect impl

Fix warnings in Inspect impl

import Kernel, except: [inspect: 1]
import Inspect.Algebra

defprotocol Inspect do
  @moduledoc """
  The `Inspect` protocol is responsible for converting any Elixir
  data structure into an algebra document. This document is then
  formatted, either in pretty printing format or a regular one.

  The `inspect/2` function receives the entity to be inspected
  followed by the inspecting options, represented by the struct
  `Inspect.Opts`.

  Inspection is done using the functions available in `Inspect.Algebra`.

  ## Examples

  Many times, inspecting a structure can be implemented in function
  of existing entities. For example, here is `MapSet`'s `inspect`
  implementation:

      defimpl Inspect, for: MapSet do
        import Inspect.Algebra

        def inspect(dict, opts) do
          concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
        end
      end

  The `concat/1` function comes from `Inspect.Algebra` and it
  concatenates algebra documents together. In the example above,
  it is concatenating the string `"MapSet<"` (all strings are
  valid algebra documents that keep their formatting when pretty
  printed), the document returned by `Inspect.Algebra.to_doc/2` and the
  other string `">"`.

  Since regular strings are valid entities in an algebra document,
  an implementation of inspect may simply return a string,
  although that will devoid it of any pretty-printing.

  ## Error handling

  In case there is an error while your structure is being inspected,
  Elixir will raise an `ArgumentError` error and will automatically fall back
  to a raw representation for printing the structure.

  You can however access the underlying error by invoking the Inspect
  implementation directly. For example, to test Inspect.MapSet above,
  you can invoke it as:

      Inspect.MapSet.inspect(MapSet.new, %Inspect.Opts{})

  """

  # Handle structs in Any
  @fallback_to_any true

  def inspect(term, opts)
end

defimpl Inspect, for: Atom do
  require Macro

  def inspect(atom, opts) do
    color(inspect(atom), color_key(atom), opts)
  end

  defp color_key(atom) when is_boolean(atom), do: :boolean
  defp color_key(nil), do: :nil
  defp color_key(_), do: :atom

  def inspect(atom) when is_nil(atom) or is_boolean(atom) do
    Atom.to_string(atom)
  end

  def inspect(atom) do
    binary = Atom.to_string(atom)

    case Macro.classify_identifier(binary) do
      :alias ->
        case binary do
          binary when binary in ["Elixir", "Elixir.Elixir"] ->
            binary
          "Elixir.Elixir." <> _rest ->
            binary
          "Elixir." <> rest ->
            rest
        end
      type when type in [:callable, :not_callable] ->
        ":" <> binary
      :other ->
        {escaped, _} = Inspect.BitString.escape(binary, ?")
        IO.iodata_to_binary [?:, ?", escaped, ?"]
    end
  end
end

defimpl Inspect, for: BitString do
  def inspect(term, opts) when is_binary(term) do
    %Inspect.Opts{binaries: bins, base: base, printable_limit: printable_limit} = opts

    if base == :decimal and
       (bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
      inspected =
        case escape(term, ?", printable_limit) do
          {escaped, ""} -> [?", escaped, ?"]
          {escaped, _} -> [?", escaped, ?", " <> ..."]
        end
      color(IO.iodata_to_binary(inspected), :string, opts)
    else
      inspect_bitstring(term, opts)
    end
  end

  def inspect(term, opts) do
    inspect_bitstring(term, opts)
  end

  ## Escaping

  @doc false
  def escape(other, char) do
    escape(other, char, :infinity, [])
  end

  @doc false
  def escape(other, char, count) do
    escape(other, char, count, [])
  end

  defp escape(<<_, _::binary>> = binary, _char, 0, acc) do
    {acc, binary}
  end
  defp escape(<<char, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | [?\\, char]])
  end
  defp escape(<<?#, ?{, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\\#{'])
  end
  defp escape(<<?\a, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\a'])
  end
  defp escape(<<?\b, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\b'])
  end
  defp escape(<<?\d, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\d'])
  end
  defp escape(<<?\e, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\e'])
  end
  defp escape(<<?\f, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\f'])
  end
  defp escape(<<?\n, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\n'])
  end
  defp escape(<<?\r, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\r'])
  end
  defp escape(<<?\\, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\\\'])
  end
  defp escape(<<?\t, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\t'])
  end
  defp escape(<<?\v, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\v'])
  end
  defp escape(<<h::utf8, t::binary>>, char, count, acc)
       when h in 0x20..0x7E
       when h in 0xA0..0xD7FF
       when h in 0xE000..0xFFFD
       when h in 0x10000..0x10FFFF do
    escape(t, char, decrement(count), [acc | <<h::utf8>>])
  end
  defp escape(<<h, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | escape_char(h)])
  end
  defp escape(<<>>, _char, _count, acc) do
    {acc, <<>>}
  end

  @doc false
  # Also used by Regex
  def escape_char(0) do
    '\\0'
  end

  def escape_char(char) when char < 0x100 do
    <<a::4, b::4>> = <<char::8>>
    ['\\x', to_hex(a), to_hex(b)]
  end

  def escape_char(char) when char < 0x10000 do
    <<a::4, b::4, c::4, d::4>> = <<char::16>>
    ['\\x{', to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}]
  end

  def escape_char(char) when char < 0x1000000 do
    <<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
    ['\\x{', to_hex(a), to_hex(b), to_hex(c),
             to_hex(d), to_hex(e), to_hex(f), ?}]
  end

  defp to_hex(c) when c in 0..9, do: ?0 + c
  defp to_hex(c) when c in 10..15, do: ?A + c - 10

  ## Bitstrings

  defp inspect_bitstring("", opts) do
    color("<<>>", :binary, opts)
  end

  defp inspect_bitstring(bitstring, opts) do
    left = color("<<", :binary, opts)
    right = color(">>", :binary, opts)
    nest surround(left, each_bit(bitstring, opts.limit, opts), right), 1
  end

  defp each_bit(_, 0, _) do
    "..."
  end

  defp each_bit(<<>>, _counter, _opts) do
    :doc_nil
  end

  defp each_bit(<<h::8>>, _counter, opts) do
    Inspect.Integer.inspect(h, opts)
  end

  defp each_bit(<<h, t::bitstring>>, counter, opts) do
    glue(concat(Inspect.Integer.inspect(h, opts), ","),
         each_bit(t, decrement(counter), opts))
  end

  defp each_bit(bitstring, _counter, opts) do
    size = bit_size(bitstring)
    <<h::size(size)>> = bitstring
    Inspect.Integer.inspect(h, opts) <> "::size(" <> Integer.to_string(size) <> ")"
  end

  @compile {:inline, decrement: 1}
  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1
end

defimpl Inspect, for: List do
  def inspect([], opts) do
    color("[]", :list, opts)
  end

  # TODO: Remove :char_list and :as_char_lists handling in 2.0
  def inspect(term, opts) do
    %Inspect.Opts{charlists: lists, char_lists: lists_deprecated, printable_limit: printable_limit} = opts
    lists =
      if lists == :infer and lists_deprecated != :infer do
        case lists_deprecated do
          :as_char_lists ->
            IO.warn "the :char_lists inspect option and its :as_char_lists " <>
              "value are deprecated, use the :charlists option and its " <>
              ":as_charlists value instead"
            :as_charlists
          _ ->
            IO.warn "the :char_lists inspect option is deprecated, use :charlists instead"
            lists_deprecated
        end
      else
        lists
      end

    open = color("[", :list, opts)
    sep = color(",", :list, opts)
    close = color("]", :list, opts)

    cond do
      lists == :as_charlists or (lists == :infer and printable?(term, printable_limit)) ->
        inspected =
          case Inspect.BitString.escape(IO.chardata_to_string(term), ?', printable_limit) do
            {escaped, ""} -> [?', escaped, ?']
            {escaped, _} -> [?', escaped, ?', " ++ ..."]
          end
        IO.iodata_to_binary inspected
      keyword?(term) ->
        surround_many(open, term, close, opts, &keyword/2, sep)
      true ->
        surround_many(open, term, close, opts, &to_doc/2, sep)
    end
  end

  @doc false
  def keyword({key, value}, opts) do
    key = color(key_to_binary(key) <> ": ", :atom, opts)
    concat(key, to_doc(value, opts))
  end

  @doc false
  def keyword?([{key, _value} | rest]) when is_atom(key) do
    case Atom.to_charlist(key) do
      'Elixir.' ++ _ -> false
      _ -> keyword?(rest)
    end
  end

  def keyword?([]),     do: true
  def keyword?(_other), do: false

  @doc false
  def printable?(list), do: printable?(list, :infinity)

  @doc false
  def printable?(_, 0), do: true
  def printable?([char | rest], counter) when char in 32..126, do: printable?(rest, decrement(counter))
  def printable?([?\n | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\r | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\t | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\v | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\b | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\f | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\e | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\a | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([], _counter), do: true
  def printable?(_, _counter), do: false

  @compile {:inline, decrement: 1}
  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1

  ## Private

  defp key_to_binary(key) do
    case Inspect.Atom.inspect(key) do
      ":" <> right -> right
      other -> other
    end
  end
end

defimpl Inspect, for: Tuple do
  def inspect(tuple, opts) do
    open = color("{", :tuple, opts)
    sep = color(",", :tuple, opts)
    close = color("}", :tuple, opts)
    surround_many(open, Tuple.to_list(tuple), close, opts, &to_doc/2, sep)
  end
end

defimpl Inspect, for: Map do
  def inspect(map, opts) do
    nest inspect(map, "", opts), 1
  end

  def inspect(map, name, opts) do
    map = :maps.to_list(map)
    open = color("%" <> name <> "{", :map, opts)
    sep = color(",", :map, opts)
    close = color("}", :map, opts)
    surround_many(open, map, close, opts, traverse_fun(map), sep)
  end

  defp traverse_fun(list) do
    if Inspect.List.keyword?(list) do
      &Inspect.List.keyword/2
    else
      &to_map/2
    end
  end

  defp to_map({key, value}, opts) do
    concat(
      concat(to_doc(key, opts), " => "),
      to_doc(value, opts)
    )
  end
end

defimpl Inspect, for: Integer do
  def inspect(term, %Inspect.Opts{base: base} = opts) do
    inspected = Integer.to_string(term, base_to_value(base)) |> prepend_prefix(base)
    color(inspected, :number, opts)
  end

  defp base_to_value(base) do
    case base do
      :binary  -> 2
      :decimal -> 10
      :octal   -> 8
      :hex     -> 16
    end
  end

  defp prepend_prefix(value, :decimal), do: value
  defp prepend_prefix(value, base) do
    prefix = case base do
      :binary -> "0b"
      :octal  -> "0o"
      :hex    -> "0x"
    end
    prefix <> value
  end
end

defimpl Inspect, for: Float do
  def inspect(term, opts) do
    inspected = IO.iodata_to_binary(:io_lib_format.fwrite_g(term))
    color(inspected, :number, opts)
  end
end

defimpl Inspect, for: Regex do
  def inspect(regex, opts) do
    source = IO.iodata_to_binary(['~r/', escape(regex.source, ?/), ?/, regex.opts])
    color(source, :regex, opts)
  end

  defp escape(bin, term),
    do: escape(bin, [], term)

  defp escape(<<?\\, term>> <> rest, buf, term),
    do: escape(rest, [buf | [?\\, term]], term)

  defp escape(<<term>> <> rest, buf, term),
    do: escape(rest, [buf | [?\\, term]], term)

  # The list of characters is from 'String.printable?' implementation
  # minus characters treated specially by regex: \s, \d, \b, \e

  defp escape(<<?\n>> <> rest, buf, term),
    do: escape(rest, [buf | '\\n'], term)

  defp escape(<<?\r>> <> rest, buf, term),
    do: escape(rest, [buf | '\\r'], term)

  defp escape(<<?\t>> <> rest, buf, term),
    do: escape(rest, [buf | '\\t'], term)

  defp escape(<<?\v>> <> rest, buf, term),
    do: escape(rest, [buf | '\\v'], term)

  defp escape(<<?\f>> <> rest, buf, term),
    do: escape(rest, [buf | '\\f'], term)

  defp escape(<<?\a>> <> rest, buf, term),
    do: escape(rest, [buf | '\\a'], term)

  defp escape(<<char::utf8, rest::binary>>, buf, term)
       when char in 0x20..0x7E
       when char in 0xA0..0xD7FF
       when char in 0xE000..0xFFFD
       when char in 0x10000..0x10FFFF,
    do: escape(rest, [buf | <<char::utf8>>], term)

  defp escape(<<char, rest::binary>>, buf, term),
    do: escape(rest, [buf | Inspect.BitString.escape_char(char)], term)

  defp escape(<<>>, buf, _), do: buf
end

defimpl Inspect, for: Function do
  def inspect(function, _opts) do
    fun_info = :erlang.fun_info(function)
    mod = fun_info[:module]
    name = fun_info[:name]

    if fun_info[:type] == :external and fun_info[:env] == [] do
      "&#{Inspect.Atom.inspect(mod)}.#{escape_name(name)}/#{fun_info[:arity]}"
    else
      case Atom.to_charlist(mod) do
        'elixir_compiler_' ++ _ ->
          if function_exported?(mod, :__RELATIVE__, 0) do
            "#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
          else
            default_inspect(mod, fun_info)
          end
        _ ->
          default_inspect(mod, fun_info)
      end
    end
  end

  def escape_name(name) when is_atom(name) do
    escape_name(Atom.to_string(name))
  end

  def escape_name(name) when is_binary(name) do
    case Macro.classify_identifier(name) do
      :callable ->
        name
      type when type in [:not_callable, :alias] ->
        "\"" <> name <> "\""
      :other ->
        {escaped, _} = Inspect.BitString.escape(name, ?")
        IO.iodata_to_binary [?", escaped, ?"]
    end
  end

  # Example of this format: -func/arity-fun-count-
  def extract_anonymous_fun_parent("-" <> rest) do
    # We use :re instead of String.split/3 because we want to keep the "/"s and
    # "-"s (this is what the "(" and ")" in the regex do). We want to keep them
    # because we want to rebuild part of this split list (the function name)
    # later on.
    result =
      rest
      |> :re.split("([/-])")
      |> Enum.reject(&(&1 == ""))
      |> Enum.reverse()

    case result do
      ["-", _count, "-", _inner, "-", arity, "/" | reversed_function] ->
        {Enum.join(Enum.reverse(reversed_function)), arity}
      _other ->
        :error
    end
  end

  def extract_anonymous_fun_parent(other) when is_binary(other), do: :error

  defp default_inspect(mod, fun_info) do
    "#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
      "#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
  end

  defp extract_name([]) do
    ""
  end

  defp extract_name(name) do
    name = Atom.to_string(name)
    case extract_anonymous_fun_parent(name) do
      {name, arity} ->
        "." <> escape_name(name) <> "/" <> arity
      :error ->
        "." <> escape_name(name)
    end
  end

  defp uniq(fun_info) do
    Integer.to_string(fun_info[:new_index]) <> "." <>
      Integer.to_string(fun_info[:uniq])
  end
end

defimpl Inspect, for: PID do
  def inspect(pid, _opts) do
    "#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
  end
end

defimpl Inspect, for: Port do
  def inspect(port, _opts) do
    IO.iodata_to_binary(:erlang.port_to_list(port))
  end
end

defimpl Inspect, for: Reference do
  def inspect(ref, _opts) do
    '#Ref' ++ rest = :erlang.ref_to_list(ref)
    "#Reference" <> IO.iodata_to_binary(rest)
  end
end

defimpl Inspect, for: Any do
  def inspect(%{__struct__: struct} = map, opts) do
    try do
      struct.__struct__
    rescue
      _ -> Inspect.Map.inspect(map, opts)
    else
      dunder ->
        if :maps.keys(dunder) == :maps.keys(map) do
          pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
          colorless_opts = %{opts | syntax_colors: []}
          Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, colorless_opts), opts)
        else
          Inspect.Map.inspect(map, opts)
        end
    end
  end
end

import Kernel, except: [inspect: 1]
import Inspect.Algebra

defprotocol Inspect do
  @moduledoc """
  The `Inspect` protocol is responsible for converting any Elixir
  data structure into an algebra document. This document is then
  formatted, either in pretty printing format or a regular one.

  The `inspect/2` function receives the entity to be inspected
  followed by the inspecting options, represented by the struct
  `Inspect.Opts`.

  Inspection is done using the functions available in `Inspect.Algebra`.

  ## Examples

  Many times, inspecting a structure can be implemented in function
  of existing entities. For example, here is `MapSet`'s `inspect`
  implementation:

      defimpl Inspect, for: MapSet do
        import Inspect.Algebra

        def inspect(dict, opts) do
          concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
        end
      end

  The `concat/1` function comes from `Inspect.Algebra` and it
  concatenates algebra documents together. In the example above,
  it is concatenating the string `"MapSet<"` (all strings are
  valid algebra documents that keep their formatting when pretty
  printed), the document returned by `Inspect.Algebra.to_doc/2` and the
  other string `">"`.

  Since regular strings are valid entities in an algebra document,
  an implementation of inspect may simply return a string,
  although that will devoid it of any pretty-printing.

  ## Error handling

  In case there is an error while your structure is being inspected,
  Elixir will raise an `ArgumentError` error and will automatically fall back
  to a raw representation for printing the structure.

  You can however access the underlying error by invoking the Inspect
  implementation directly. For example, to test Inspect.MapSet above,
  you can invoke it as:

      Inspect.MapSet.inspect(MapSet.new, %Inspect.Opts{})

  """

  # Handle structs in Any
  @fallback_to_any true

  def inspect(term, opts)
end

defimpl Inspect, for: Atom do
  require Macro

  def inspect(atom, opts) do
    color(inspect(atom), color_key(atom), opts)
  end

  defp color_key(atom) when is_boolean(atom), do: :boolean
  defp color_key(nil), do: :nil
  defp color_key(_), do: :atom

  def inspect(atom) when is_nil(atom) or is_boolean(atom) do
    Atom.to_string(atom)
  end

  def inspect(atom) do
    binary = Atom.to_string(atom)

    case Macro.classify_identifier(binary) do
      :alias ->
        case binary do
          binary when binary in ["Elixir", "Elixir.Elixir"] ->
            binary
          "Elixir.Elixir." <> _rest ->
            binary
          "Elixir." <> rest ->
            rest
        end
      type when type in [:callable, :not_callable] ->
        ":" <> binary
      :other ->
        {escaped, _} = Inspect.BitString.escape(binary, ?")
        IO.iodata_to_binary [?:, ?", escaped, ?"]
    end
  end
end

defimpl Inspect, for: BitString do
  def inspect(term, opts) when is_binary(term) do
    %Inspect.Opts{binaries: bins, base: base, printable_limit: printable_limit} = opts

    if base == :decimal and
       (bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
      inspected =
        case escape(term, ?", printable_limit) do
          {escaped, ""} -> [?", escaped, ?"]
          {escaped, _} -> [?", escaped, ?", " <> ..."]
        end
      color(IO.iodata_to_binary(inspected), :string, opts)
    else
      inspect_bitstring(term, opts)
    end
  end

  def inspect(term, opts) do
    inspect_bitstring(term, opts)
  end

  ## Escaping

  @doc false
  def escape(other, char) do
    escape(other, char, :infinity, [])
  end

  @doc false
  def escape(other, char, count) do
    escape(other, char, count, [])
  end

  defp escape(<<_, _::binary>> = binary, _char, 0, acc) do
    {acc, binary}
  end
  defp escape(<<char, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | [?\\, char]])
  end
  defp escape(<<?#, ?{, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\\#{'])
  end
  defp escape(<<?\a, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\a'])
  end
  defp escape(<<?\b, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\b'])
  end
  defp escape(<<?\d, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\d'])
  end
  defp escape(<<?\e, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\e'])
  end
  defp escape(<<?\f, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\f'])
  end
  defp escape(<<?\n, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\n'])
  end
  defp escape(<<?\r, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\r'])
  end
  defp escape(<<?\\, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\\\'])
  end
  defp escape(<<?\t, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\t'])
  end
  defp escape(<<?\v, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | '\\v'])
  end
  defp escape(<<h::utf8, t::binary>>, char, count, acc)
       when h in 0x20..0x7E
       when h in 0xA0..0xD7FF
       when h in 0xE000..0xFFFD
       when h in 0x10000..0x10FFFF do
    escape(t, char, decrement(count), [acc | <<h::utf8>>])
  end
  defp escape(<<h, t::binary>>, char, count, acc) do
    escape(t, char, decrement(count), [acc | escape_char(h)])
  end
  defp escape(<<>>, _char, _count, acc) do
    {acc, <<>>}
  end

  @doc false
  # Also used by Regex
  def escape_char(0) do
    '\\0'
  end

  def escape_char(char) when char < 0x100 do
    <<a::4, b::4>> = <<char::8>>
    ['\\x', to_hex(a), to_hex(b)]
  end

  def escape_char(char) when char < 0x10000 do
    <<a::4, b::4, c::4, d::4>> = <<char::16>>
    ['\\x{', to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}]
  end

  def escape_char(char) when char < 0x1000000 do
    <<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
    ['\\x{', to_hex(a), to_hex(b), to_hex(c),
             to_hex(d), to_hex(e), to_hex(f), ?}]
  end

  defp to_hex(c) when c in 0..9, do: ?0 + c
  defp to_hex(c) when c in 10..15, do: ?A + c - 10

  ## Bitstrings

  defp inspect_bitstring("", opts) do
    color("<<>>", :binary, opts)
  end

  defp inspect_bitstring(bitstring, opts) do
    left = color("<<", :binary, opts)
    right = color(">>", :binary, opts)
    nest surround(left, each_bit(bitstring, opts.limit, opts), right), 1
  end

  defp each_bit(_, 0, _) do
    "..."
  end

  defp each_bit(<<>>, _counter, _opts) do
    :doc_nil
  end

  defp each_bit(<<h::8>>, _counter, opts) do
    Inspect.Integer.inspect(h, opts)
  end

  defp each_bit(<<h, t::bitstring>>, counter, opts) do
    glue(concat(Inspect.Integer.inspect(h, opts), ","),
         each_bit(t, decrement(counter), opts))
  end

  defp each_bit(bitstring, _counter, opts) do
    size = bit_size(bitstring)
    <<h::size(size)>> = bitstring
    Inspect.Integer.inspect(h, opts) <> "::size(" <> Integer.to_string(size) <> ")"
  end

  @compile {:inline, decrement: 1}
  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1
end

defimpl Inspect, for: List do
  def inspect([], opts) do
    color("[]", :list, opts)
  end

  # TODO: Remove :char_list and :as_char_lists handling in 2.0
  def inspect(term, opts) do
    %Inspect.Opts{charlists: lists, char_lists: lists_deprecated, printable_limit: printable_limit} = opts
    lists =
      if lists == :infer and lists_deprecated != :infer do
        case lists_deprecated do
          :as_char_lists ->
            IO.warn "the :char_lists inspect option and its :as_char_lists " <>
              "value are deprecated, use the :charlists option and its " <>
              ":as_charlists value instead"
            :as_charlists
          _ ->
            IO.warn "the :char_lists inspect option is deprecated, use :charlists instead"
            lists_deprecated
        end
      else
        lists
      end

    open = color("[", :list, opts)
    sep = color(",", :list, opts)
    close = color("]", :list, opts)

    cond do
      lists == :as_charlists or (lists == :infer and printable?(term, printable_limit)) ->
        inspected =
          case Inspect.BitString.escape(IO.chardata_to_string(term), ?', printable_limit) do
            {escaped, ""} -> [?', escaped, ?']
            {escaped, _} -> [?', escaped, ?', " ++ ..."]
          end
        IO.iodata_to_binary inspected
      keyword?(term) ->
        surround_many(open, term, close, opts, &keyword/2, sep)
      true ->
        surround_many(open, term, close, opts, &to_doc/2, sep)
    end
  end

  @doc false
  def keyword({key, value}, opts) do
    key = color(key_to_binary(key) <> ": ", :atom, opts)
    concat(key, to_doc(value, opts))
  end

  @doc false
  def keyword?([{key, _value} | rest]) when is_atom(key) do
    case Atom.to_charlist(key) do
      'Elixir.' ++ _ -> false
      _ -> keyword?(rest)
    end
  end

  def keyword?([]),     do: true
  def keyword?(_other), do: false

  @doc false
  def printable?(list), do: printable?(list, :infinity)

  @doc false
  def printable?(_, 0), do: true
  def printable?([char | rest], counter) when char in 32..126, do: printable?(rest, decrement(counter))
  def printable?([?\n | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\r | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\t | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\v | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\b | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\f | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\e | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([?\a | rest], counter), do: printable?(rest, decrement(counter))
  def printable?([], counter), do: true
  def printable?(_, counter), do: false

  @compile {:inline, decrement: 1}
  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1

  ## Private

  defp key_to_binary(key) do
    case Inspect.Atom.inspect(key) do
      ":" <> right -> right
      other -> other
    end
  end
end

defimpl Inspect, for: Tuple do
  def inspect(tuple, opts) do
    open = color("{", :tuple, opts)
    sep = color(",", :tuple, opts)
    close = color("}", :tuple, opts)
    surround_many(open, Tuple.to_list(tuple), close, opts, &to_doc/2, sep)
  end
end

defimpl Inspect, for: Map do
  def inspect(map, opts) do
    nest inspect(map, "", opts), 1
  end

  def inspect(map, name, opts) do
    map = :maps.to_list(map)
    open = color("%" <> name <> "{", :map, opts)
    sep = color(",", :map, opts)
    close = color("}", :map, opts)
    surround_many(open, map, close, opts, traverse_fun(map), sep)
  end

  defp traverse_fun(list) do
    if Inspect.List.keyword?(list) do
      &Inspect.List.keyword/2
    else
      &to_map/2
    end
  end

  defp to_map({key, value}, opts) do
    concat(
      concat(to_doc(key, opts), " => "),
      to_doc(value, opts)
    )
  end
end

defimpl Inspect, for: Integer do
  def inspect(term, %Inspect.Opts{base: base} = opts) do
    inspected = Integer.to_string(term, base_to_value(base)) |> prepend_prefix(base)
    color(inspected, :number, opts)
  end

  defp base_to_value(base) do
    case base do
      :binary  -> 2
      :decimal -> 10
      :octal   -> 8
      :hex     -> 16
    end
  end

  defp prepend_prefix(value, :decimal), do: value
  defp prepend_prefix(value, base) do
    prefix = case base do
      :binary -> "0b"
      :octal  -> "0o"
      :hex    -> "0x"
    end
    prefix <> value
  end
end

defimpl Inspect, for: Float do
  def inspect(term, opts) do
    inspected = IO.iodata_to_binary(:io_lib_format.fwrite_g(term))
    color(inspected, :number, opts)
  end
end

defimpl Inspect, for: Regex do
  def inspect(regex, opts) do
    source = IO.iodata_to_binary(['~r/', escape(regex.source, ?/), ?/, regex.opts])
    color(source, :regex, opts)
  end

  defp escape(bin, term),
    do: escape(bin, [], term)

  defp escape(<<?\\, term>> <> rest, buf, term),
    do: escape(rest, [buf | [?\\, term]], term)

  defp escape(<<term>> <> rest, buf, term),
    do: escape(rest, [buf | [?\\, term]], term)

  # The list of characters is from 'String.printable?' implementation
  # minus characters treated specially by regex: \s, \d, \b, \e

  defp escape(<<?\n>> <> rest, buf, term),
    do: escape(rest, [buf | '\\n'], term)

  defp escape(<<?\r>> <> rest, buf, term),
    do: escape(rest, [buf | '\\r'], term)

  defp escape(<<?\t>> <> rest, buf, term),
    do: escape(rest, [buf | '\\t'], term)

  defp escape(<<?\v>> <> rest, buf, term),
    do: escape(rest, [buf | '\\v'], term)

  defp escape(<<?\f>> <> rest, buf, term),
    do: escape(rest, [buf | '\\f'], term)

  defp escape(<<?\a>> <> rest, buf, term),
    do: escape(rest, [buf | '\\a'], term)

  defp escape(<<char::utf8, rest::binary>>, buf, term)
       when char in 0x20..0x7E
       when char in 0xA0..0xD7FF
       when char in 0xE000..0xFFFD
       when char in 0x10000..0x10FFFF,
    do: escape(rest, [buf | <<char::utf8>>], term)

  defp escape(<<char, rest::binary>>, buf, term),
    do: escape(rest, [buf | Inspect.BitString.escape_char(char)], term)

  defp escape(<<>>, buf, _), do: buf
end

defimpl Inspect, for: Function do
  def inspect(function, _opts) do
    fun_info = :erlang.fun_info(function)
    mod = fun_info[:module]
    name = fun_info[:name]

    if fun_info[:type] == :external and fun_info[:env] == [] do
      "&#{Inspect.Atom.inspect(mod)}.#{escape_name(name)}/#{fun_info[:arity]}"
    else
      case Atom.to_charlist(mod) do
        'elixir_compiler_' ++ _ ->
          if function_exported?(mod, :__RELATIVE__, 0) do
            "#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
          else
            default_inspect(mod, fun_info)
          end
        _ ->
          default_inspect(mod, fun_info)
      end
    end
  end

  def escape_name(name) when is_atom(name) do
    escape_name(Atom.to_string(name))
  end

  def escape_name(name) when is_binary(name) do
    case Macro.classify_identifier(name) do
      :callable ->
        name
      type when type in [:not_callable, :alias] ->
        "\"" <> name <> "\""
      :other ->
        {escaped, _} = Inspect.BitString.escape(name, ?")
        IO.iodata_to_binary [?", escaped, ?"]
    end
  end

  # Example of this format: -func/arity-fun-count-
  def extract_anonymous_fun_parent("-" <> rest) do
    # We use :re instead of String.split/3 because we want to keep the "/"s and
    # "-"s (this is what the "(" and ")" in the regex do). We want to keep them
    # because we want to rebuild part of this split list (the function name)
    # later on.
    result =
      rest
      |> :re.split("([/-])")
      |> Enum.reject(&(&1 == ""))
      |> Enum.reverse()

    case result do
      ["-", _count, "-", _inner, "-", arity, "/" | reversed_function] ->
        {Enum.join(Enum.reverse(reversed_function)), arity}
      _other ->
        :error
    end
  end

  def extract_anonymous_fun_parent(other) when is_binary(other), do: :error

  defp default_inspect(mod, fun_info) do
    "#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
      "#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
  end

  defp extract_name([]) do
    ""
  end

  defp extract_name(name) do
    name = Atom.to_string(name)
    case extract_anonymous_fun_parent(name) do
      {name, arity} ->
        "." <> escape_name(name) <> "/" <> arity
      :error ->
        "." <> escape_name(name)
    end
  end

  defp uniq(fun_info) do
    Integer.to_string(fun_info[:new_index]) <> "." <>
      Integer.to_string(fun_info[:uniq])
  end
end

defimpl Inspect, for: PID do
  def inspect(pid, _opts) do
    "#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
  end
end

defimpl Inspect, for: Port do
  def inspect(port, _opts) do
    IO.iodata_to_binary(:erlang.port_to_list(port))
  end
end

defimpl Inspect, for: Reference do
  def inspect(ref, _opts) do
    '#Ref' ++ rest = :erlang.ref_to_list(ref)
    "#Reference" <> IO.iodata_to_binary(rest)
  end
end

defimpl Inspect, for: Any do
  def inspect(%{__struct__: struct} = map, opts) do
    try do
      struct.__struct__
    rescue
      _ -> Inspect.Map.inspect(map, opts)
    else
      dunder ->
        if :maps.keys(dunder) == :maps.keys(map) do
          pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
          colorless_opts = %{opts | syntax_colors: []}
          Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, colorless_opts), opts)
        else
          Inspect.Map.inspect(map, opts)
        end
    end
  end
end