parser

parser

defmodule Pan.Parser.Download do
  require Logger

  def check_for_rss(feed_xml) do
    unless String.contains?(feed_xml, "<rss") do
      {:error, "This is not an rss feed!"}
    else
      {:ok, feed_xml}
    end
  end

  def download(url, option \\ nil) do
    case get(url, option) do
      {:ok, %HTTPoison.Response{status_code: 200, body: feed_xml}} -> check_for_rss(feed_xml)
      {:ok, %HTTPoison.Response{status_code: 203, body: feed_xml}} -> check_for_rss(feed_xml)
      {:ok, %HTTPoison.Response{status_code: 206, body: feed_xml}} -> check_for_rss(feed_xml)

      {:ok, %HTTPoison.Response{status_code: 401}} -> {:error, "401: unauthorized"}
      {:ok, %HTTPoison.Response{status_code: 404}} -> {:error, "404: feed not found"}
      {:ok, %HTTPoison.Response{status_code: 406}} -> {:error, "406: not acceptable"}
      {:ok, %HTTPoison.Response{status_code: 410}} -> {:error, "410: Gone"}
      {:ok, %HTTPoison.Response{status_code: 422}} -> {:error, "422: Unprocessible entity"}
      {:ok, %HTTPoison.Response{status_code: 429}} -> {:error, "429: To many requests"}
      {:ok, %HTTPoison.Response{status_code: 479}} -> {:error, "479: Not a standard status code"}

      {:ok, %HTTPoison.Response{status_code: 500}} -> {:error, "500: internal server error"}
      {:ok, %HTTPoison.Response{status_code: 502}} -> {:error, "502: bad gateway"}
      {:ok, %HTTPoison.Response{status_code: 503}} -> {:error, "503: service unavailable"}
      {:ok, %HTTPoison.Response{status_code: 504}} -> {:error, "504: gateway time-out"}
      {:ok, %HTTPoison.Response{status_code: 508}} -> {:error, "508: loop detected"}
      {:ok, %HTTPoison.Response{status_code: 509}} -> {:error, "509: Bandwidth Limit Exceeded"}

      {:ok, %HTTPoison.Response{status_code: 301, headers: headers}} -> redirect(url, headers)
      {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} -> redirect(url, headers)
      {:ok, %HTTPoison.Response{status_code: 303, headers: headers}} -> redirect(url, headers)
      {:ok, %HTTPoison.Response{status_code: 307, headers: headers}} -> redirect(url, headers)

      {:ok, %HTTPoison.Response{status_code: 403}} ->
        if option == "no_headers" do
          {:error, "403: forbidden"}
        else
         download(url, "no_headers")
        end

      {:ok, %HTTPoison.Response{status_code: code}} ->
        Logger.error "status_code unknown #{inspect code}"

      {:error, %HTTPoison.Error{id: nil, reason: :timeout}} -> {:error, "Timeout"}
      {:error, %HTTPoison.Error{id: nil, reason: :ehostunreach}} -> {:error, "Host unreachable"}
      {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}} -> {:error, "Domain not resolveable"}
      {:error, %HTTPoison.Error{id: nil, reason: :connect_timeout}} -> {:error, "Connection timeout"}
      {:error, %HTTPoison.Error{id: nil, reason: :econnrefused}} -> {:error, "Connection refused"}

      {:error, %HTTPoison.Error{id: nil, reason: :closed}} ->
        try_without_tls_set(url, option)
      {:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'handshake failure'}}} ->
        try_without_tls_set(url, option)
      {:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'protocol version'}}} ->
        try_without_tls_set(url, option)

      {:error, reason} -> {:error, reason}
    end
  end

  def try_without_tls_set(url, option) do
    case option do
      "unset_tls_version" -> {:error, "Does not work without specifying tls version as well!"}
                        _ -> download(url, "unset_tls_version")
    end
  end

  def redirect(url, headers) do
    header_map = Enum.into(headers, %{})
    redirect_target =
      cond do
        Map.has_key?(header_map, "Location") ->
          Map.fetch!(header_map, "Location")
        true ->
          Map.fetch!(header_map, "location")
      end

    redirect_target =
      case String.starts_with?(redirect_target, "http") do
        true -> redirect_target
        false ->
          domain = String.split(url, "/", parts: 3, trim: true)
                   |> Enum.drop(-1)
                   |> Enum.join("//")
          domain <> redirect_target
      end

    if redirect_target == url do
      {:error, "redirects to itself"}
    else
      {:redirect, redirect_target}
    end
  end

  def get(url, option \\ nil) do
    headers = case option do
      "no_headers" ->
        []
      _ ->
        ["User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0"]
    end

    options = case option do
      "unset_tls_version" ->
        [recv_timeout: 15_000, timeout: 15_000, hackney: [:insecure],
         ssl: [{:versions, [:'tlsv1.2']}]]
      _ ->
        [recv_timeout: 15_000, timeout: 15_000, hackney: [:insecure]]
    end
    HTTPoison.get(url, headers, options)
  end
end

defmodule Pan.Parser.Download do
  require Logger

  def check_for_rss(feed_xml) do
    unless String.contains?(feed_xml, "<rss") do
      {:error, "This is not an rss feed!"}
    else
      {:ok, feed_xml}
    end
  end

  def download(url, option \\ nil) do
    case get(url, option) do
      {:ok, %HTTPoison.Response{status_code: 200, body: feed_xml}} -> check_for_rss(feed_xml)
      {:ok, %HTTPoison.Response{status_code: 203, body: feed_xml}} -> check_for_rss(feed_xml)
      {:ok, %HTTPoison.Response{status_code: 206, body: feed_xml}} -> check_for_rss(feed_xml)

      {:ok, %HTTPoison.Response{status_code: 401}} -> {:error, "401: unauthorized"}
      {:ok, %HTTPoison.Response{status_code: 404}} -> {:error, "404: feed not found"}
      {:ok, %HTTPoison.Response{status_code: 406}} -> {:error, "406: not acceptable"}
      {:ok, %HTTPoison.Response{status_code: 410}} -> {:error, "410: Gone"}
      {:ok, %HTTPoison.Response{status_code: 422}} -> {:error, "422: Unprocessible entity"}
      {:ok, %HTTPoison.Response{status_code: 429}} -> {:error, "429: To many requests"}
      {:ok, %HTTPoison.Response{status_code: 479}} -> {:error, "479: Not a standard status code"}

      {:ok, %HTTPoison.Response{status_code: 500}} -> {:error, "500: internal server error"}
      {:ok, %HTTPoison.Response{status_code: 502}} -> {:error, "502: bad gateway"}
      {:ok, %HTTPoison.Response{status_code: 503}} -> {:error, "503: service unavailable"}
      {:ok, %HTTPoison.Response{status_code: 504}} -> {:error, "504: gateway time-out"}
      {:ok, %HTTPoison.Response{status_code: 508}} -> {:error, "508: loop detected"}
      {:ok, %HTTPoison.Response{status_code: 509}} -> {:error, "509: Bandwidth Limit Exceeded"}

      {:ok, %HTTPoison.Response{status_code: 301, headers: headers}} -> redirect(url, headers)
      {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} -> redirect(url, headers)
      {:ok, %HTTPoison.Response{status_code: 307, headers: headers}} -> redirect(url, headers)

      {:ok, %HTTPoison.Response{status_code: 403}} ->
        if option == "no_headers" do
          {:error, "403: forbidden"}
        else
         download(url, "no_headers")
        end

      {:ok, %HTTPoison.Response{status_code: code}} ->
        Logger.error "status_code unknown #{inspect code}"

      {:error, %HTTPoison.Error{id: nil, reason: :timeout}} -> {:error, "Timeout"}
      {:error, %HTTPoison.Error{id: nil, reason: :ehostunreach}} -> {:error, "Host unreachable"}
      {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}} -> {:error, "Domain not resolveable"}
      {:error, %HTTPoison.Error{id: nil, reason: :connect_timeout}} -> {:error, "Connection timeout"}
      {:error, %HTTPoison.Error{id: nil, reason: :econnrefused}} -> {:error, "Connection refused"}

      {:error, %HTTPoison.Error{id: nil, reason: :closed}} ->
        try_without_tls_set(url, option)
      {:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'handshake failure'}}} ->
        try_without_tls_set(url, option)
      {:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'protocol version'}}} ->
        try_without_tls_set(url, option)

      {:error, reason} -> {:error, reason}
    end
  end

  def try_without_tls_set(url, option) do
    case option do
      "unset_tls_version" -> {:error, "Does not work without specifying tls version as well!"}
                        _ -> download(url, "unset_tls_version")
    end
  end

  def redirect(url, headers) do
    header_map = Enum.into(headers, %{})
    redirect_target =
      cond do
        Map.has_key?(header_map, "Location") ->
          Map.fetch!(header_map, "Location")
        true ->
          Map.fetch!(header_map, "location")
      end

    redirect_target =
      case String.starts_with?(redirect_target, "http") do
        true -> redirect_target
        false ->
          domain = String.split(url, "/", parts: 3, trim: true)
                   |> Enum.drop(-1)
                   |> Enum.join("//")
          domain <> redirect_target
      end

    if redirect_target == url do
      {:error, "redirects to itself"}
    else
      {:redirect, redirect_target}
    end
  end

  def get(url, option \\ nil) do
    headers = case option do
      "no_headers" ->
        []
      _ ->
        ["User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0"]
    end

    options = case option do
      "unset_tls_version" ->
        [recv_timeout: 15_000, timeout: 15_000, hackney: [:insecure],
         ssl: [{:versions, [:'tlsv1.2']}]]
      _ ->
        [recv_timeout: 15_000, timeout: 15_000, hackney: [:insecure]]
    end
    HTTPoison.get(url, headers, options)
  end
end


dependency updates, HTTPoison refactcoring

defmodule Pan.Parser.Download do
  require Logger
  alias Pan.Parser.Feed
  alias HTTPoison.{Error, Response}

  def check_for_rss(feed_xml) do
    if String.contains?(feed_xml, "<rss") do
      {:ok, feed_xml}
    else
      {:error, "not an rss feed"}
    end
  end

  def download(url, option \\ nil, feed_id \\ nil) do
    error_map = %{
      204 => "204: no content",
      304 => "304: not modified",
      400 => "400: bad request",
      401 => "401: unauthorized",
      402 => "402: payment required",
      404 => "404: feed not found",
      406 => "406: not acceptable",
      408 => "408: request timeout",
      410 => "410: gone",
      416 => "416: range not satisfiable",
      422 => "422: unprocessible entity"
    }

    case get(url, option) do
      {:ok, %Response{status_code: status_code, body: feed_xml}}
      when status_code in [200, 203, 206] ->
        check_for_rss(feed_xml)

      {:ok, %Response{status_code: status_code}}
      when status_code in [204, 304, 400, 401, 402, 404, 406, 408, 410, 416, 422] ->
        {:error, Map.get(error_map, status_code)}

      {:ok, %Response{status_code: 423}} ->
        {:error, "423: locked"}

      {:ok, %Response{status_code: 429}} ->
        {:error, "429: too many requests"}

      {:ok, %Response{status_code: 451}} ->
        {:error, "451: unavailable For legal reasons"}

      {:ok, %Response{status_code: 479}} ->
        {:error, "479: not a standard status code"}

      {:ok, %Response{status_code: 500}} ->
        {:error, "500: internal server error"}

      {:ok, %Response{status_code: 501}} ->
        {:error, "501: not implemented"}

      {:ok, %Response{status_code: 502}} ->
        {:error, "502: bad gateway"}

      {:ok, %Response{status_code: 503}} ->
        {:error, "503: service unavailable"}

      {:ok, %Response{status_code: 504}} ->
        {:error, "504: gateway time-out"}

      {:ok, %Response{status_code: 508}} ->
        {:error, "508: loop detected"}

      {:ok, %Response{status_code: 509}} ->
        {:error, "509: bandwidth limit exceeded"}

      {:ok, %Response{status_code: 520}} ->
        {:error, "520: unknown error"}

      {:ok, %Response{status_code: 521}} ->
        {:error, "521: web server is down"}

      {:ok, %Response{status_code: 523}} ->
        {:error, "523: origin is unreachable"}

      {:ok, %Response{status_code: 526}} ->
        {:error, "526: invalid SSL certificate"}

      {:ok, %Response{status_code: 530}} ->
        {:error, "530: origin DNS error with cloudflare"}

      {:ok, %Response{status_code: 301, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 302, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 303, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 308, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 307}} ->
        {:error, "307: temporary redirect"}

      {:ok, %Response{status_code: 403}} ->
        if option == "no_headers" do
          {:error, "403: forbidden"}
        else
          download(url, "no_headers")
        end

      {:ok, %Response{status_code: code}} ->
        Logger.error("status_code unknown #{inspect(code)}")

      {:error, %Error{id: nil, reason: :timeout}} ->
        {:error, "Timeout"}

      {:error, %Error{id: nil, reason: :ehostunreach}} ->
        {:error, "Host unreachable"}

      {:error, %Error{id: nil, reason: :nxdomain}} ->
        {:error, "Domain not resolveable"}

      {:error, %Error{id: nil, reason: :connect_timeout}} ->
        {:error, "Connection timeout"}

      {:error, %Error{id: nil, reason: :econnrefused}} ->
        {:error, "Connection refused"}

      {:error, %Error{id: nil, reason: :closed}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:closed, _feed_xml}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'handshake failure'}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'protocol version'}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'unrecognised name'}}} ->
        try_without_tls_set(url, option)

      {:error, reason} ->
        {:error, reason}
    end
  end

  def try_without_tls_set(url, option) do
    case option do
      "unset_tls_version" -> {:error, "Does not work without specifying tls version as well!"}
      _ -> download(url, "unset_tls_version")
    end
  end

  def redirect(url, headers, feed_id) do
    header_map = Enum.into(headers, %{})

    redirect_target =
      if Map.has_key?(header_map, "Location") do
        Map.fetch!(header_map, "Location")
      else
        Map.fetch!(header_map, "location")
      end

    Feed.check_for_redirect_loop(url, redirect_target, feed_id)
  end

  def get(url, option) do
    headers =
      if option == "no_headers" do
        []
      else
        ["User-Agent": "Mozilla/5.0 (compatible; Panoptikum; +https://panoptikum.io/)"]
      end

    ssl_versions =
      if option == "unset_tls_version" do
        [:"tlsv1.2"]
      else
        [:"tlsv1.2", :"tlsv1.1", :tlsv1]
      end

    HTTPoison.get(url, headers,
      recv_timeout: 10_000,
      timeout: 10_000,
      hackney: [:insecure],
      ssl: [{:versions, ssl_versions}]
    )
  end
end

defmodule Pan.Parser.Download do
  require Logger
  alias Pan.Parser.Feed
  alias HTTPoison.{Error, Response}

  def check_for_rss(feed_xml) do
    if String.contains?(feed_xml, "<rss") do
      {:ok, feed_xml}
    else
      {:error, "not an rss feed"}
    end
  end

  def download(url, option \\ nil, feed_id \\ nil) do
    case get(url, option) do
      {:ok, %Response{status_code: 200, body: feed_xml}} ->
        check_for_rss(feed_xml)

      {:ok, %Response{status_code: 203, body: feed_xml}} ->
        check_for_rss(feed_xml)

      {:ok, %Response{status_code: 206, body: feed_xml}} ->
        check_for_rss(feed_xml)

      {:ok, %Response{status_code: 204}} ->
        {:error, "204: no content"}

      {:ok, %Response{status_code: 304}} ->
        {:error, "304: not modified"}

      {:ok, %Response{status_code: 400}} ->
        {:error, "400: bad request"}

      {:ok, %Response{status_code: 401}} ->
        {:error, "401: unauthorized"}

      {:ok, %Response{status_code: 402}} ->
        {:error, "402: payment required"}

      {:ok, %Response{status_code: 404}} ->
        {:error, "404: feed not found"}

      {:ok, %Response{status_code: 406}} ->
        {:error, "406: not acceptable"}

      {:ok, %Response{status_code: 408}} ->
        {:error, "408: request timeout"}

      {:ok, %Response{status_code: 410}} ->
        {:error, "410: gone"}

      {:ok, %Response{status_code: 416}} ->
        {:error, "416: range not satisfiable"}

      {:ok, %Response{status_code: 422}} ->
        {:error, "422: unprocessible entity"}

      {:ok, %Response{status_code: 423}} ->
        {:error, "423: locked"}

      {:ok, %Response{status_code: 429}} ->
        {:error, "429: too many requests"}

      {:ok, %Response{status_code: 451}} ->
        {:error, "451: unavailable For legal reasons"}

      {:ok, %Response{status_code: 479}} ->
        {:error, "479: not a standard status code"}

      {:ok, %Response{status_code: 500}} ->
        {:error, "500: internal server error"}

      {:ok, %Response{status_code: 501}} ->
        {:error, "501: not implemented"}

      {:ok, %Response{status_code: 502}} ->
        {:error, "502: bad gateway"}

      {:ok, %Response{status_code: 503}} ->
        {:error, "503: service unavailable"}

      {:ok, %Response{status_code: 504}} ->
        {:error, "504: gateway time-out"}

      {:ok, %Response{status_code: 508}} ->
        {:error, "508: loop detected"}

      {:ok, %Response{status_code: 509}} ->
        {:error, "509: bandwidth limit exceeded"}

      {:ok, %Response{status_code: 520}} ->
        {:error, "520: unknown error"}

      {:ok, %Response{status_code: 521}} ->
        {:error, "521: web server is down"}

      {:ok, %Response{status_code: 523}} ->
        {:error, "523: origin is unreachable"}

      {:ok, %Response{status_code: 526}} ->
        {:error, "526: invalid SSL certificate"}

      {:ok, %Response{status_code: 530}} ->
        {:error, "530: origin DNS error with cloudflare"}

      {:ok, %Response{status_code: 301, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 302, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 303, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 308, headers: headers}} ->
        redirect(url, headers, feed_id)

      {:ok, %Response{status_code: 307}} ->
        {:error, "307: temporary redirect"}

      {:ok, %Response{status_code: 403}} ->
        if option == "no_headers" do
          {:error, "403: forbidden"}
        else
          download(url, "no_headers")
        end

      {:ok, %Response{status_code: code}} ->
        Logger.error("status_code unknown #{inspect(code)}")

      {:error, %Error{id: nil, reason: :timeout}} ->
        {:error, "Timeout"}

      {:error, %Error{id: nil, reason: :ehostunreach}} ->
        {:error, "Host unreachable"}

      {:error, %Error{id: nil, reason: :nxdomain}} ->
        {:error, "Domain not resolveable"}

      {:error, %Error{id: nil, reason: :connect_timeout}} ->
        {:error, "Connection timeout"}

      {:error, %Error{id: nil, reason: :econnrefused}} ->
        {:error, "Connection refused"}

      {:error, %Error{id: nil, reason: :closed}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:closed, _feed_xml}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'handshake failure'}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'protocol version'}}} ->
        try_without_tls_set(url, option)

      {:error, %Error{id: nil, reason: {:tls_alert, 'unrecognised name'}}} ->
        try_without_tls_set(url, option)

      {:error, reason} ->
        {:error, reason}
    end
  end

  def try_without_tls_set(url, option) do
    case option do
      "unset_tls_version" -> {:error, "Does not work without specifying tls version as well!"}
      _ -> download(url, "unset_tls_version")
    end
  end

  def redirect(url, headers, feed_id) do
    header_map = Enum.into(headers, %{})

    redirect_target =
      if Map.has_key?(header_map, "Location") do
        Map.fetch!(header_map, "Location")
      else
        Map.fetch!(header_map, "location")
      end

    Feed.check_for_redirect_loop(url, redirect_target, feed_id)
  end

  def get(url, option) do
    headers =
      if option == "no_headers" do
        []
      else
        ["User-Agent": "Mozilla/5.0 (compatible; Panoptikum; +https://panoptikum.io/)"]
      end

      ssl_versions =
      if option == "unset_tls_version" do
        [:"tlsv1.2"]
      else
        [:"tlsv1.2", :"tlsv1.1", :tlsv1]
      end

    HTTPoison.get(url, headers, [
      recv_timeout: 10_000,
      timeout: 10_000,
      hackney: [:insecure],
      ssl: [{:versions, ssl_versions}]
    ])
  end
end


Use the correct repositories URL

Use the correct repositories URL

defmodule Aelita2.GitHub.Integration do
  @moduledoc """
  Wrappers for accessing the GitHub Integration API.
  """

  @content_type "application/vnd.github.machine-man-preview+json"

  # Get a repository by ID:
  # https://api.github.com/repositories/59789129

  # Public API

  def config do
    :aelita2
    |> Application.get_env(Aelita2.GitHub.Integration)
    |> Keyword.merge(Application.get_env(:aelita2, Aelita2.GitHub))
    |> Keyword.merge([site: "https://api.github.com"])
  end

  def get_installation_token!(installation_xref) do
    import Joken
    cfg = config()
    pem = JOSE.JWK.from_pem(cfg[:pem])
    jwt_token = %{
      iat: current_time(),
      exp: current_time() + 400,
      iss: cfg[:iss]}
    |> token()
    |> sign(rs256(pem))
    |> get_compact()
    %{body: raw, status_code: 201} = HTTPoison.post!(
      "#{cfg[:site]}/installations/#{installation_xref}/access_tokens",
      "",
      [{"Authorization", "Bearer #{jwt_token}"}, {"Accept", @content_type}])
    Poison.decode!(raw)["token"]
  end

  def get_my_repos!(token, url \\ nil, append \\ []) when is_binary(token) do
    {url, params} = case url do
      nil ->
        {"#{config()[:site]}/installation/repositories", []}
      url ->
        params = URI.parse(url).query |> URI.query_decoder() |> Enum.to_list()
        {url, [params: params]}
    end
    %{body: raw, status_code: 200, headers: headers} = HTTPoison.get!(
      url,
      [{"Authorization", "token #{token}"}, {"Accept", @content_type}],
      params)
    repositories = Poison.decode!(raw)["repositories"]
    |> Enum.map(&%{
      id: &1["id"],
      name: &1["full_name"],
      permissions: %{
        admin: &1["permissions"]["admin"],
        push: &1["permissions"]["push"],
        pull: &1["permissions"]["pull"]
      },
      owner: %{
        id: &1["owner"]["id"],
        login: &1["owner"]["login"],
        avatar_url: &1["owner"]["avatar_url"],
        type: &1["owner"]["type"]}})
    |> Enum.concat(append)
    next_headers = headers
    |> Enum.filter(&(elem(&1, 0) == "Link"))
    |> Enum.map(&(ExLinkHeader.parse!(elem(&1, 1))))
    |> Enum.filter(&!is_nil(&1.next))
    case next_headers do
      [] -> repositories
      [next] -> get_my_repos!(token, next.next.url, repositories)
    end
  end
end

defmodule Aelita2.GitHub.Integration do
  @moduledoc """
  Wrappers for accessing the GitHub Integration API.
  """

  @content_type "application/vnd.github.machine-man-preview+json"

  # Get a repository by ID:
  # https://api.github.com/repositories/59789129

  # Public API

  def config do
    :aelita2
    |> Application.get_env(Aelita2.GitHub.Integration)
    |> Keyword.merge(Application.get_env(:aelita2, Aelita2.GitHub))
    |> Keyword.merge([site: "https://api.github.com"])
  end

  def get_installation_token!(installation_xref) do
    import Joken
    cfg = config()
    pem = JOSE.JWK.from_pem(cfg[:pem])
    jwt_token = %{
      iat: current_time(),
      exp: current_time() + 400,
      iss: cfg[:iss]}
    |> token()
    |> sign(rs256(pem))
    |> get_compact()
    %{body: raw, status_code: 201} = HTTPoison.post!(
      "#{cfg[:site]}/installations/#{installation_xref}/access_tokens",
      "",
      [{"Authorization", "Bearer #{jwt_token}"}, {"Accept", @content_type}])
    Poison.decode!(raw)["token"]
  end

  def get_my_repos!(token, url \\ nil, append \\ []) when is_binary(token) do
    {url, params} = case url do
      nil ->
        {"#{config()[:site]}/installation/repos", []}
      url ->
        params = URI.parse(url).query |> URI.query_decoder() |> Enum.to_list()
        {url, [params: params]}
    end
    %{body: raw, status_code: 200, headers: headers} = HTTPoison.get!(
      url,
      [{"Authorization", "token #{token}"}, {"Accept", @content_type}],
      params)
    repositories = Poison.decode!(raw)["repositories"]
    |> Enum.map(&%{
      id: &1["id"],
      name: &1["full_name"],
      permissions: %{
        admin: &1["permissions"]["admin"],
        push: &1["permissions"]["push"],
        pull: &1["permissions"]["pull"]
      },
      owner: %{
        id: &1["owner"]["id"],
        login: &1["owner"]["login"],
        avatar_url: &1["owner"]["avatar_url"],
        type: &1["owner"]["type"]}})
    |> Enum.concat(append)
    next_headers = headers
    |> Enum.filter(&(elem(&1, 0) == "Link"))
    |> Enum.map(&(ExLinkHeader.parse!(elem(&1, 1))))
    |> Enum.filter(&!is_nil(&1.next))
    case next_headers do
      [] -> repositories
      [next] -> get_my_repos!(token, next.next.url, repositories)
    end
  end
end


plug fix -> x-forwarded-for

plug fix -> x-forwarded-for

defmodule Askbywhoplugs.RemoteIPPlug do
  @behaviour Plug
  import Plug.Conn

  def init([]), do: []

  def call(conn, []) do
    case get_req_header(conn, "x-forwarded-for") do
      [] -> conn
      [real_ip] ->
        case :inet_parse.address(to_char_list(real_ip)) do
          {:ok, real_ip} -> %{conn | remote_ip: real_ip}
          _ -> conn
        end
    end
  end
end

defmodule Askbywhoplugs.RemoteIPPlug do
  @behaviour Plug
  import Plug.Conn

  def init([]), do: []

  def call(conn, []) do
    case get_req_header(conn, "x-real-ip") do
      [] -> conn
      [real_ip] ->
        case :inet_parse.address(to_char_list(real_ip)) do
          {:ok, real_ip} -> %{conn | remote_ip: real_ip}
          _ -> conn
        end
    end
  end
end


whitespace

whitespace

defmodule ProcessesTests do
  use ExUnit.Case
  import TestHarness

  test "Processes" do
    answers = [
      true,
      :running,
      true,
      true,
      "hola!",
      :how_are_you?,
      {:waited_too_long, "I am impatient"},
      false,
      {:multiple, [true, false]},
      {:exited, :random_reason},
      true,
      false,
      :normal,
      :normal,
    ]

    test_all(Processes, answers)
  end
end


Fix Inspect.Algebra.format/3 to handle :infinity correctly

defmodule Inspect.Algebra do
  @moduledoc %S"""
  A set of functions for creating and manipulating algebra
  documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].

  An algebra document is represented by an `Inspect.Algebra` node
  or a regular string.

      iex> Inspect.Algebra.empty
      :doc_nil
      iex> "foo"
      "foo"

  With the functions in this module, we can concatenate different
  elements together and render them:

      iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
      iex> Inspect.Algebra.pretty(doc, 80)
      "foo"

  The functions `nest/2`, `space/2` and `line/2` help you put the
  document together into a rigid structure. However, the document
  algebra gets interesting when using functions like `break/2`, which
  converts the given string into a line break depending on how much space
  there is to print. Let's glue two docs together with a break and then
  render it:

      iex> doc = Inspect.Algebra.glue("a", " ", "b")
      iex> Inspect.Algebra.pretty(doc, 80)
      "a b"

  Notice the break was represented as is, because we haven't reached
  a line limit. Once we do, it is replaced by a newline:

      iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
      iex> Inspect.Algebra.pretty(doc, 10)
      "aaaaaaaaaaaaaaaaaaaa\nb"

  Finally, this module also contains Elixir related functions, a bit
  tied to Elixir formatting, namely `surround/3` and `surround_many/5`.

  ## Implementation details

  The original Haskell implementation of the algorithm by [Wadler][1]
  relies on lazy evaluation to unfold document groups on two alternatives:
  `:flat` (breaks as spaces) and `:break` (breaks as newlines).
  Implementing the same logic in a strict language such as Elixir leads
  to an exponential growth of possible documents, unless document groups
  are encoded explictly as `:flat` or `:break`. Those groups are then reduced
  to a simple document, where the layout is already decided, per [Lindig][0].

  This implementation slightly changes the semantic of Lindig's algorithm
  to allow elements that belong to the same group to be printed together
  in the same line, even if they do not fit the line fully. This was achieved
  by changing `:break` to mean a possible break and `:flat` to force a flat
  structure. Then deciding if a break works as a newline is just a matter
  of checking if we have enough space until the next break that is not
  inside a group (which is still flat).

  Custom pretty printers can be implemented using the documents returned
  by this module and by providing their own rendering functions.

    [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
    [1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf

  """

  @surround_separator ","
  @tail_separator " |"
  @newline "\n"
  @nesting 1
  @break " "

  defp repeat(_, 0), do: ""
  defp repeat(s, i), do: :lists.duplicate(i, s)

  # Functional interface to `doc` records

  @type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
  defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
  defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
  defrecordp :doc_break, str: " " :: binary
  defrecordp :doc_group, doc: :doc_nil :: t

  defmacrop is_doc(doc) do
    if __CALLER__.in_guard? do
      do_is_doc(doc)
    else
      var = quote do: doc
      quote do
        unquote(var) = unquote(doc)
        unquote(do_is_doc(var))
      end
    end
  end

  defp do_is_doc(doc) do
    quote do
      unquote(doc) |> is_binary or
      unquote(doc) |> is_integer or
      unquote(doc) == :doc_nil or
      (unquote(doc) |> is_tuple and
       elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
    end
  end

  @doc """
  Returns `:doc_nil` which is a document entity used to represent
  nothingness. Takes no arguments.

  ## Examples

      iex> Inspect.Algebra.empty
      :doc_nil

  """
  @spec empty() :: :doc_nil
  def empty, do: :doc_nil

  @doc """
  Concatenates two document entities. Takes two arguments:
  left doc and right doc. Returns a DocCons doc

  ## Examples

      iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
      iex> Inspect.Algebra.pretty(doc, 80)
      "TastelessArtosis"

  """
  @spec concat(t, t) :: doc_cons_t
  def concat(x, y) when is_doc(x) and is_doc(y) do
    doc_cons(left: x, right: y)
  end

  @doc """
  Concatenates a list of documents.
  """
  @spec concat([t]) :: doc_cons_t
  def concat(docs) do
    folddoc(docs, &concat(&1, &2))
  end

  @doc """
  Nests document entity `x` positions deep. Nesting will be
  appended to the line breaks.

  ## Examples

      iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
      iex> Inspect.Algebra.pretty(doc, 80)
      " 6"

  """
  @spec nest(t, non_neg_integer) :: doc_nest_t
  def nest(x, 0) when is_doc(x) do
    x
  end

  def nest(x, i) when is_doc(x) and is_integer(i) do
    doc_nest(indent: i, doc: x)
  end

  @doc %S"""
  Document entity representing a break. This break can
  be rendered as a linebreak or as spaces, depending on the
  `mode` of the chosen layout or the provided separator.

  ## Examples

  Let's glue two docs together with a break and then render it:

      iex> doc = Inspect.Algebra.glue("a", " ", "b")
      iex> Inspect.Algebra.pretty(doc, 80)
      "a b"

  Notice the break was represented as is, because we haven't reached
  a line limit. Once we do, it is replaced by a newline:

      iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
      iex> Inspect.Algebra.pretty(doc, 10)
      "aaaaaaaaaaaaaaaaaaaa\nb"

  """
  @spec break(binary) :: doc_break_t
  def break(s) when is_binary(s), do: doc_break(str: s)

  @spec break() :: doc_break_t
  def break(), do: doc_break(str: @break)

  @doc """
  Inserts a break between two docs. See `break/1` for more info.
  """
  @spec glue(t, t) :: doc_cons_t
  def glue(x, y), do: concat(x, concat(break, y))

  @doc """
  Inserts a break, passed as the second argument, between two docs,
  the first and the third arguments.
  """
  @spec glue(t, binary, t) :: doc_cons_t
  def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))

  @doc %S"""
  Returns a group containing the specified document.

  ## Examples

      iex> doc = Inspect.Algebra.group(
      ...>   Inspect.Algebra.concat(
      ...>     Inspect.Algebra.group(
      ...>       Inspect.Algebra.concat(
      ...>         "Hello,",
      ...>         Inspect.Algebra.concat(
      ...>           Inspect.Algebra.break,
      ...>           "A"
      ...>         )
      ...>       )
      ...>     ),
      ...>     Inspect.Algebra.concat(
      ...>       Inspect.Algebra.break,
      ...>       "B"
      ...>     )
      ...> ))
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hello, A B"
      iex> Inspect.Algebra.pretty(doc, 6)
      "Hello,\nA B"

  """
  @spec group(t) :: doc_group_t
  def group(d) when is_doc(d) do
    doc_group(doc: d)
  end

  @doc """
  Inserts a mandatory single space between two document entities.

  ## Examples

      iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hughes Wadler"

  """
  @spec space(t, t) :: doc_cons_t
  def space(x, y), do: concat(x, concat(" ", y))

  @doc %S"""
  Inserts a mandatory linebreak between two document entities.

  ## Examples

      iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hughes\nWadler"

  """
  @spec line(t, t) :: doc_cons_t
  def line(x, y), do: concat(x, concat(@newline, y))

  @doc """
  Folds a list of document entities into a document entity
  using a function that is passed as the first argument.

  ## Examples

      iex> doc = ["A", "B"]
      iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
      ...>   Inspect.Algebra.concat [x, "!", y]
      ...> end)
      iex> Inspect.Algebra.pretty(doc, 80)
      "A!B"

  """
  @spec folddoc([t], ((t, t) -> t)) :: t
  def folddoc([], _), do: empty
  def folddoc([doc], _), do: doc
  def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))

  # Elixir conveniences

  @doc %S"""
  Surrounds a document with characters.

  Puts the document between left and right enclosing and nesting it.
  The document is marked as a group, to show the maximum as possible
  concisely together.

  ## Examples

      iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
      iex> Inspect.Algebra.pretty(doc, 3)
      "[a\n b]"

  """
  @spec surround(binary, t, binary) :: t
  def surround(left, doc, right) do
    group concat [left, nest(doc, @nesting), right]
  end

  @doc %S"""
  Maps and glues a collection of items together using the given separator
  and surrounds them. A limit can be passed which, once reached, stops
  gluing and outputs "..." instead.

  ## Examples

      iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
      iex> Inspect.Algebra.pretty(doc, 5)
      "[1,\n 2,\n 3,\n 4,\n 5]"

      iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
      iex> Inspect.Algebra.pretty(doc, 20)
      "[1, 2, 3, ...]"

  """
  @spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
  def surround_many(left, [], right, _, _fun) do
    concat(left, right)
  end

  def surround_many(left, docs, right, limit, fun) do
    surround(left, surround_many(docs, limit, fun), right)
  end

  defp surround_many(_, 0, _fun) do
    "..."
  end

  defp surround_many([h], _limit, fun) do
    fun.(h)
  end

  defp surround_many([h|t], limit, fun) when is_list(t) do
    glue(
      concat(fun.(h), @surround_separator),
      surround_many(t, decrement(limit), fun)
    )
  end

  defp surround_many([h|t], _limit, fun) do
    glue(
      concat(fun.(h), @tail_separator),
      fun.(t)
    )
  end

  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1

  @doc """
  The pretty printing function.

  Takes the maximum width and a document to print as its arguments
  and returns the string representation of the best layout for the
  document to fit in the given width.
  """
  @spec pretty(t, non_neg_integer | :infinity) :: binary
  def pretty(d, w) do
    sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
    render(sdoc)
  end

  defp default_mode(:infinity), do: :flat
  defp default_mode(_),         do: :break

  # Rendering and internal helpers

  # Records representing __simple__ documents, already on a fixed layout
  # Those are generalized by `sdoc` type.
  @type sdoc :: :s_nil | s_text_t | s_line_t
  defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
  defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc

  # Record representing the document mode to be rendered: flat or broken
  @typep mode :: :flat | :break

  @doc false
  @spec fits?(integer, [{ integer, mode, t }]) :: boolean
  def fits?(:infinity, _),                                  do: true # no pretty printing
  def fits?(w, _) when w < 0,                               do: false
  def fits?(_, []),                                         do: true
  def fits?(w, [{_, _, :doc_nil} | t]),                     do: fits?(w, t)
  def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]),  do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
  def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]),  do: fits?(w, [{i + j, m, x} | t])
  def fits?(w, [{_, _, s} | t]) when is_binary(s),          do: fits?((w - byte_size s), t)
  def fits?(w, [{_, :flat, doc_break(str: s)} | t]),        do: fits?((w - byte_size s), t)
  def fits?(_, [{_, :break, doc_break(str: _)} | _]),       do: true
  def fits?(w, [{i, _, doc_group(doc: x)} | t]),            do: fits?(w, [{i, :flat, x} | t])

  @doc false
  @spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
  def format(_, _, []),                                        do: :s_nil
  def format(w, k, [{_, _, :doc_nil} | t]),                    do: format(w, k, t)
  def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
  def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
  def format(w, k, [{_, _, s} | t]) when is_binary(s),         do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
  def format(w, k, [{i, m, doc_group(doc: x)} | t]),           do: format(w, k, [{i, m, x} | t])
  def format(w, k, [{_, :flat, doc_break(str: s)} | t]),       do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
  def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
    k = k + byte_size(s)
    w2 = if w == :infinity, do: :infinity, else: w - k
    if fits?(w2, t) do
      s_text(str: s, sdoc: format(w, k, t))
    else
      s_line(indent: i, sdoc: format(w, i, t))
    end
  end

  @doc false
  @spec render(sdoc) :: binary
  def render(sdoc) do
    iolist_to_binary do_render sdoc
  end

  @spec do_render(sdoc) :: [binary]
  defp do_render(:s_nil), do: [""]
  defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
  defp do_render(s_line(indent: i, sdoc: d)) do
    prefix = repeat " ", i
    [@newline | [prefix | do_render d]]
  end
end

defmodule Inspect.Algebra do
  @moduledoc %S"""
  A set of functions for creating and manipulating algebra
  documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].

  An algebra document is represented by an `Inspect.Algebra` node
  or a regular string.

      iex> Inspect.Algebra.empty
      :doc_nil
      iex> "foo"
      "foo"

  With the functions in this module, we can concatenate different
  elements together and render them:

      iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
      iex> Inspect.Algebra.pretty(doc, 80)
      "foo"

  The functions `nest/2`, `space/2` and `line/2` help you put the
  document together into a rigid structure. However, the document
  algebra gets interesting when using functions like `break/2`, which
  converts the given string into a line break depending on how much space
  there is to print. Let's glue two docs together with a break and then
  render it:

      iex> doc = Inspect.Algebra.glue("a", " ", "b")
      iex> Inspect.Algebra.pretty(doc, 80)
      "a b"

  Notice the break was represented as is, because we haven't reached
  a line limit. Once we do, it is replaced by a newline:

      iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
      iex> Inspect.Algebra.pretty(doc, 10)
      "aaaaaaaaaaaaaaaaaaaa\nb"

  Finally, this module also contains Elixir related functions, a bit
  tied to Elixir formatting, namely `surround/3` and `surround_many/5`.

  ## Implementation details

  The original Haskell implementation of the algorithm by [Wadler][1]
  relies on lazy evaluation to unfold document groups on two alternatives:
  `:flat` (breaks as spaces) and `:break` (breaks as newlines).
  Implementing the same logic in a strict language such as Elixir leads
  to an exponential growth of possible documents, unless document groups
  are encoded explictly as `:flat` or `:break`. Those groups are then reduced
  to a simple document, where the layout is already decided, per [Lindig][0].

  This implementation slightly changes the semantic of Lindig's algorithm
  to allow elements that belong to the same group to be printed together
  in the same line, even if they do not fit the line fully. This was achieved
  by changing `:break` to mean a possible break and `:flat` to force a flat
  structure. Then deciding if a break works as a newline is just a matter
  of checking if we have enough space until the next break that is not
  inside a group (which is still flat).

  Custom pretty printers can be implemented using the documents returned
  by this module and by providing their own rendering functions.

    [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
    [1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf

  """

  @surround_separator ","
  @tail_separator " |"
  @newline "\n"
  @nesting 1
  @break " "

  defp repeat(_, 0), do: ""
  defp repeat(s, i), do: :lists.duplicate(i, s)

  # Functional interface to `doc` records

  @type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
  defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
  defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
  defrecordp :doc_break, str: " " :: binary
  defrecordp :doc_group, doc: :doc_nil :: t

  defmacrop is_doc(doc) do
    if __CALLER__.in_guard? do
      do_is_doc(doc)
    else
      var = quote do: doc
      quote do
        unquote(var) = unquote(doc)
        unquote(do_is_doc(var))
      end
    end
  end

  defp do_is_doc(doc) do
    quote do
      unquote(doc) |> is_binary or
      unquote(doc) |> is_integer or
      unquote(doc) == :doc_nil or
      (unquote(doc) |> is_tuple and
       elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
    end
  end

  @doc """
  Returns `:doc_nil` which is a document entity used to represent
  nothingness. Takes no arguments.

  ## Examples

      iex> Inspect.Algebra.empty
      :doc_nil

  """
  @spec empty() :: :doc_nil
  def empty, do: :doc_nil

  @doc """
  Concatenates two document entities. Takes two arguments:
  left doc and right doc. Returns a DocCons doc

  ## Examples

      iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
      iex> Inspect.Algebra.pretty(doc, 80)
      "TastelessArtosis"

  """
  @spec concat(t, t) :: doc_cons_t
  def concat(x, y) when is_doc(x) and is_doc(y) do
    doc_cons(left: x, right: y)
  end

  @doc """
  Concatenates a list of documents.
  """
  @spec concat([t]) :: doc_cons_t
  def concat(docs) do
    folddoc(docs, &concat(&1, &2))
  end

  @doc """
  Nests document entity `x` positions deep. Nesting will be
  appended to the line breaks.

  ## Examples

      iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
      iex> Inspect.Algebra.pretty(doc, 80)
      " 6"

  """
  @spec nest(t, non_neg_integer) :: doc_nest_t
  def nest(x, 0) when is_doc(x) do
    x
  end

  def nest(x, i) when is_doc(x) and is_integer(i) do
    doc_nest(indent: i, doc: x)
  end

  @doc %S"""
  Document entity representing a break. This break can
  be rendered as a linebreak or as spaces, depending on the
  `mode` of the chosen layout or the provided separator.

  ## Examples

  Let's glue two docs together with a break and then render it:

      iex> doc = Inspect.Algebra.glue("a", " ", "b")
      iex> Inspect.Algebra.pretty(doc, 80)
      "a b"

  Notice the break was represented as is, because we haven't reached
  a line limit. Once we do, it is replaced by a newline:

      iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
      iex> Inspect.Algebra.pretty(doc, 10)
      "aaaaaaaaaaaaaaaaaaaa\nb"

  """
  @spec break(binary) :: doc_break_t
  def break(s) when is_binary(s), do: doc_break(str: s)

  @spec break() :: doc_break_t
  def break(), do: doc_break(str: @break)

  @doc """
  Inserts a break between two docs. See `break/1` for more info.
  """
  @spec glue(t, t) :: doc_cons_t
  def glue(x, y), do: concat(x, concat(break, y))

  @doc """
  Inserts a break, passed as the second argument, between two docs,
  the first and the third arguments.
  """
  @spec glue(t, binary, t) :: doc_cons_t
  def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))

  @doc %S"""
  Returns a group containing the specified document.

  ## Examples

      iex> doc = Inspect.Algebra.group(
      ...>   Inspect.Algebra.concat(
      ...>     Inspect.Algebra.group(
      ...>       Inspect.Algebra.concat(
      ...>         "Hello,",
      ...>         Inspect.Algebra.concat(
      ...>           Inspect.Algebra.break,
      ...>           "A"
      ...>         )
      ...>       )
      ...>     ),
      ...>     Inspect.Algebra.concat(
      ...>       Inspect.Algebra.break,
      ...>       "B"
      ...>     )
      ...> ))
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hello, A B"
      iex> Inspect.Algebra.pretty(doc, 6)
      "Hello,\nA B"

  """
  @spec group(t) :: doc_group_t
  def group(d) when is_doc(d) do
    doc_group(doc: d)
  end

  @doc """
  Inserts a mandatory single space between two document entities.

  ## Examples

      iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hughes Wadler"

  """
  @spec space(t, t) :: doc_cons_t
  def space(x, y), do: concat(x, concat(" ", y))

  @doc %S"""
  Inserts a mandatory linebreak between two document entities.

  ## Examples

      iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
      iex> Inspect.Algebra.pretty(doc, 80)
      "Hughes\nWadler"

  """
  @spec line(t, t) :: doc_cons_t
  def line(x, y), do: concat(x, concat(@newline, y))

  @doc """
  Folds a list of document entities into a document entity
  using a function that is passed as the first argument.

  ## Examples

      iex> doc = ["A", "B"]
      iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
      ...>   Inspect.Algebra.concat [x, "!", y]
      ...> end)
      iex> Inspect.Algebra.pretty(doc, 80)
      "A!B"

  """
  @spec folddoc([any], ((any, [t]) -> t)) :: t
  def folddoc([], _), do: empty
  def folddoc([doc], _), do: doc
  def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))

  # Elixir conveniences

  @doc %S"""
  Surrounds a document with characters.

  Puts the document between left and right enclosing and nesting it.
  The document is marked as a group, to show the maximum as possible
  concisely together.

  ## Examples

      iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
      iex> Inspect.Algebra.pretty(doc, 3)
      "[a\n b]"

  """
  @spec surround(binary, t, binary) :: t
  def surround(left, doc, right) do
    group concat [left, nest(doc, @nesting), right]
  end

  @doc %S"""
  Maps and glues a collection of items together using the given separator
  and surrounds them. A limit can be passed which, once reached, stops
  gluing and outputs "..." instead.

  ## Examples

      iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
      iex> Inspect.Algebra.pretty(doc, 5)
      "[1,\n 2,\n 3,\n 4,\n 5]"

      iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
      iex> Inspect.Algebra.pretty(doc, 20)
      "[1, 2, 3, ...]"

  """
  @spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
  def surround_many(left, [], right, _, _fun) do
    concat(left, right)
  end

  def surround_many(left, docs, right, limit, fun) do
    surround(left, surround_many(docs, limit, fun), right)
  end

  defp surround_many(_, 0, _fun) do
    "..."
  end

  defp surround_many([h], _limit, fun) do
    fun.(h)
  end

  defp surround_many([h|t], limit, fun) when is_list(t) do
    glue(
      concat(fun.(h), @surround_separator),
      surround_many(t, decrement(limit), fun)
    )
  end

  defp surround_many([h|t], _limit, fun) do
    glue(
      concat(fun.(h), @tail_separator),
      fun.(t)
    )
  end

  defp decrement(:infinity), do: :infinity
  defp decrement(counter),   do: counter - 1

  @doc """
  The pretty printing function.

  Takes the maximum width and a document to print as its arguments
  and returns the string representation of the best layout for the
  document to fit in the given width.
  """
  @spec pretty(t, non_neg_integer | :infinity) :: binary
  def pretty(d, w) do
    sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
    render(sdoc)
  end

  defp default_mode(:infinity), do: :flat
  defp default_mode(_),         do: :break

  # Rendering and internal helpers

  # Records representing __simple__ documents, already on a fixed layout
  # Those are generalized by `sdoc` type.
  @type sdoc :: :s_nil | s_text_t | s_line_t
  defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
  defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc

  # Record representing the document mode to be rendered: flat or broken
  @typep mode :: :flat | :break

  @doc false
  @spec fits?(integer, [{ integer, mode, t }]) :: boolean
  def fits?(:infinity, _),                                  do: true # no pretty printing
  def fits?(w, _) when w < 0,                               do: false
  def fits?(_, []),                                         do: true
  def fits?(w, [{_, _, :doc_nil} | t]),                     do: fits?(w, t)
  def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]),  do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
  def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]),  do: fits?(w, [{i + j, m, x} | t])
  def fits?(w, [{_, _, s} | t]) when is_binary(s),          do: fits?((w - byte_size s), t)
  def fits?(w, [{_, :flat, doc_break(str: s)} | t]),        do: fits?((w - byte_size s), t)
  def fits?(_, [{_, :break, doc_break(str: _)} | _]),       do: true
  def fits?(w, [{i, _, doc_group(doc: x)} | t]),            do: fits?(w, [{i, :flat, x} | t])

  @doc false
  @spec format(integer, integer, [{ integer, mode, t }]) :: atom | tuple
  def format(_, _, []),                                        do: :s_nil
  def format(w, k, [{_, _, :doc_nil} | t]),                    do: format(w, k, t)
  def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
  def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
  def format(w, k, [{_, _, s} | t]) when is_binary(s),         do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
  def format(w, k, [{i, m, doc_group(doc: x)} | t]),           do: format(w, k, [{i, m, x} | t])
  def format(w, k, [{_, :flat, doc_break(str: s)} | t]),       do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
  def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
    k = k + byte_size(s)
    if fits?(w - k, t) do
      s_text(str: s, sdoc: format(w, k, t))
    else
      s_line(indent: i, sdoc: format(w, i, t))
    end
  end

  @doc false
  @spec render(sdoc) :: binary
  def render(sdoc) do
    iolist_to_binary do_render sdoc
  end

  @spec do_render(sdoc) :: [binary]
  defp do_render(:s_nil), do: [""]
  defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
  defp do_render(s_line(indent: i, sdoc: d)) do
    prefix = repeat " ", i
    [@newline | [prefix | do_render d]]
  end
end


Run the code formatter on Mix.Dep.ElixirSCM (#6670)

# Manifest file where we treat Elixir and SCMs as a dependency.
defmodule Mix.Dep.ElixirSCM do
  @moduledoc false
  @manifest ".compile.elixir_scm"
  @manifest_vsn :v2

  def manifest(manifest_path \\ Mix.Project.manifest_path()) do
    Path.join(manifest_path, @manifest)
  end

  def update(manifest_path \\ Mix.Project.manifest_path()) do
    config = Mix.Project.config()
    File.mkdir_p!(manifest_path)

    manifest_data =
      {@manifest_vsn, {System.version(), :erlang.system_info(:otp_release)}, config[:build_scm]}
      |> :erlang.term_to_binary()

    File.write!(manifest(manifest_path), manifest_data)
  end

  def read(manifest_path \\ Mix.Project.manifest_path()) do
    case File.read(manifest(manifest_path)) do
      {:ok, contents} ->
        try do
          {@manifest_vsn, vsn, scm} = :erlang.binary_to_term(contents)
          {:ok, vsn, scm}
        rescue
          _ -> {:ok, {"1.0.0", '17'}, nil}
        end

      _ ->
        :error
    end
  end
end

# Manifest file where we treat Elixir and SCMs as a dependency.
defmodule Mix.Dep.ElixirSCM do
  @moduledoc false
  @manifest ".compile.elixir_scm"
  @manifest_vsn :v2

  def manifest(manifest_path \\ Mix.Project.manifest_path) do
    Path.join(manifest_path, @manifest)
  end

  def update(manifest_path \\ Mix.Project.manifest_path) do
    config = Mix.Project.config
    File.mkdir_p!(manifest_path)

    manifest_data =
      {@manifest_vsn, {System.version, :erlang.system_info(:otp_release)}, config[:build_scm]}
      |> :erlang.term_to_binary()

    File.write!(manifest(manifest_path), manifest_data)
  end

  def read(manifest_path \\ Mix.Project.manifest_path) do
    case File.read(manifest(manifest_path)) do
      {:ok, contents} ->
        try do
          {@manifest_vsn, vsn, scm} = :erlang.binary_to_term(contents)
          {:ok, vsn, scm}
        rescue
          _ -> {:ok, {"1.0.0", '17'}, nil}
        end
      _ ->
        :error
    end
  end
end


better grouping an organization of the Store.Persistent tests

defmodule FunWithFlags.Store.PersistentTest do
  use ExUnit.Case, async: true
  import FunWithFlags.TestUtils

  alias FunWithFlags.Store.Persistent

  setup_all do
    on_exit(__MODULE__, fn() -> clear_redis_test_db() end)
    :ok
  end

  describe "put(flag_name, value)" do
    test "put() can change the value of a flag" do
      flag_name = unique_atom()

      assert false == Persistent.get(flag_name)
      Persistent.put(flag_name, true)
      assert true == Persistent.get(flag_name)
      Persistent.put(flag_name, false)
      assert false == Persistent.get(flag_name)
    end

    test "put() returns the tuple {:ok, a_boolean_value}" do
      flag_name = unique_atom()
      assert {:ok, true} == Persistent.put(flag_name, true)
      assert {:ok, false} == Persistent.put(flag_name, false)
    end
  end

  describe "get(flag_name)" do
    test "looking up an undefined flag returns false" do
      flag_name = unique_atom()
      assert false == Persistent.get(flag_name)
    end

    test "get() returns a boolean" do
      flag_name = unique_atom()
      assert false == Persistent.get(flag_name)
      Persistent.put(flag_name, true)
      assert true == Persistent.get(flag_name)
    end
  end

  describe "unit: enable and disable with this module's API" do
    test "looking up a disabled flag returns false" do
      flag_name = unique_atom()
      Persistent.put(flag_name, false)
      assert false == Persistent.get(flag_name)
    end

    test "looking up an enabled flag returns true" do
      flag_name = unique_atom()
      Persistent.put(flag_name, true)
      assert true == Persistent.get(flag_name)
    end
  end

  describe "integration: enable and disable with the top-level API" do
    test "looking up a disabled flag returns false" do
      flag_name = unique_atom()
      FunWithFlags.disable(flag_name)
      assert false == Persistent.get(flag_name)
    end

    test "looking up an enabled flag returns true" do
      flag_name = unique_atom()
      FunWithFlags.enable(flag_name)
      assert true == Persistent.get(flag_name)
    end
  end
end

defmodule FunWithFlags.Store.PersistentTest do
  use ExUnit.Case, async: true
  import FunWithFlags.TestUtils

  alias FunWithFlags.Store.Persistent

  setup_all do
    on_exit(__MODULE__, fn() -> clear_redis_test_db() end)
    :ok
  end

  test "looking up an undefined flag returns false" do
    flag_name = unique_atom()
    assert false == Persistent.get(flag_name)
  end

  test "put() can change the value of a flag" do
    flag_name = unique_atom()

    assert false == Persistent.get(flag_name)
    Persistent.put(flag_name, true)
    assert true == Persistent.get(flag_name)
    Persistent.put(flag_name, false)
    assert false == Persistent.get(flag_name)
  end

  test "put() returns the tuple {:ok, a_boolean_value}" do
    flag_name = unique_atom()
    assert {:ok, true} == Persistent.put(flag_name, true)
    assert {:ok, false} == Persistent.put(flag_name, false)
  end

  test "get() returns a boolean" do
    flag_name = unique_atom()
    assert false == Persistent.get(flag_name)
    Persistent.put(flag_name, true)
    assert true == Persistent.get(flag_name)
  end

  describe "unit: enable and disable with this module's API" do
    test "looking up a disabled flag returns false" do
      flag_name = unique_atom()
      Persistent.put(flag_name, false)
      assert false == Persistent.get(flag_name)
    end

    test "looking up an enabled flag returns true" do
      flag_name = unique_atom()
      Persistent.put(flag_name, true)
      assert true == Persistent.get(flag_name)
    end
  end

  describe "integration: enable and disable with the top-level API" do
    test "looking up a disabled flag returns false" do
      flag_name = unique_atom()
      FunWithFlags.disable(flag_name)
      assert false == Persistent.get(flag_name)
    end

    test "looking up an enabled flag returns true" do
      flag_name = unique_atom()
      FunWithFlags.enable(flag_name)
      assert true == Persistent.get(flag_name)
    end
  end
end


Update telemetry documentation

Update telemetry documentation

defmodule Tesla.Middleware.Telemetry do
  @behaviour Tesla.Middleware

  @moduledoc """
  Send the request time and meta-information through telemetry.

  ### Example usage
  ```
  defmodule MyClient do
    use Tesla

    plug Tesla.Middleware.Telemetry

  end

  :telemetry.attach("my-tesla-telemetry", [:tesla, :request], fn event, time, meta, config ->
    # Do something with the event
  end)
  ```

  Please check the [telemetry](https://hexdocs.pm/telemetry/) for the further usage.
  """

  @doc false
  def call(env, next, _opts) do
    {time, res} = :timer.tc(Tesla, :run, [env, next])
    :telemetry.execute([:tesla, :request], time, %{result: res})
    res
  end
end

defmodule Tesla.Middleware.Telemetry do
  @behaviour Tesla.Middleware

  @moduledoc """
  Send the request duration and meta-information through telemetry.

  ### Example usage
  ```
  defmodule TelemetryHandler do
    use GenServer

    def start_link(_, _) do
      :telemetry_app.start(nil, nil)
      :telemetry.attach(
        "tesla-telemetry",
        [:tesla, :telemetry, :traffic],
        fn ([:tesla, :telemetry, :traffic], time, meta, _config) -> # Do sth end,
        nil)
    end
  end

  defmodule MyClient do
    use Tesla

    plug Tesla.Middleware.Telemetry

  end
  ```
  """

  @doc false
  def call(env, next, _opts) do
    {time, res} = :timer.tc(Tesla, :run, [env, next])
    :telemetry.execute([:tesla, :request], time, %{result: res})
    res
  end
end


Do not use external secret file

Do not use external secret file

use Mix.Config

# For production, we configure the host to read the PORT
# from the system environment. Therefore, you will need
# to set PORT=80 before running your server.
#
# You should also configure the url host to something
# meaningful, we use this information when generating URLs.
#
# Finally, we also include the path to a manifest
# containing the digested version of static files. This
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :spot_the_station, SpotTheStation.Endpoint,
  http: [port: {:system, "PORT"}],
  # url: [scheme: "https", host: "mysterious-meadow-6277.herokuapp.com", port: 443],
  # force_ssl: [rewrite_on: [:x_forwarded_proto]],
  url: [host: "iss.ede.li", port: 80],
  cache_static_manifest: "priv/static/manifest.json",
  secret_key_base: System.get_env("SECRET_KEY_BASE")

# Do not print debug messages in production
config :logger, level: :info

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
#     config :spot_the_station, SpotTheStation.Endpoint,
#       ...
#       url: [host: "example.com", port: 443],
#       https: [port: 443,
#               keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
#               certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
#
# Where those two env variables return an absolute path to
# the key and cert in disk or a relative path inside priv,
# for example "priv/ssl/server.key".
#
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
#     config :spot_the_station, SpotTheStation.Endpoint,
#       force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
#     config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
#     config :spot_the_station, SpotTheStation.Endpoint, server: true
#
# You will also need to set the application root to `.` in order
# for the new static assets to be served after a hot upgrade:
#
#     config :spot_the_station, SpotTheStation.Endpoint, root: "."

# Finally import the config/prod.secret.exs
# which should be versioned separately.
# import_config "prod.secret.exs"

use Mix.Config

# For production, we configure the host to read the PORT
# from the system environment. Therefore, you will need
# to set PORT=80 before running your server.
#
# You should also configure the url host to something
# meaningful, we use this information when generating URLs.
#
# Finally, we also include the path to a manifest
# containing the digested version of static files. This
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :spot_the_station, SpotTheStation.Endpoint,
  http: [port: {:system, "PORT"}],
  # url: [scheme: "https", host: "mysterious-meadow-6277.herokuapp.com", port: 443],
  # force_ssl: [rewrite_on: [:x_forwarded_proto]],
  url: [host: "iss.ede.li", port: 80],
  cache_static_manifest: "priv/static/manifest.json",
  secret_key_base: System.get_env("SECRET_KEY_BASE")

# Do not print debug messages in production
config :logger, level: :info

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
#     config :spot_the_station, SpotTheStation.Endpoint,
#       ...
#       url: [host: "example.com", port: 443],
#       https: [port: 443,
#               keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
#               certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
#
# Where those two env variables return an absolute path to
# the key and cert in disk or a relative path inside priv,
# for example "priv/ssl/server.key".
#
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
#     config :spot_the_station, SpotTheStation.Endpoint,
#       force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
#     config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
#     config :spot_the_station, SpotTheStation.Endpoint, server: true
#
# You will also need to set the application root to `.` in order
# for the new static assets to be served after a hot upgrade:
#
#     config :spot_the_station, SpotTheStation.Endpoint, root: "."

# Finally import the config/prod.secret.exs
# which should be versioned separately.
import_config "prod.secret.exs"


Cleanup old logs

Cleanup old logs

defmodule Farmbot do
  @moduledoc """
  Supervises the individual modules that make up the Farmbot Application.
  This is the entry point of the application.
  """

  require Farmbot.Logger
  require Logger
  use Supervisor

  @version Mix.Project.config()[:version]
  @commit Mix.Project.config()[:commit]

  @doc false
  def start(type, start_opts)

  def start(_, _start_opts) do
    case Supervisor.start_link(__MODULE__, [], [name: __MODULE__]) do
      {:ok, pid} -> {:ok, pid}
      error ->
        IO.puts "Failed to boot Farmbot: #{inspect error}"
        Farmbot.System.factory_reset(error)
        exit(error)
    end
  end

  def init([]) do
    Logger.remove_backend Logger.Backends.Console
    children = [
      supervisor(Farmbot.Logger.Supervisor, []),
      supervisor(Farmbot.System.Supervisor, []),
      supervisor(Farmbot.Bootstrap.Supervisor, [])
    ]

    Farmbot.Logger.info(1, "Booting Farmbot OS version: #{@version} - #{@commit}")
    opts = [strategy: :one_for_one]
    supervise(children, opts)
  end
end

defmodule Farmbot do
  @moduledoc """
  Supervises the individual modules that make up the Farmbot Application.
  This is the entry point of the application.

  Here is kind of how the tree works.
      + `Farmbot` - the Entry point of the app.
      |
      +---> + `Farmbot.System.Supervisor`    - The `init` system.
      |
      +---> + `Farmbot.Bootstrap.Supervisor` - Bootstraps into the main app.
            |
            +---> +` Farmbot.BotState.Superviror` - The main application.
                  |
                  +---> `BotState`               - Unions the next 5 modules.
                  |
                  +---> `InformationalSettings`  - Externally imutable settings.
                  |
                  +---> `Configuration`          - Externally mutable settings.
                  |
                  +---> `LocationData`           - Where the bot is in space.
                  |
                  +---> `McuParams`              - mutable hardware configuration.
                  |
                  +---> `ProcessInfo`            - Information about running processes on the bot.
                  |
                  +---> `Transport.Supervisor`   - Consumes the bot's state and talks to the rest of the world.
                  |
                  +---> `Firmware.Supervisor`    - Communicates with the `arduino-firmware`.
  """

  require Farmbot.Logger
  require Logger
  use Supervisor

  @version Mix.Project.config()[:version]
  @commit Mix.Project.config()[:commit]

  @doc """
  Entry Point to Farmbot
  """
  def start(type, start_opts)

  def start(_, _start_opts) do
    case Supervisor.start_link(__MODULE__, [], [name: __MODULE__]) do
      {:ok, pid} -> {:ok, pid}
      error ->
        IO.puts "Failed to boot Farmbot: #{inspect error}"
        Farmbot.System.factory_reset(error)
        exit(error)
    end
  end

  def init([]) do
    Logger.remove_backend Logger.Backends.Console
    children = [
      supervisor(Farmbot.Logger.Supervisor, []),
      supervisor(Farmbot.System.Supervisor, []),
      supervisor(Farmbot.Bootstrap.Supervisor, [])
    ]

    Farmbot.Logger.info(1, "Booting Farmbot OS version: #{@version} - #{@commit}")
    opts = [strategy: :one_for_one]
    supervise(children, opts)
  end
end