Updated SSHkit (#314)

Updated SSHkit (#314)

sshkit got updated to fix some [deprecation warnings](https://github.com/bitcrowd/sshkit.ex/issues/147).

defmodule Bootleg.Mixfile do
  use Mix.Project

  @version "0.12.0"
  @source "https://github.com/labzero/bootleg"
  @homepage "https://labzero.github.io/bootleg/"

  def project do
    [
      app: :bootleg,
      version: @version,
      elixir: "~> 1.6",
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      elixirc_paths: elixirc_paths(Mix.env()),
      test_coverage: [tool: ExCoveralls],
      preferred_cli_env: [
        docs: :docs,
        "hex.publish": :docs,
        dialyzer: :dev,
        coveralls: :test,
        "coveralls.detail": :test,
        "coveralls.circle": :test,
        "coveralls.html": :test
      ],
      dialyzer: [plt_add_apps: [:mix, :sshkit, :ex_unit]],
      docs: docs(),
      aliases: aliases(),
      description: description(),
      deps: deps(),
      package: package(),
      source_url: @source,
      homepage_url: @homepage
    ]
  end

  def application do
    [extra_applications: [:logger, :sshkit, :mix]]
  end

  defp deps do
    [
      {:sshkit, "0.2.0"},
      {:ssh_client_key_api, "~> 0.2.1"},
      {:credo, "~> 0.10", only: [:dev, :test]},
      {:dialyxir, "~> 1.0.0-rc.6", only: [:dev], runtime: false},
      {:ex_doc, "~> 0.18", only: [:docs], runtime: false},
      {:excoveralls, "~> 0.10", only: [:test]},
      {:mock, "~> 0.3.3", only: [:test]},
      {:junit_formatter, "~> 2.0", only: [:test]},
      {:temp, "~> 0.4.3", only: [:test]},
      {:distillery, ">= 2.1.0", runtime: false}
    ]
  end

  defp aliases do
    [
      docs: [&mkdocs/1, "docs"]
    ]
  end

  defp mkdocs(_args) do
    docs = Path.join([File.cwd!(), "script", "docs", "docs.sh"])
    {_, 0} = System.cmd(docs, ["build"], into: IO.stream(:stdio, :line))
  end

  defp docs do
    [
      source_url: @source,
      homepage_url: @homepage,
      main: "home"
    ]
  end

  defp description do
    "Simple deployment and server automation for Elixir."
  end

  defp package do
    [
      maintainers: ["labzero", "Brien Wankel", "Ned Holets", "Rob Adams"],
      licenses: ["MIT"],
      links: %{"GitHub" => @source, "Homepage" => @homepage}
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
  defp elixirc_paths(:dev), do: ["lib", "web"]
  defp elixirc_paths(_), do: ["lib", "web"]
end

defmodule Bootleg.Mixfile do
  use Mix.Project

  @version "0.12.0"
  @source "https://github.com/labzero/bootleg"
  @homepage "https://labzero.github.io/bootleg/"

  def project do
    [
      app: :bootleg,
      version: @version,
      elixir: "~> 1.6",
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      elixirc_paths: elixirc_paths(Mix.env()),
      test_coverage: [tool: ExCoveralls],
      preferred_cli_env: [
        docs: :docs,
        "hex.publish": :docs,
        dialyzer: :dev,
        coveralls: :test,
        "coveralls.detail": :test,
        "coveralls.circle": :test,
        "coveralls.html": :test
      ],
      dialyzer: [plt_add_apps: [:mix, :sshkit, :ex_unit]],
      docs: docs(),
      aliases: aliases(),
      description: description(),
      deps: deps(),
      package: package(),
      source_url: @source,
      homepage_url: @homepage
    ]
  end

  def application do
    [extra_applications: [:logger, :sshkit, :mix]]
  end

  defp deps do
    [
      {:sshkit, "0.1.0"},
      {:ssh_client_key_api, "~> 0.2.1"},
      {:credo, "~> 0.10", only: [:dev, :test]},
      {:dialyxir, "~> 1.0.0-rc.6", only: [:dev], runtime: false},
      {:ex_doc, "~> 0.18", only: [:docs], runtime: false},
      {:excoveralls, "~> 0.10", only: [:test]},
      {:mock, "~> 0.3.3", only: [:test]},
      {:junit_formatter, "~> 2.0", only: [:test]},
      {:temp, "~> 0.4.3", only: [:test]},
      {:distillery, ">= 2.1.0", runtime: false}
    ]
  end

  defp aliases do
    [
      docs: [&mkdocs/1, "docs"]
    ]
  end

  defp mkdocs(_args) do
    docs = Path.join([File.cwd!(), "script", "docs", "docs.sh"])
    {_, 0} = System.cmd(docs, ["build"], into: IO.stream(:stdio, :line))
  end

  defp docs do
    [
      source_url: @source,
      homepage_url: @homepage,
      main: "home"
    ]
  end

  defp description do
    "Simple deployment and server automation for Elixir."
  end

  defp package do
    [
      maintainers: ["labzero", "Brien Wankel", "Ned Holets", "Rob Adams"],
      licenses: ["MIT"],
      links: %{"GitHub" => @source, "Homepage" => @homepage}
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
  defp elixirc_paths(:dev), do: ["lib", "web"]
  defp elixirc_paths(_), do: ["lib", "web"]
end


version bump (^･ｪ･^)

version bump (^･ｪ･^)

defmodule Glasnost.Mixfile do
  use Mix.Project

  def project do
    [app: :glasnost,
     version: "0.15.0",
     elixir: "~> 1.4",
     elixirc_paths: elixirc_paths(Mix.env),
     compilers: [:phoenix, :gettext] ++ Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     aliases: aliases(),
     deps: deps()]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [mod: {Glasnost.Application, []},
     extra_applications: [:logger, :ex_golos]]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_),     do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "1.3.0", override: true},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.2"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:ex_golos, github: "cyberpunk-ventures/ex_golos"},
     {:steemex, github: "cyberpunk-ventures/steemex"},
     {:phoenix_expug, ">= 0.0.0"},
     {:calendar, ">= 0.16.0"},
     {:distillery, ">= 0.0.0"},
     {:httpoison, ">= 0.0.0"},
     {:hound, ">= 0.0.0"},
     {:con_cache, "~> 0.12.0"},
     {:cowboy, "~> 1.0"},
     {:floki, ">= 0.0.0"},
     {:absinthe, "~> 1.3.1"},
     {:absinthe_plug, "~> 1.3.0"},
     {:credo, ">= 0.0.0", only: :dev},
     {:exfmt, github: "lpil/exfmt", only: :dev},
     {:earmark, ">= 0.0.0"}]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
     "ecto.reset": ["ecto.drop", "ecto.setup"],
     "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end
end

defmodule Glasnost.Mixfile do
  use Mix.Project

  def project do
    [app: :glasnost,
     version: "0.14.1",
     elixir: "~> 1.4",
     elixirc_paths: elixirc_paths(Mix.env),
     compilers: [:phoenix, :gettext] ++ Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     aliases: aliases(),
     deps: deps()]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [mod: {Glasnost.Application, []},
     extra_applications: [:logger, :ex_golos]]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_),     do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "1.3.0", override: true},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.2"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:ex_golos, github: "cyberpunk-ventures/ex_golos"},
     {:steemex, github: "cyberpunk-ventures/steemex"},
     {:phoenix_expug, ">= 0.0.0"},
     {:calendar, ">= 0.16.0"},
     {:distillery, ">= 0.0.0"},
     {:httpoison, ">= 0.0.0"},
     {:hound, ">= 0.0.0"},
     {:con_cache, "~> 0.12.0"},
     {:cowboy, "~> 1.0"},
     {:floki, ">= 0.0.0"},
     {:absinthe, "~> 1.3.1"},
     {:absinthe_plug, "~> 1.3.0"},
     {:credo, ">= 0.0.0", only: :dev},
     {:exfmt, github: "lpil/exfmt", only: :dev},
     {:earmark, ">= 0.0.0"}]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
     "ecto.reset": ["ecto.drop", "ecto.setup"],
     "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end
end


bump to 0.7.0

bump to 0.7.0

defmodule Protobuf.Mixfile do
  use Mix.Project

  @version "0.7.0"

  def project do
    [
      app: :protobuf,
      version: @version,
      elixir: "~> 1.4",
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      escript: escript(),
      description: description(),
      package: package()
    ]
  end

  def application, do: [extra_applications: [:logger]]

  defp deps do
    [
      {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
      {:credo, "~> 0.8", only: [:dev, :test], runtime: false},
      {:ex_doc, "~> 0.14", only: :dev, runtime: false},
      {:eqc_ex, "~> 1.4", only: [:dev, :test]}
    ]
  end

  defp escript do
    [main_module: Protobuf.Protoc.CLI, name: "protoc-gen-elixir", app: nil]
  end

  defp description do
    "A pure Elixir implementation of Google Protobuf."
  end

  defp package do
    [
      maintainers: ["Bing Han"],
      licenses: ["MIT"],
      links: %{"GitHub" => "https://github.com/tony612/protobuf-elixir"},
      files:
        ~w(mix.exs README.md lib/google lib/protobuf lib/protobuf.ex src config LICENSE priv/templates .formatter.exs)
    ]
  end
end

defmodule Protobuf.Mixfile do
  use Mix.Project

  @version "0.6.3"

  def project do
    [
      app: :protobuf,
      version: @version,
      elixir: "~> 1.4",
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      escript: escript(),
      description: description(),
      package: package()
    ]
  end

  def application, do: [extra_applications: [:logger]]

  defp deps do
    [
      {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
      {:credo, "~> 0.8", only: [:dev, :test], runtime: false},
      {:ex_doc, "~> 0.14", only: :dev, runtime: false},
      {:eqc_ex, "~> 1.4", only: [:dev, :test]}
    ]
  end

  defp escript do
    [main_module: Protobuf.Protoc.CLI, name: "protoc-gen-elixir", app: nil]
  end

  defp description do
    "A pure Elixir implementation of Google Protobuf."
  end

  defp package do
    [
      maintainers: ["Bing Han"],
      licenses: ["MIT"],
      links: %{"GitHub" => "https://github.com/tony612/protobuf-elixir"},
      files:
        ~w(mix.exs README.md lib/google lib/protobuf lib/protobuf.ex src config LICENSE priv/templates .formatter.exs)
    ]
  end
end


v0.0.11

defmodule Dogma.Mixfile do
  use Mix.Project

  @version "0.0.11"

  def project do
    [
      app: :dogma,
      version: @version,
      elixir: "~> 1.0",
      deps: deps,
      build_embedded: Mix.env == :prod,
      start_permanent: Mix.env == :prod,
      test_coverage: [tool: ExCoveralls],

      name: "Dogma",
      source_url: "https://github.com/lpil/dogma",
      description: "A code style linter for Elixir, powered by shame.",
      package: [
        maintainers: ["Louis Pilfold"],
        licenses: ["MIT"],
        links: %{ "GitHub" => "https://github.com/lpil/dogma" },
      ]
    ]
  end

  def application do
    [
      applications: []
    ]
  end

  defp deps do
    [
      # Test framework
      {:shouldi, only: :test},
      # App config test helper
      {:temporary_env, only: :test},
      # Test coverage checker
      {:excoveralls, only: :test},
      # Automatic test runner
      {:mix_test_watch, only: :dev},

      # Benchmark framework
      {:benchfella, only: :dev},

      # Documentation checker
      {:inch_ex, only: ~w(dev test docs)a},
      # Markdown processor
      {:earmark, only: :dev},
      # Documentation generator
      {:ex_doc, only: :dev},

      # JSON encoder
      {:poison, "~> 1.0"},
    ]
  end
end

defmodule Dogma.Mixfile do
  use Mix.Project

  @version "0.0.10"

  def project do
    [
      app: :dogma,
      version: @version,
      elixir: "~> 1.0",
      deps: deps,
      build_embedded: Mix.env == :prod,
      start_permanent: Mix.env == :prod,
      test_coverage: [tool: ExCoveralls],

      name: "Dogma",
      source_url: "https://github.com/lpil/dogma",
      description: "A code style linter for Elixir, powered by shame.",
      package: [
        maintainers: ["Louis Pilfold"],
        licenses: ["MIT"],
        links: %{ "GitHub" => "https://github.com/lpil/dogma" },
      ]
    ]
  end

  def application do
    [
      applications: []
    ]
  end

  defp deps do
    [
      # Test framework
      {:shouldi, only: :test},
      # App config test helper
      {:temporary_env, only: :test},
      # Test coverage checker
      {:excoveralls, only: :test},
      # Automatic test runner
      {:mix_test_watch, only: :dev},

      # Benchmark framework
      {:benchfella, only: :dev},

      # Documentation checker
      {:inch_ex, only: ~w(dev test docs)a},
      # Markdown processor
      {:earmark, only: :dev},
      # Documentation generator
      {:ex_doc, only: :dev},

      # JSON encoder
      {:poison, "~> 1.0"},
    ]
  end
end


Remove default commit hash

Remove default commit hash

Stop being lazy.

defmodule BorsNG.Worker.AttemptorTest do
  use BorsNG.Worker.TestCase

  alias BorsNG.Worker.Attemptor
  alias BorsNG.Database.Attempt
  alias BorsNG.Database.AttemptStatus
  alias BorsNG.Database.Installation
  alias BorsNG.Database.Patch
  alias BorsNG.Database.Project
  alias BorsNG.Database.Repo
  alias BorsNG.GitHub

  setup do
    inst = %Installation{installation_xref: 91}
    |> Repo.insert!()
    proj = %Project{
      installation_id: inst.id,
      repo_xref: 14,
      staging_branch: "staging",
      trying_branch: "trying"}
    |> Repo.insert!()
    {:ok, inst: inst, proj: proj}
  end

  def new_patch(proj, pr_xref, commit) do
    %Patch{
      project_id: proj.id,
      pr_xref: pr_xref,
      into_branch: "master",
      commit: commit}
    |> Repo.insert!()
  end

  def new_attempt(patch, state) do
    %Attempt{patch_id: patch.id, state: state, into_branch: "master"}
    |> Repo.insert!()
  end

  test "rejects running patches", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{},
        comments: %{1 => []},
        statuses: %{},
        files: %{}
      }})
    patch = new_patch(proj, 1, nil)
    _attempt = new_attempt(patch, 0)
    Attemptor.handle_cast({:tried, patch.id, ""}, proj.id)
    state = GitHub.ServerMock.get_state()
    assert state == %{
      {{:installation, 91}, 14} => %{
        branches: %{},
        comments: %{
          1 => ["Not awaiting review"]
          },
        statuses: %{},
        files: %{}
      }}
  end

  test "infer from .travis.yml", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{".travis.yml" => ""}}
      }})
    patch = new_patch(proj, 1, "N")
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    [status] = Repo.all(AttemptStatus)
    assert status.identifier == "continuous-integration/travis-ci/push"
  end

  test "infer from .travis.yml and appveyor.yml", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{".travis.yml" => "", "appveyor.yml" => ""}}
      }})
    patch = new_patch(proj, 1, "N")
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    statuses = Repo.all(AttemptStatus)
    assert Enum.any?(statuses,
      &(&1.identifier == "continuous-integration/travis-ci/push"))
    assert Enum.any?(statuses,
      &(&1.identifier == "continuous-integration/appveyor/branch"))
  end

  test "full runthrough (with polling fallback)", %{proj: proj} do
    # Attempts start running immediately
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }})
    patch = new_patch(proj, 1, "N")
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    # Polling should not change that.
    Attemptor.handle_info(:poll, proj.id)
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    # Mark the CI as having finished.
    # At this point, just running should still do nothing.
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }})
    Attemptor.handle_info(:poll, proj.id)
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    # Finally, an actual poll should finish it.
    attempt
    |> Attempt.changeset(%{last_polled: 0})
    |> Repo.update!()
    Attemptor.handle_info(:poll, proj.id)
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 2
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => ["# Build succeeded\n  * ci"]},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
  end
end

defmodule BorsNG.Worker.AttemptorTest do
  use BorsNG.Worker.TestCase

  alias BorsNG.Worker.Attemptor
  alias BorsNG.Database.Attempt
  alias BorsNG.Database.AttemptStatus
  alias BorsNG.Database.Installation
  alias BorsNG.Database.Patch
  alias BorsNG.Database.Project
  alias BorsNG.Database.Repo
  alias BorsNG.GitHub

  setup do
    inst = %Installation{installation_xref: 91}
    |> Repo.insert!()
    proj = %Project{
      installation_id: inst.id,
      repo_xref: 14,
      staging_branch: "staging",
      trying_branch: "trying"}
    |> Repo.insert!()
    {:ok, inst: inst, proj: proj}
  end

  def new_patch(proj, pr_xref, commit \\ "N") do
    %Patch{
      project_id: proj.id,
      pr_xref: pr_xref,
      into_branch: "master",
      commit: commit}
    |> Repo.insert!()
  end

  def new_attempt(patch, state) do
    %Attempt{patch_id: patch.id, state: state, into_branch: "master"}
    |> Repo.insert!()
  end

  test "rejects running patches", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{},
        comments: %{1 => []},
        statuses: %{},
        files: %{}
      }})
    patch = new_patch(proj, 1, nil)
    _attempt = new_attempt(patch, 0)
    Attemptor.handle_cast({:tried, patch.id, ""}, proj.id)
    state = GitHub.ServerMock.get_state()
    assert state == %{
      {{:installation, 91}, 14} => %{
        branches: %{},
        comments: %{
          1 => ["Not awaiting review"]
          },
        statuses: %{},
        files: %{}
      }}
  end

  test "infer from .travis.yml", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{".travis.yml" => ""}}
      }})
    patch = new_patch(proj, 1)
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    [status] = Repo.all(AttemptStatus)
    assert status.identifier == "continuous-integration/travis-ci/push"
  end

  test "infer from .travis.yml and appveyor.yml", %{proj: proj} do
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{".travis.yml" => "", "appveyor.yml" => ""}}
      }})
    patch = new_patch(proj, 1)
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    statuses = Repo.all(AttemptStatus)
    assert Enum.any?(statuses,
      &(&1.identifier == "continuous-integration/travis-ci/push"))
    assert Enum.any?(statuses,
      &(&1.identifier == "continuous-integration/appveyor/branch"))
  end

  test "full runthrough (with polling fallback)", %{proj: proj} do
    # Attempts start running immediately
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{"master" => "ini", "trying" => "", "trying.tmp" => ""},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }})
    patch = new_patch(proj, 1)
    Attemptor.handle_cast({:tried, patch.id, "test"}, proj.id)
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    # Polling should not change that.
    Attemptor.handle_info(:poll, proj.id)
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => []},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    # Mark the CI as having finished.
    # At this point, just running should still do nothing.
    GitHub.ServerMock.put_state(%{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }})
    Attemptor.handle_info(:poll, proj.id)
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 1
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => []},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
    # Finally, an actual poll should finish it.
    attempt
    |> Attempt.changeset(%{last_polled: 0})
    |> Repo.update!()
    Attemptor.handle_info(:poll, proj.id)
    attempt = Repo.get_by! Attempt, patch_id: patch.id
    assert attempt.state == 2
    assert GitHub.ServerMock.get_state() == %{
      {{:installation, 91}, 14} => %{
        branches: %{
          "master" => "ini",
          "trying" => "iniN"},
        comments: %{1 => ["# Build succeeded\n  * ci"]},
        statuses: %{"iniN" => [{"ci", :ok}]},
        files: %{"trying" => %{"bors.toml" => ~s/status = [ "ci" ]/}}
      }}
  end
end


make sure we dont break on no data collected (#15)

defmodule Benchee.Formatters.JSONIntegrationTest do
  use ExUnit.Case
  import ExUnit.CaptureIO

  @file_path "test.json"
  test "works just fine" do
    basic_test(
      time: 0.01,
      memory_time: 0.01,
      warmup: 0.02,
      formatters: [{Benchee.Formatters.JSON, file: @file_path}]
    )
  end

  test "doesn't blow up with no data collected" do
    capture_io(fn ->
      assert %Benchee.Suite{} =
               Benchee.run(
                 %{
                   "Sleep" => fn -> :timer.sleep(10) end
                 },
                 time: 0,
                 warmup: 0,
                 memory_time: 0
               )
    end)
  after
    File.rm(@file_path)
  end

  defp basic_test(options) do
    capture_io(fn ->
      Benchee.run(
        %{
          "Sleep" => fn -> :timer.sleep(10) end,
          "List" => fn -> [:rand.uniform()] end
        },
        options
      )

      assert File.exists?(@file_path)
      content = File.read!(@file_path)
      [list, sleep] = Jason.decode!(content)

      assert %{
               "name" => "Sleep",
               "run_time_statistics" => %{"average" => _, "ips" => _},
               "run_times" => [run_time | _]
             } = sleep

      assert run_time > 10_000_000

      assert %{
               "name" => "List",
               "memory_usage_statistics" => %{"average" => _, "ips" => _},
               "memory_usages" => [376 | _]
             } = list
    end)
  after
    File.rm(@file_path)
  end
end

defmodule Benchee.Formatters.JSONIntegrationTest do
  use ExUnit.Case
  import ExUnit.CaptureIO

  @file_path "test.json"
  test "works just fine" do
    basic_test(
      time: 0.01,
      memory_time: 0.01,
      warmup: 0.02,
      formatters: [{Benchee.Formatters.JSON, file: @file_path}]
    )
  end

  defp basic_test(options) do
    capture_io(fn ->
      Benchee.run(
        %{
          "Sleep" => fn -> :timer.sleep(10) end,
          "List" => fn -> [:rand.uniform()] end
        },
        options
      )

      assert File.exists?(@file_path)
      content = File.read!(@file_path)
      [list, sleep] = Jason.decode!(content)

      assert %{
               "name" => "Sleep",
               "run_time_statistics" => %{"average" => _, "ips" => _},
               "run_times" => [run_time | _]
             } = sleep

      assert run_time > 10_000_000

      assert %{
               "name" => "List",
               "memory_usage_statistics" => %{"average" => _, "ips" => _},
               "memory_usages" => [376 | _]
             } = list
    end)
  after
    File.rm(@file_path)
  end
end


Can perform several cache queries at the same time

Cahce queries are no longer serialized, but create a task per query.

require Logger

defmodule Serverboards.Query.Cache do
  use GenServer

  def start_link(options \\ []) do
    GenServer.start_link(__MODULE__, nil, options)
  end

  def get(id, f, options) do
    # Logger.debug("Get #{inspect id}")
    case :ets.lookup(__MODULE__, id) do
      [{^id, :running}] ->
        get_at_genserver(id, f, options)
      [{^id, value}] ->
        value
      other ->
        get_at_genserver(id, f, options)
    end
  end

  # Make the server ask for the data
  defp get_at_genserver(id, f, options) do
    val = GenServer.call(__MODULE__, {:get, id, f}, 60_000)
    case options[:ttl] do
      nil -> :ok
      ttl ->
        Process.send_after(__MODULE__, {:remove, id}, ttl)
    end
    val
  end

  ## impl

  def init(nil) do
    :ets.new(__MODULE__, [:named_table])

    # status is a map of {id, f} to queue of requesters
    {:ok, %{}}
  end

  def calculate_and_add(id, f) do
    value = f.()
  end

  def handle_call({:get, id, f}, from, status) do
    case :ets.lookup(__MODULE__, id) do
      [{^id, value}] ->
        {:reply, value, status}
      _ ->
        # try get again, as several calls may have been queued
        prev_from = Map.get(status,id, [])
        if prev_from == [] do # the first calculates the value
          Task.start_link(fn ->
            value = f.()
            GenServer.cast(__MODULE__, {:insert, id, value})
          end)
        end
        status = Map.put(status, id, [from | prev_from] )
        {:noreply, status}
    end
  end

  def handle_info({:remove, id}, status) do
    :ets.delete(__MODULE__, id)
    {:noreply, status}
  end
  def handle_cast({:insert, id, value}, status) do
    :ets.insert(__MODULE__, {id, value})

    Enum.map(status[id], fn from ->
      GenServer.reply(from, value)
    end)
    status = Map.drop(status, [id])
    {:noreply, status}
  end
end

require Logger

defmodule Serverboards.Query.Cache do
  use GenServer

  def start_link(options \\ []) do
    GenServer.start_link(__MODULE__, nil, options)
  end

  def get(id, f, options) do
    # Logger.debug("Get #{inspect id}")
    case :ets.lookup(__MODULE__, id) do
      [{^id, value}] ->
        value
      other ->
        val = GenServer.call(__MODULE__, {:insert, id, f}, 60_000)
        case options[:ttl] do
          nil -> :ok
          ttl ->
            Process.send_after(__MODULE__, {:remove, id}, ttl)
        end
        val
    end
  end

  ## impl

  def init(nil) do
    :ets.new(__MODULE__, [:named_table])
    {:ok, :ok}
  end

  def handle_call({:insert, id, f}, _from, status) do
    # try get again, as several calls may have been queued
    val = case :ets.lookup(__MODULE__, id) do
      [{^id, value}] ->
        value
      _other ->
        # Logger.debug("Calculate value for #{inspect id}")
        value = f.()
        :ets.insert(__MODULE__, {id, value})
        value
    end
    {:reply, val, status}
  end

  def handle_info({:remove, id}, status) do
    :ets.delete(__MODULE__, id)
    {:noreply, status}
  end
end


Fix formatter integration test

Fix formatter integration test

Code.require_file("../test_helper.exs", __DIR__)

defmodule Code.Formatter.IntegrationTest do
  use ExUnit.Case, async: true

  import CodeFormatterHelpers

  test "empty documents" do
    assert_format "   ", ""
    assert_format "\n", ""
    assert_format ";", ""
  end

  test "function with multiple calls and case" do
    assert_same """
    def equivalent(string1, string2) when is_binary(string1) and is_binary(string2) do
      quoted1 = Code.string_to_quoted!(string1)
      quoted2 = Code.string_to_quoted!(string2)

      case not_equivalent(quoted1, quoted2) do
        {left, right} -> {:error, left, right}
        nil -> :ok
      end
    end
    """
  end

  test "function with long pipeline" do
    assert_same ~S"""
    def to_algebra!(string, opts \\ []) when is_binary(string) and is_list(opts) do
      string
      |> Code.string_to_quoted!(wrap_literals_in_blocks: true, unescape: false)
      |> block_to_algebra(state(opts))
      |> elem(0)
    end
    """
  end

  test "case with multiple multi-line arrows" do
    assert_same ~S"""
    case meta[:format] do
      :list_heredoc ->
        string = list |> List.to_string() |> escape_string(:heredoc)
        {@single_heredoc |> line(string) |> concat(@single_heredoc) |> force_unfit(), state}

      :charlist ->
        string = list |> List.to_string() |> escape_string(@single_quote)
        {@single_quote |> concat(string) |> concat(@single_quote), state}

      _other ->
        list_to_algebra(list, state)
    end
    """
  end

  test "function with long guards" do
    assert_same """
    defp module_attribute_read?({:@, _, [{var, _, var_context}]})
         when is_atom(var) and is_atom(var_context) do
      Code.Identifier.classify(var) == :callable_local
    end
    """
  end

  test "anonymous function with single clause and blocks" do
    assert_same """
    {args_doc, state} =
      Enum.reduce(args, {[], state}, fn quoted, {acc, state} ->
        {doc, state} = quoted_to_algebra(quoted, :block, state)
        doc = doc |> concat(nest(break(""), :reset)) |> group()
        {[doc | acc], state}
      end)
    """
  end

  test "anonymous function with long single clause and blocks" do
    assert_same """
    {function_count, call_count, total_time} =
      Enum.reduce(call_results, {0, 0, 0}, fn {_, {count, time}},
                                              {function_count, call_count, total_time} ->
        {function_count + 1, call_count + count, total_time + time}
      end)
    """
  end

  test "cond with long clause args" do
    assert_same """
    cond do
      parent_prec == prec and parent_assoc == side ->
        binary_op_to_algebra(op, op_string, left, right, context, state, op_info, nesting)

      parent_op in @required_parens_on_binary_operands or parent_prec > prec or
          (parent_prec == prec and parent_assoc != side) ->
        {operand, state} =
          binary_op_to_algebra(op, op_string, left, right, context, state, op_info, 2)

        {concat(concat("(", nest(operand, 1)), ")"), state}

      true ->
        binary_op_to_algebra(op, op_string, left, right, context, state, op_info, 2)
    end
    """
  end

  test "type with multiple |" do
    assert_same """
    @type t ::
            binary
            | :doc_nil
            | :doc_line
            | doc_string
            | doc_cons
            | doc_nest
            | doc_break
            | doc_group
            | doc_color
            | doc_force
            | doc_cancel
    """
  end

  test "spec with when keywords and |" do
    assert_same """
    @spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend
          when dest: pid | port | atom | {atom, node},
               msg: any,
               option: :noconnect | :nosuspend
    """

    assert_same """
    @spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend
          when dest:
                 pid
                 | port
                 | atom
                 | {atom, node}
                 | and_a_really_long_type_to_force_a_line_break
                 | followed_by_another_really_long_type
    """

    assert_same """
    @callback get_and_update(data, key, (value -> {get_value, value} | :pop)) :: {get_value, data}
              when get_value: var, data: container
    """
  end

  test "spec with multiple keys on type" do
    assert_same """
    @spec foo(%{(String.t() | atom) => any}) :: any
    """
  end

  test "multiple whens with new lines" do
    assert_same """
    def sleep(timeout)
        when is_integer(timeout) and timeout >= 0
        when timeout == :infinity do
      receive after: (timeout -> :ok)
    end
    """
  end

  test "function with operator and pipeline" do
    assert_same """
    defp apply_next_break_fits?({fun, meta, args}) when is_atom(fun) and is_list(args) do
      meta[:terminator] in [@double_heredoc, @single_heredoc] and
        fun |> Atom.to_string() |> String.starts_with?("sigil_")
    end
    """
  end

  test "mixed parens and no parens calls with anonymous function" do
    assert_same ~S"""
    node interface do
      resolve_type(fn
        %{__struct__: str}, _ ->
          str |> Model.Node.model_to_node_type()

        value, _ ->
          Logger.warn("Could not extract node type from value: #{inspect(value)}")
          nil
      end)
    end
    """
  end

  test "long defstruct definition" do
    assert_same """
    defstruct name: nil,
              module: nil,
              schema: nil,
              alias: nil,
              base_module: nil,
              web_module: nil,
              basename: nil,
              file: nil,
              test_file: nil
    """
  end

  test "mix of operators and arguments" do
    assert_same """
    def count(%{path: path, line_or_bytes: bytes}) do
      case File.stat(path) do
        {:ok, %{size: 0}} -> {:error, __MODULE__}
        {:ok, %{size: size}} -> {:ok, div(size, bytes) + if(rem(size, bytes) == 0, do: 0, else: 1)}
        {:error, reason} -> raise File.Error, reason: reason, action: "stream", path: path
      end
    end
    """
  end

  test "mix of left and right operands" do
    assert_same """
    defp server_get_modules(handlers) do
      for(handler(module: module) <- handlers, do: module)
      |> :ordsets.from_list()
      |> :ordsets.to_list()
    end
    """

    assert_same """
    neighbours = for({_, _} = t <- neighbours, do: t) |> :sets.from_list()
    """
  end

  test "long expression with single line anonymous function" do
    assert_same """
    for_many(uniq_list_of(integer(1..10000)), fn list ->
      assert Enum.uniq(list) == list
    end)
    """
  end

  test "long comprehension" do
    assert_same """
    for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
        Keyword.get(opts, :app, true),
        Keyword.get(opts, :runtime, true),
        not Keyword.get(opts, :optional, false),
        app not in included_applications,
        app not in included_applications,
        do: app
    """
  end

  test "short comprehensions" do
    assert_same """
    for {protocol, :protocol, _beam} <- removed_metadata,
        remove_consolidated(protocol, output),
        do: {protocol, true},
        into: %{}
    """
  end

  test "comprehensions with when" do
    assert_same """
    for {key, value} when is_atom(key) <- Map.to_list(map),
        key = Atom.to_string(key),
        String.starts_with?(key, hint) do
      %{kind: :map_key, name: key, value_is_map: is_map(value)}
    end
    """

    assert_same """
    with {_, doc} when unquote(doc_attr?) <-
           Module.get_attribute(__MODULE__, unquote(name), unquote(escaped)),
         do: doc
    """
  end

  test "next break fits followed by inline tuple" do
    assert_same """
    assert ExUnit.Filters.eval([line: "1"], [:line], %{line: 3, describe_line: 2}, tests) ==
             {:error, "due to line filter"}
    """
  end

  test "try/catch with clause comment" do
    assert_same """
    def format_error(reason) do
      try do
        do_format_error(reason)
      catch
        # A user could create an error that looks like a built-in one
        # causing an error.
        :error, _ ->
          inspect(reason)
      end
    end
    """
  end

  test "case with when and clause comment" do
    assert_same """
    case decomposition do
      # Decomposition
      <<h, _::binary>> when h != ?< ->
        decomposition =
          decomposition
          |> :binary.split(" ", [:global])
          |> Enum.map(&String.to_integer(&1, 16))

        Map.put(dacc, String.to_integer(codepoint, 16), decomposition)

      _ ->
        dacc
    end
    """
  end

  test "do/end inside binary" do
    assert_same """
    <<if true do
        "hello"
      end::binary>>
    """
  end

  test "anonymous function with parens around integer argument" do
    bad = """
    fn (1) -> "hello" end
    """

    assert_format bad, """
    fn 1 -> "hello" end
    """
  end

  test "no parens keywords at the end of the line" do
    bad = """
    defmodule Mod do
      def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
      def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
    end
    """

    assert_format bad, """
    defmodule Mod do
      def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
        do: token_list_downcase(rest, acc)

      def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
        do: token_list_downcase(rest, acc)
    end
    """
  end

  test "do at the end of the line" do
    bad = """
    foo bar, baz, quux do
      :ok
    end
    """

    good = """
    foo bar,
        baz,
        quux do
      :ok
    end
    """

    assert_format bad, good, line_length: 18
  end

  test "keyword lists in last line" do
    assert_same """
    content =
      config(VeryLongModuleNameThatWillCauseBreak, "new.html",
        conn: conn,
        changeset: changeset,
        categories: categories
      )
    """

    assert_same """
    content =
      config VeryLongModuleNameThatWillCauseBreak, "new.html",
        conn: conn,
        changeset: changeset,
        categories: categories
    """
  end

  test "do at the end of the line with single argument" do
    bad = """
    defmodule Location do
      def new(line, column) when is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
        %{column: column, line: line}
      end
    end
    """

    assert_format bad, """
    defmodule Location do
      def new(line, column)
          when is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
        %{column: column, line: line}
      end
    end
    """
  end

  test "tuples as trees" do
    bad = """
    @document Parser.parse(
      {"html", [], [
         {"head", [], []},
         {"body", [], [
              {"div", [], [
                  {"p", [], ["1"]},
                  {"p", [], ["2"]},
                  {"div", [], [
                      {"p", [], ["3"]},
                      {"p", [], ["4"]}]},
                  {"p", [], ["5"]}]}]}]})
    """

    assert_format bad, """
    @document Parser.parse(
                {"html", [],
                 [
                   {"head", [], []},
                   {"body", [],
                    [
                      {"div", [],
                       [
                         {"p", [], ["1"]},
                         {"p", [], ["2"]},
                         {"div", [],
                          [
                            {"p", [], ["3"]},
                            {"p", [], ["4"]}
                          ]},
                         {"p", [], ["5"]}
                       ]}
                    ]}
                 ]}
              )
    """
  end

  test "first argument in a call without parens with comments" do
    assert_same """
    with bar ::
           :ok
           | :invalid
           # | :unknown
           | :other
    """

    assert_same """
    @spec bar ::
            :ok
            | :invalid
            # | :unknown
            | :other
    """
  end

  test "when with keywords inside call" do
    assert_same """
    quote((bar(foo(1)) when bat: foo(1)), [])
    """

    assert_same """
    quote(do: (bar(foo(1)) when bat: foo(1)), line: 1)
    """

    assert_same """
    typespec(quote(do: (bar(foo(1)) when bat: foo(1))), [foo: 1], [])
    """
  end

  test "false positive sigil" do
    assert_same """
    def sigil_d(<<year::2-bytes, "-", month::2-bytes, "-", day::2-bytes>>, calendar) do
      ymd(year, month, day, calendar)
    end
    """
  end

  test "newline after stab" do
    assert_same """
    capture_io(":erl. mof*,,l", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:":", 1}, {:atom, 1, :erl}, {:dot, 1}], 1}

      expected_tokens = [{:atom, 1, :mof}, {:*, 1}, {:",", 1}, {:",", 1}, {:atom, 1, :l}]
      assert :io.scan_erl_form('>') == {:ok, expected_tokens, 1}

      assert :io.scan_erl_form('>') == {:eof, 1}
    end)
    """
  end

  test "capture with operators" do
    assert_same """
    "this works" |> (&String.upcase/1) |> (&String.downcase/1)
    """

    assert_same """
    "this works" || (&String.upcase/1) || (&String.downcase/1)
    """

    assert_same """
    "this works" == (&String.upcase/1) == (&String.downcase/1)
    """

    bad = """
    "this works" = (&String.upcase/1) = (&String.downcase/1)
    """

    assert_format bad, """
    "this works" = (&String.upcase/1) = &String.downcase/1
    """

    bad = """
    "this works" ++ (&String.upcase/1) ++ (&String.downcase/1)
    """

    assert_format bad, """
    "this works" ++ (&String.upcase/1) ++ &String.downcase/1
    """

    bad = """
    "this works" +++ (&String.upcase/1) +++ (&String.downcase/1)
    """

    assert_format bad, """
    "this works" +++ (&String.upcase/1) +++ &String.downcase/1
    """

    bad = """
    "this works" | (&String.upcase/1) | (&String.downcase/1)
    """

    assert_format bad, """
    "this works" | (&String.upcase/1) | &String.downcase/1
    """

    bad = ~S"""
    "this works" \\ (&String.upcase/1) \\ (&String.downcase/1)
    """

    assert_format bad, ~S"""
    "this works" \\ &String.upcase/1 \\ &String.downcase/1
    """
  end

  test "multiline expression inside interpolation" do
    bad = ~S"""
    Logger.info("Example: #{
      inspect(%{
        a: 1,
        b: 2
      })
    }")
    """

    assert_format bad, ~S"""
    Logger.info("Example: #{inspect(%{a: 1, b: 2})}")
    """
  end

  test "comment inside operator with when" do
    bad = """
    raise function(x) ::
            # Comment
            any
    """

    assert_format bad, """
    # Comment
    raise function(x) ::
            any
    """

    bad = """
    raise function(x) ::
            # Comment
            any
          when x: any
    """

    assert_format bad, """
    raise function(x) ::
            any
          # Comment
          when x: any
    """

    bad = """
    @spec function(x) ::
            # Comment
            any
          when x: any
    """

    assert_format bad, """
    @spec function(x) ::
            any
          # Comment
          when x: any
    """

    bad = """
    @spec function(x) ::
            # Comment
            any
          when x
          when y
    """

    assert_format bad, """
    @spec function(x) ::
            any
          # Comment
          when x
          when y
    """
  end
end

Code.require_file("../test_helper.exs", __DIR__)

defmodule Code.Formatter.IntegrationTest do
  use ExUnit.Case, async: true

  import CodeFormatterHelpers

  test "empty documents" do
    assert_format "   ", ""
    assert_format "\n", ""
    assert_format ";", ""
  end

  test "function with multiple calls and case" do
    assert_same """
    def equivalent(string1, string2) when is_binary(string1) and is_binary(string2) do
      quoted1 = Code.string_to_quoted!(string1)
      quoted2 = Code.string_to_quoted!(string2)

      case not_equivalent(quoted1, quoted2) do
        {left, right} -> {:error, left, right}
        nil -> :ok
      end
    end
    """
  end

  test "function with long pipeline" do
    assert_same ~S"""
    def to_algebra!(string, opts \\ []) when is_binary(string) and is_list(opts) do
      string
      |> Code.string_to_quoted!(wrap_literals_in_blocks: true, unescape: false)
      |> block_to_algebra(state(opts))
      |> elem(0)
    end
    """
  end

  test "case with multiple multi-line arrows" do
    assert_same ~S"""
    case meta[:format] do
      :list_heredoc ->
        string = list |> List.to_string() |> escape_string(:heredoc)
        {@single_heredoc |> line(string) |> concat(@single_heredoc) |> force_unfit(), state}

      :charlist ->
        string = list |> List.to_string() |> escape_string(@single_quote)
        {@single_quote |> concat(string) |> concat(@single_quote), state}

      _other ->
        list_to_algebra(list, state)
    end
    """
  end

  test "function with long guards" do
    assert_same """
    defp module_attribute_read?({:@, _, [{var, _, var_context}]})
         when is_atom(var) and is_atom(var_context) do
      Code.Identifier.classify(var) == :callable_local
    end
    """
  end

  test "anonymous function with single clause and blocks" do
    assert_same """
    {args_doc, state} =
      Enum.reduce(args, {[], state}, fn quoted, {acc, state} ->
        {doc, state} = quoted_to_algebra(quoted, :block, state)
        doc = doc |> concat(nest(break(""), :reset)) |> group()
        {[doc | acc], state}
      end)
    """
  end

  test "anonymous function with long single clause and blocks" do
    assert_same """
    {function_count, call_count, total_time} =
      Enum.reduce(call_results, {0, 0, 0}, fn {_, {count, time}},
                                              {function_count, call_count, total_time} ->
        {function_count + 1, call_count + count, total_time + time}
      end)
    """
  end

  test "cond with long clause args" do
    assert_same """
    cond do
      parent_prec == prec and parent_assoc == side ->
        binary_op_to_algebra(op, op_string, left, right, context, state, op_info, nesting)

      parent_op in @required_parens_on_binary_operands or parent_prec > prec or
          (parent_prec == prec and parent_assoc != side) ->
        {operand, state} =
          binary_op_to_algebra(op, op_string, left, right, context, state, op_info, 2)

        {concat(concat("(", nest(operand, 1)), ")"), state}

      true ->
        binary_op_to_algebra(op, op_string, left, right, context, state, op_info, 2)
    end
    """
  end

  test "type with multiple |" do
    assert_same """
    @type t ::
            binary
            | :doc_nil
            | :doc_line
            | doc_string
            | doc_cons
            | doc_nest
            | doc_break
            | doc_group
            | doc_color
            | doc_force
            | doc_cancel
    """
  end

  test "spec with when keywords and |" do
    assert_same """
    @spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend
          when dest: pid | port | atom | {atom, node},
               msg: any,
               option: :noconnect | :nosuspend
    """

    assert_same """
    @spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend
          when dest:
                 pid
                 | port
                 | atom
                 | {atom, node}
                 | and_a_really_long_type_to_force_a_line_break
                 | followed_by_another_really_long_type
    """

    assert_same """
    @callback get_and_update(data, key, (value -> {get_value, value} | :pop)) :: {get_value, data}
              when get_value: var, data: container
    """
  end

  test "spec with multiple keys on type" do
    assert_same """
    @spec foo(%{(String.t() | atom) => any}) :: any
    """
  end

  test "multiple whens with new lines" do
    assert_same """
    def sleep(timeout)
        when is_integer(timeout) and timeout >= 0
        when timeout == :infinity do
      receive after: (timeout -> :ok)
    end
    """
  end

  test "function with operator and pipeline" do
    assert_same """
    defp apply_next_break_fits?({fun, meta, args}) when is_atom(fun) and is_list(args) do
      meta[:terminator] in [@double_heredoc, @single_heredoc] and
        fun |> Atom.to_string() |> String.starts_with?("sigil_")
    end
    """
  end

  test "mixed parens and no parens calls with anonymous function" do
    assert_same ~S"""
    node interface do
      resolve_type(fn
        %{__struct__: str}, _ ->
          str |> Model.Node.model_to_node_type()

        value, _ ->
          Logger.warn("Could not extract node type from value: #{inspect(value)}")
          nil
      end)
    end
    """
  end

  test "long defstruct definition" do
    assert_same """
    defstruct name: nil,
              module: nil,
              schema: nil,
              alias: nil,
              base_module: nil,
              web_module: nil,
              basename: nil,
              file: nil,
              test_file: nil
    """
  end

  test "mix of operators and arguments" do
    assert_same """
    def count(%{path: path, line_or_bytes: bytes}) do
      case File.stat(path) do
        {:ok, %{size: 0}} -> {:error, __MODULE__}
        {:ok, %{size: size}} -> {:ok, div(size, bytes) + if(rem(size, bytes) == 0, do: 0, else: 1)}
        {:error, reason} -> raise File.Error, reason: reason, action: "stream", path: path
      end
    end
    """
  end

  test "mix of left and right operands" do
    assert_same """
    defp server_get_modules(handlers) do
      for(handler(module: module) <- handlers, do: module)
      |> :ordsets.from_list()
      |> :ordsets.to_list()
    end
    """

    assert_same """
    neighbours = for({_, _} = t <- neighbours, do: t) |> :sets.from_list()
    """
  end

  test "long expression with single line anonymous function" do
    assert_same """
    for_many(uniq_list_of(integer(1..10000)), fn list ->
      assert Enum.uniq(list) == list
    end)
    """
  end

  test "long comprehension" do
    assert_same """
    for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
        Keyword.get(opts, :app, true),
        Keyword.get(opts, :runtime, true),
        not Keyword.get(opts, :optional, false),
        app not in included_applications,
        app not in included_applications,
        do: app
    """
  end

  test "short comprehensions" do
    assert_same """
    for {protocol, :protocol, _beam} <- removed_metadata,
        remove_consolidated(protocol, output),
        do: {protocol, true},
        into: %{}
    """
  end

  test "comprehensions with when" do
    assert_same """
    for {key, value} when is_atom(key) <- Map.to_list(map),
        key = Atom.to_string(key),
        String.starts_with?(key, hint) do
      %{kind: :map_key, name: key, value_is_map: is_map(value)}
    end
    """

    assert_same """
    with {_, doc} when unquote(doc_attr?) <-
           Module.get_attribute(__MODULE__, unquote(name), unquote(escaped)),
         do: doc
    """
  end

  test "next break fits followed by inline tuple" do
    assert_same """
    assert ExUnit.Filters.eval([line: "1"], [:line], %{line: 3, describe_line: 2}, tests) ==
             {:error, "due to line filter"}
    """
  end

  test "try/catch with clause comment" do
    assert_same """
    def format_error(reason) do
      try do
        do_format_error(reason)
      catch
        # A user could create an error that looks like a built-in one
        # causing an error.
        :error, _ ->
          inspect(reason)
      end
    end
    """
  end

  test "case with when and clause comment" do
    assert_same """
    case decomposition do
      # Decomposition
      <<h, _::binary>> when h != ?< ->
        decomposition =
          decomposition
          |> :binary.split(" ", [:global])
          |> Enum.map(&String.to_integer(&1, 16))

        Map.put(dacc, String.to_integer(codepoint, 16), decomposition)

      _ ->
        dacc
    end
    """
  end

  test "do/end inside binary" do
    assert_same """
    <<if true do
        "hello"
      end::binary>>
    """
  end

  test "anonymous function with parens around integer argument" do
    bad = """
    fn (1) -> "hello" end
    """

    assert_format bad, """
    fn 1 -> "hello" end
    """
  end

  test "no parens keywords at the end of the line" do
    bad = """
    defmodule Mod do
      def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
      def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
    end
    """

    assert_format bad, """
    defmodule Mod do
      def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
        do: token_list_downcase(rest, acc)

      def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
        do: token_list_downcase(rest, acc)
    end
    """
  end

  test "do at the end of the line" do
    bad = """
    foo bar, baz, quux do
      :ok
    end
    """

    good = """
    foo bar,
        baz,
        quux do
      :ok
    end
    """

    assert_format bad, good, line_length: 18
  end

  test "keyword lists in last line" do
    assert_same """
    content =
      config(VeryLongModuleNameThatWillCauseBreak, "new.html",
        conn: conn,
        changeset: changeset,
        categories: categories
      )
    """

    assert_same """
    content =
      config VeryLongModuleNameThatWillCauseBreak, "new.html",
        conn: conn,
        changeset: changeset,
        categories: categories
    """
  end

  test "do at the end of the line with single argument" do
    bad = """
    defmodule Location do
      def new(line, column) when is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
        %{column: column, line: line}
      end
    end
    """

    assert_format bad, """
    defmodule Location do
      def new(line, column)
          when is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
        %{column: column, line: line}
      end
    end
    """
  end

  test "tuples as trees" do
    bad = """
    @document Parser.parse(
      {"html", [], [
         {"head", [], []},
         {"body", [], [
              {"div", [], [
                  {"p", [], ["1"]},
                  {"p", [], ["2"]},
                  {"div", [], [
                      {"p", [], ["3"]},
                      {"p", [], ["4"]}]},
                  {"p", [], ["5"]}]}]}]})
    """

    assert_format bad, """
    @document Parser.parse(
                {"html", [],
                 [
                   {"head", [], []},
                   {"body", [],
                    [
                      {"div", [],
                       [
                         {"p", [], ["1"]},
                         {"p", [], ["2"]},
                         {"div", [],
                          [
                            {"p", [], ["3"]},
                            {"p", [], ["4"]}
                          ]},
                         {"p", [], ["5"]}
                       ]}
                    ]}
                 ]}
              )
    """
  end

  test "first argument in a call without parens with comments" do
    assert_same """
    with bar ::
           :ok
           | :invalid
           # | :unknown
           | :other
    """

    assert_same """
    @spec bar ::
            :ok
            | :invalid
            # | :unknown
            | :other
    """
  end

  test "when with keywords inside call" do
    assert_same """
    quote((bar(foo(1)) when bat: foo(1)), [])
    """

    assert_same """
    quote(do: (bar(foo(1)) when bat: foo(1)), line: 1)
    """

    assert_same """
    typespec(quote(do: (bar(foo(1)) when bat: foo(1))), [foo: 1], [])
    """
  end

  test "false positive sigil" do
    assert_same """
    def sigil_d(<<year::2-bytes, "-", month::2-bytes, "-", day::2-bytes>>, calendar) do
      ymd(year, month, day, calendar)
    end
    """
  end

  test "newline after stab" do
    assert_same """
    capture_io(":erl. mof*,,l", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:":", 1}, {:atom, 1, :erl}, {:dot, 1}], 1}

      expected_tokens = [{:atom, 1, :mof}, {:*, 1}, {:",", 1}, {:",", 1}, {:atom, 1, :l}]
      assert :io.scan_erl_form('>') == {:ok, expected_tokens, 1}

      assert :io.scan_erl_form('>') == {:eof, 1}
    end)
    """
  end

  test "capture with operators" do
    assert_same """
    "this works" |> (&String.upcase/1) |> (&String.downcase/1)
    """

    assert_same """
    "this works" || (&String.upcase/1) || (&String.downcase/1)
    """

    assert_same """
    "this works" == (&String.upcase/1) == (&String.downcase/1)
    """

    bad = """
    "this works" = (&String.upcase/1) = (&String.downcase/1)
    """

    assert_format bad, """
    "this works" = (&String.upcase/1) = &String.downcase/1
    """

    bad = """
    "this works" ++ (&String.upcase/1) ++ (&String.downcase/1)
    """

    assert_format bad, """
    "this works" ++ (&String.upcase/1) ++ &String.downcase/1
    """

    bad = """
    "this works" +++ (&String.upcase/1) +++ (&String.downcase/1)
    """

    assert_format bad, """
    "this works" +++ (&String.upcase/1) +++ &String.downcase/1
    """

    bad = """
    "this works" | (&String.upcase/1) | (&String.downcase/1)
    """

    assert_format bad, """
    "this works" | (&String.upcase/1) | &String.downcase/1
    """

    bad = ~S"""
    "this works" \\ (&String.upcase/1) \\ (&String.downcase/1)
    """

    assert_format bad, ~S"""
    "this works" \\ &String.upcase/1 \\ &String.downcase/1
    """
  end

  test "multiline expression inside interpolation" do
    bad = """
    Logger.info("Example: #{inspect(%{a: 1, b: 2})}")
    """

    assert_format bad, """
    Logger.info("Example: #{inspect(%{a: 1, b: 2})}")
    """
  end

  test "comment inside operator with when" do
    bad = """
    raise function(x) ::
            # Comment
            any
    """

    assert_format bad, """
    # Comment
    raise function(x) ::
            any
    """

    bad = """
    raise function(x) ::
            # Comment
            any
          when x: any
    """

    assert_format bad, """
    raise function(x) ::
            any
          # Comment
          when x: any
    """

    bad = """
    @spec function(x) ::
            # Comment
            any
          when x: any
    """

    assert_format bad, """
    @spec function(x) ::
            any
          # Comment
          when x: any
    """

    bad = """
    @spec function(x) ::
            # Comment
            any
          when x
          when y
    """

    assert_format bad, """
    @spec function(x) ::
            any
          # Comment
          when x
          when y
    """
  end
end


Add support for :https_only_handler config option

Still defaults to `Sugar.Request.HttpsOnly`, but allows other plugs /
request handlers to be used (like `Plug.SSL`, for example).


Add more docs to phx.gen.auth tokens (#4405)

defmodule <%= inspect schema.module %>Token do
  use Ecto.Schema
  import Ecto.Query

  @hash_algorithm :sha256
  @rand_size 32

  # It is very important to keep the reset password token expiry short,
  # since someone with access to the email may take over the account.
  @reset_password_validity_in_days 1
  @confirm_validity_in_days 7
  @change_email_validity_in_days 7
  @session_validity_in_days 60
<%= if schema.binary_id do %>
  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id<% end %>
  schema "<%= schema.table %>_tokens" do
    field :token, :binary
    field :context, :string
    field :sent_to, :string
    belongs_to :<%= schema.singular %>, <%= inspect schema.module %>

    timestamps(updated_at: false)
  end

  @doc """
  Generates a token that will be stored in a signed place,
  such as session or cookie. As they are signed, those
  tokens do not need to be hashed.

  The reason why we store session tokens in the database, even
  though Phoenix already provides a session cookie, is because
  Phoenix' default session cookies are not persisted, they are
  simply signed and potentially encrypted. This means they are
  valid indefinitely, unless you change the signing/encryption
  salt.

  Therefore, storing them allows individual <%= schema.singular %>
  sessions to be expired. The token system can also be extended
  to store additional data, such as the device used for logging in.
  You could then use this information to display all valid sessions
  and devices in the UI and allow users to explicitly expire any
  session they deem invalid.
  """
  def build_session_token(<%= schema.singular %>) do
    token = :crypto.strong_rand_bytes(@rand_size)
    {token, %<%= inspect schema.module %>Token{token: token, context: "session", <%= schema.singular %>_id: <%= schema.singular %>.id}}
  end

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> found by the token, if any.

  The token is valid if it matches the value in the database and it has
  not expired (after @session_validity_in_days).
  """
  def verify_session_token_query(token) do
    query =
      from token in token_and_context_query(token, "session"),
        join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
        where: token.inserted_at > ago(@session_validity_in_days, "day"),
        select: <%= schema.singular %>

    {:ok, query}
  end

  @doc """
  Builds a token and its hash to be delivered to the <%= schema.singular %>'s email.

  The non-hashed token is sent to the <%= schema.singular %> email while the
  hashed part is stored in the database. The original token cannot be reconstructed,
  which means anyone with read-only access to the database cannot directly use
  the token in the application to gain access. Furthermore, if the user changes
  their email in the system, the tokens sent to the previous email are no longer
  valid.

  Users can easily adapt the existing code to provide other types of delivery methods,
  for example, by phone numbers.
  """
  def build_email_token(<%= schema.singular %>, context) do
    build_hashed_token(<%= schema.singular %>, context, <%= schema.singular %>.email)
  end

  defp build_hashed_token(<%= schema.singular %>, context, sent_to) do
    token = :crypto.strong_rand_bytes(@rand_size)
    hashed_token = :crypto.hash(@hash_algorithm, token)

    {Base.url_encode64(token, padding: false),
     %<%= inspect schema.module %>Token{
       token: hashed_token,
       context: context,
       sent_to: sent_to,
       <%= schema.singular %>_id: <%= schema.singular %>.id
     }}
  end

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> found by the token, if any.

  The given token is valid if it matches its hashed counterpart in the
  database and the user email has not changed. This function also checks
  if the token is being used within a certain period, depending on the
  context. The default contexts supported by this function are either
  "confirm", for account confirmation emails, and "reset_password",
  for resetting the password. For verifying requests to change the email,
  see `verify_change_email_token_query/2`.
  """
  def verify_email_token_query(token, context) do
    case Base.url_decode64(token, padding: false) do
      {:ok, decoded_token} ->
        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
        days = days_for_context(context)

        query =
          from token in token_and_context_query(hashed_token, context),
            join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
            where: token.inserted_at > ago(^days, "day") and token.sent_to == <%= schema.singular %>.email,
            select: <%= schema.singular %>

        {:ok, query}

      :error ->
        :error
    end
  end

  defp days_for_context("confirm"), do: @confirm_validity_in_days
  defp days_for_context("reset_password"), do: @reset_password_validity_in_days

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> found by the token, if any.

  This is used to validate requests to change the <%= schema.singular %>
  email. It is different from `verify_email_token_query/2` precisely because
  `verify_email_token_query/2` validates the email has not changed, which is
  the starting point by this function.

  The given token is valid if it matches its hashed counterpart in the
  database and if it has not expired (after @change_email_validity_in_days).
  The context must always start with "change:".
  """
  def verify_change_email_token_query(token, "change:" <> _ = context) do
    case Base.url_decode64(token, padding: false) do
      {:ok, decoded_token} ->
        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)

        query =
          from token in token_and_context_query(hashed_token, context),
            where: token.inserted_at > ago(@change_email_validity_in_days, "day")

        {:ok, query}

      :error ->
        :error
    end
  end

  @doc """
  Returns the token struct for the given token value and context.
  """
  def token_and_context_query(token, context) do
    from <%= inspect schema.module %>Token, where: [token: ^token, context: ^context]
  end

  @doc """
  Gets all tokens for the given <%= schema.singular %> for the given contexts.
  """
  def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all) do
    from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id
  end

  def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [_ | _] = contexts) do
    from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id and t.context in ^contexts
  end
end

defmodule <%= inspect schema.module %>Token do
  use Ecto.Schema
  import Ecto.Query

  @hash_algorithm :sha256
  @rand_size 32

  # It is very important to keep the reset password token expiry short,
  # since someone with access to the email may take over the account.
  @reset_password_validity_in_days 1
  @confirm_validity_in_days 7
  @change_email_validity_in_days 7
  @session_validity_in_days 60
<%= if schema.binary_id do %>
  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id<% end %>
  schema "<%= schema.table %>_tokens" do
    field :token, :binary
    field :context, :string
    field :sent_to, :string
    belongs_to :<%= schema.singular %>, <%= inspect schema.module %>

    timestamps(updated_at: false)
  end

  @doc """
  Generates a token that will be stored in a signed place,
  such as session or cookie. As they are signed, those
  tokens do not need to be hashed.
  """
  def build_session_token(<%= schema.singular %>) do
    token = :crypto.strong_rand_bytes(@rand_size)
    {token, %<%= inspect schema.module %>Token{token: token, context: "session", <%= schema.singular %>_id: <%= schema.singular %>.id}}
  end

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> found by the token.
  """
  def verify_session_token_query(token) do
    query =
      from token in token_and_context_query(token, "session"),
        join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
        where: token.inserted_at > ago(@session_validity_in_days, "day"),
        select: <%= schema.singular %>

    {:ok, query}
  end

  @doc """
  Builds a token with a hashed counter part.

  The non-hashed token is sent to the <%= schema.singular %> email while the
  hashed part is stored in the database, to avoid reconstruction.
  The token is valid for a week as long as <%= schema.singular %>s don't change
  their email.
  """
  def build_email_token(<%= schema.singular %>, context) do
    build_hashed_token(<%= schema.singular %>, context, <%= schema.singular %>.email)
  end

  defp build_hashed_token(<%= schema.singular %>, context, sent_to) do
    token = :crypto.strong_rand_bytes(@rand_size)
    hashed_token = :crypto.hash(@hash_algorithm, token)

    {Base.url_encode64(token, padding: false),
     %<%= inspect schema.module %>Token{
       token: hashed_token,
       context: context,
       sent_to: sent_to,
       <%= schema.singular %>_id: <%= schema.singular %>.id
     }}
  end

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> found by the token.
  """
  def verify_email_token_query(token, context) do
    case Base.url_decode64(token, padding: false) do
      {:ok, decoded_token} ->
        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
        days = days_for_context(context)

        query =
          from token in token_and_context_query(hashed_token, context),
            join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
            where: token.inserted_at > ago(^days, "day") and token.sent_to == <%= schema.singular %>.email,
            select: <%= schema.singular %>

        {:ok, query}

      :error ->
        :error
    end
  end

  defp days_for_context("confirm"), do: @confirm_validity_in_days
  defp days_for_context("reset_password"), do: @reset_password_validity_in_days

  @doc """
  Checks if the token is valid and returns its underlying lookup query.

  The query returns the <%= schema.singular %> token record.
  """
  def verify_change_email_token_query(token, context) do
    case Base.url_decode64(token, padding: false) do
      {:ok, decoded_token} ->
        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)

        query =
          from token in token_and_context_query(hashed_token, context),
            where: token.inserted_at > ago(@change_email_validity_in_days, "day")

        {:ok, query}

      :error ->
        :error
    end
  end

  @doc """
  Returns the given token with the given context.
  """
  def token_and_context_query(token, context) do
    from <%= inspect schema.module %>Token, where: [token: ^token, context: ^context]
  end

  @doc """
  Gets all tokens for the given <%= schema.singular %> for the given contexts.
  """
  def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all) do
    from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id
  end

  def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [_ | _] = contexts) do
    from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id and t.context in ^contexts
  end
end


Log Application start

Log Application start

# Inspired by http://pedroassumpcao.ghost.io/creating-a-supervision-tree-for-elixir-genevent-behavior/

defmodule Microcrawler do
    use Application

    def start(_type, _args) do
        Logger.info("Starting #{__MODULE__}")

        import Supervisor.Spec, warn: false

        children = [
            supervisor(Microcrawler.Supervisor.Event, [])
        ]

        opts = [strategy: :one_for_one, name: Microcrawler.Supervisor]
        Supervisor.start_link(children, opts)
    end
end

# Inspired by http://pedroassumpcao.ghost.io/creating-a-supervision-tree-for-elixir-genevent-behavior/

defmodule Microcrawler do
    use Application

    def start(_type, _args) do
        import Supervisor.Spec, warn: false

        children = [
            supervisor(Microcrawler.Supervisor.Event, [])
        ]

        opts = [strategy: :one_for_one, name: Microcrawler.Supervisor]
        Supervisor.start_link(children, opts)
    end
end


Fix food group display in admin

Fix food group display in admin

defmodule OpenPantry.ExAdmin.FoodGroup do
  use ExAdmin.Register

  register_resource OpenPantry.FoodGroup do

  end

  def display_name(food_group) do
    food_group.foodgroup_desc
  end
end

defmodule OpenPantry.ExAdmin.FoodGroup do
  use ExAdmin.Register

  register_resource OpenPantry.FoodGroup do

  end
end